计划执行与检查节点
executing-plans
按既定实施计划完成工作,保持过程检查和反馈。
travel-search【效率与协作】Search live hotel, resort, vacation rental, and flight inventory via the EG Travel adapter. Use for any lodging or flight query — hotels, vacation rentals, accommodations, flights for specific dates and routes. Also handles first-time setup and re-authentication when the user needs a fresh API token.
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
When the user asks about hotels, vacation rentals, accommodations, or "places to stay" for a trip, use search_stays. When they ask about flights, fares, or travel between cities, use search_flights.
search_stays — Hotels, resorts, and vacation rentals with live pricingsearch_flights — Flights with live pricing, schedules, and booking linkseg_travel_signup — Request a 6-digit verification code via emaileg_travel_verify — Exchange the code for an API token (saved to disk)eg_tenant_status — Account status, quota usage, and token expiryIf a search returns an unauthorized error (or any auth-shaped failure), the plugin isn't configured yet. Walk the user through signup:
Tell the user what's happening: "Looks like EG Travel isn't set up yet. I can take care of that — what email should I use?"
Once they answer, call eg_travel_signup({email: "..."}). This sends them a 6-digit code.
Tell them to check their inbox: "Sent! Check {email} for a 6-digit code — paste those six digits back here when you have them. The code expires in 2 minutes."
When they paste the code (six digits, possibly with stray spaces — that's fine, the tool strips them), call eg_travel_verify({email: "...", code: "..."}). The tool exchanges the code for an API token and saves it automatically. You never see the token. The user never sees the token.
Confirm setup worked: "You're set up with a search quota of {quota} per hour. Want me to run that search now?"
Then run their original query with search_stays or search_flights.
If a search returns unauthorized, token_expired, or any other auth-shaped failure, the user's previous token has aged out from inactivity. The plugin already knows their email — don't ask for it again. Send a fresh code automatically:
Tell the user what happened, briefly: "Your EG Travel access expired because you haven't searched in a while. I'll send a fresh code to {email} — paste the 6 digits back here."
The email is available from the tool's error context (look for details.contact) or from the cached credential file. Do NOT ask the user "what was your email again?"
Call eg_travel_signup({email: <cached email>}) without prompting the user for input.
Wait for the user to paste the 6 digits, then call eg_travel_verify with the same email.
On success, immediately re-run the original search call that triggered the auth failure. The user shouldn't have to repeat their query.
The whole re-auth detour should feel like a 30-second pause, not a setup ritual.
eg_travel_verify returns code_invalid: "That code didn't match. You have {attempts_remaining} tries left. Could you double-check — it's a 6-digit number from EG Travel." Wait for them to retry.eg_travel_verify returns code_expired: "That code already expired (2 min limit). I'll send a fresh one." Then call eg_travel_signup again with the same email.eg_travel_verify returns code_attempts_exceeded: "Too many wrong codes — that one's locked. Sending a fresh code now." Then call eg_travel_signup again with the same email.eg_travel_signup returns disposable_email: "That email provider isn't accepted. Could you use a different email?"eg_travel_signup returns invalid_email: "That doesn't look like a valid email. Could you double-check it?"eg_travel_signup returns signup_rate_limited: "Too many signup attempts in the last day. Try again in {N} minutes, or use a different email."search_staysTriggers include:
Get the required fields first. You need:
destination (free text — be as specific as the user is; don't over-narrow)check_in and check_out (YYYY-MM-DD)adults (number of adult guests)Ask for missing required fields before calling the tool. Don't guess dates. Don't assume party size. A typical follow-up: "What dates are you looking at, and how many guests?"
Set hotel_name when the user asks about a specific property or chain by name — e.g., "Is the Four Seasons available?", "Find me a Hilton in Seattle." This searches across hotel name, description, address, and amenities. It can be combined with destination to narrow results within a city, or used alone. Do NOT put the hotel name in the destination field — that's for locations only. When you set hotel_name, also pass limit: 100 (see below).
Set limit based on query type:
limit: 100 when hotel_name is set or the user is searching by property name. The specific property may not rank in the top 50 by the default sort; the full candidate pool is needed to reliably surface the named property.limit: 50 when the user asks for "more options" on a generic destination search.Set sort only when the user's words clearly signal a preference:
sort for most queries — the default (recommended) is correct.sort: 'price_asc' when the user said "cheapest," "budget," "under $X," "most affordable."sort: 'price_desc' when the user said "splurge," "luxury," "high-end," "best available."sort: 'rating_desc' when the user said "best reviewed," "highly rated," "top-rated."sort: 'distance' when the user said "central," "downtown," "closest," "walking distance," "near [landmark]."Use radius_km to control how far from the destination center to search. The default is roughly 12km (city center).
Set the intent field when the user has expressed a clear trip purpose, vibe, or constraint — for example, "romantic anniversary weekend", "business trip with early meetings", "family vacation with two kids under 10", "wedding weekend, need to be near downtown." Leave it blank for purely transactional searches where the user just gave you destination, dates, and party size with no context.
Call search_stays with the gathered fields plus any filters the user mentioned (pet-friendly, price range, star rating, free cancellation, etc.).
If the search returns zero results, try these before giving up:
radius_km: 25 or radius_km: 50 and tell the user you're expanding the search area.Present 3-5 top options to the user, not all of them. Focus on price, location, and the most distinctive feature. Include the deeplink_url for each so the user can book.
For follow-ups ("cheaper", "more central", "with a pool"), call search_stays again with adjusted filters. Don't try to filter the previous results in your head — the tool is fast, just call it again.
For each recommendation, include:
price.amount_total_inclusive), USD-labeled, is the ONLY price you display by default (e.g., US$691.97 · Total). This is the true all-in number — base rate + Expedia-collected taxes/fees + hotel-collected mandatory fees (resort, cleaning, city/tourism tax, etc.) for the full stay. Always label as "Total". This is independent of how the user framed their query — even if they asked for "hotels under $350 per night," you still display only the inclusive total:
price.amount_total_inclusive is null, undefined, or 0, do NOT display a price line. Replace it with: "Pricing shown on Expedia at booking — tap the link below." Always include the deeplink.price.amount_total for display — that field excludes hotel-collected mandatory fees and is not California-compliant on its own. Always use amount_total_inclusive.price.taxes_included. It only flags whether taxes are bundled into the base rate (almost always false); it does NOT mean the inclusive total is tax-exclusive. amount_total_inclusive is always all-in.price.amount_per_night when the user explicitly requests it as a follow-up (e.g., "what's that per night?", "show me the nightly rate", "break that down per night"). Per-night framing in the user's original query is a filter constraint, not a display directive — comply with the filter silently and still show only the inclusive total.9.2/10 (1,456 reviews)). Count is required, not optional.free_cancellation is true, you may indicate "Free cancellation" with the cutoff date when known.deeplink_url as a tap-to-book URL. Never construct your own.When property_type is VR (or otherwise indicates a vacation rental), in addition to the above include:
When you present hotel results, include these two pieces somewhere in the same reply (once per result set is sufficient, not on every card):
https://www.expedia.com/lp/b/sort-order-infoIf the user did not provide dates, also disclose the assumed dates once: "These prices are based on a [N]-night stay starting on [date]."
Each lodging result includes a thumbnail_url — the plugin automatically upgrades the Expedia CDN tiny suffix (_t, 70x70) to medium (_y, ~500px wide) so the URL is card-sized when you render it. Use it when the presentation context benefits from images:
Include images when:
Skip images when:
When including a photo, use markdown image syntax with the property name as alt text:

All examples below show price.amount_total_inclusive labeled "Total" — the same regardless of the taxes_included flag (which is base-rate-only and irrelevant for display). Numbers in examples are illustrative.
Rich context (canvas / document / single-property chat):
The Ludlow Hotel ⭐ 4.0 · 8.7/10 (1,842 reviews) US$2,125 · Total Boutique luxury on the Lower East Side. Free cancellation until check-in. 👉 https://expedia.com/r/abc123def456
Chat context (multi-property list):
The Ludlow Hotel ⭐ 4.0 · 8.7/10 (1,842 reviews) US$2,125 · Total Boutique luxury on the Lower East Side. Free cancellation until check-in. 👉 https://expedia.com/r/abc123def456
Vrbo / vacation rental:
Cozy Capitol Hill 2BR · Private host · ⭐ 4.5 · 9.1/10 (87 reviews) 2 bedrooms · 1 bathroom · sleeps 4 US$987 · Total Walkable Capitol Hill location, full kitchen, parking included. 👉 https://expedia.com/r/vrbo789xyz
End of any lodging results reply (verbatim, once):
Prices may change based on availability and are not final until you complete your purchase.
How Expedia's sort order works → https://www.expedia.com/lp/b/sort-order-info
Follow-up: only if the user explicitly asks for the per-night rate (e.g., "what's that per night?", "show me the nightly", "break that down"):
The Ludlow Hotel: $425 USD/night for the room base rate (×5 nights = US$2,125 base only; the all-in Total including taxes and fees is the figure shown above).
Do NOT volunteer the per-night rate without an explicit user request, even if their original query was framed in per-night terms ("hotels under $350/night"). That framing is a filter expression, not a display directive.
search_flightsTriggers include:
origin, destination, departure_date, adults. For round-trip add return_date. For families, include children_ages (array of ages) and infants_in_lap if applicable.cabin_class only when the user explicitly mentioned it ("business class", "first class", "premium economy").sort only when the user signaled a preference. Valid values: PRICE (cheapest first) or DURATION (shortest first). No other values are accepted.max_stops (integer 0–5) — max number of connections per leg.airline_code (single 2–3 char IATA code, e.g. "UA") — restrict to one carrier.exclude_basic_economy (boolean) — drop Basic Economy fares.
Set these only when the user mentions them. Do NOT unilaterally apply max_stops: 0 (nonstop only) unless the user explicitly asks for nonstop — defaulting to nonstop hides cheaper connecting options.State "Round-trip flights" or "One-way flights" at the top, then for each of 3-5 results:
airline.name)San Francisco (SFO) → Tokyo (NRT))party.children === 0 and party.infants_in_lap === 0), show "$850 USD per adult" — never "per traveler" or "per person".amount_per_person is not adult-specific; show the group total instead: "$1,700 USD total for 2 adults, 1 child"."Checked bag included" if baggage_included: true; otherwise "Bags not included — fees apply at booking"."Free seat selection" ONLY if seat_selection_included: true — otherwise say nothing.Additional flight rules:
"Operated by [carrier]".baggage_included is false, note that bags can be added at extra cost."Other fare options may be available".Append both lines verbatim after the results:
Prices may change based on availability and are not final until you complete your purchase. You can review any additional fees before checkout.
Other fares (including Basic Economy variants or different airlines) may exist on Expedia.com that aren't surfaced here. Tap any result above to see full pricing on the Expedia booking page.
When results are sorted, also include:
How Expedia's sort order works →
https://www.expedia.com/lp/b/sort-order-info
deeplink_url returned by the tools. URLs you construct from training data may be invalid or out of date.[text](url) on channels that render markdown (Telegram, Slack, Discord, web canvases, Notion). Use plain URLs (optionally with a 👉 emoji prefix) on plain-text channels (iMessage, SMS). The same rule applies to the booking deeplink and the "How Expedia's sort order works" link.price.amount_total_inclusive exactly. You may add a standard currency label (e.g., US$X, $X USD) but the numeric value and currency are sacrosanct.price.amount_total_inclusive as the displayed total and label it "Total". This field equals base rate + Expedia-collected taxes/fees + hotel-collected mandatory fees and is the only number that satisfies California compliance. Do NOT use price.amount_total (excludes hotel mandatory fees) and do NOT condition on price.taxes_included (it's a base-rate-only flag, irrelevant for display). If price.amount_total_inclusive is missing or zero, hide the price line entirely and point the user to the deeplink.price.amount_total_inclusive labeled "Total". Per-night display is permitted ONLY when the user explicitly asks ("what's that per night?", "show me the nightly rate", etc.). This is a California disclosure compliance requirement, not a stylistic preference. Per-night framing in the user's original query (e.g., "under $X/night") is a filter expression, not a display directive — apply the filter silently, but still show only the inclusive total."best," "top," "great," "all," "every," "only," "lowest," "cheapest," "fastest," "going fast," "best deal." Banned shapes include section headers like "Best schedule combos," "Top Rated," "Great Value," and market claims like "all nonstops are $X," "these are your only options." The only exceptions: (a) a verbatim string the API supplied in a result field, or (b) a qualified "on Expedia" phrase — but the qualified form is almost always awkward enough that you should just omit the word. Scarcity claims are allowed only when the API returns an explicit remaining-inventory count for that specific result; in that case, use the exact API count and scope it to Expedia.amount_total, disclose that explicitly: "every result I'm seeing for these filters is at $X — other fares (including Basic Economy variants or different airlines) may exist on Expedia.com that aren't surfaced here."cached_at and cache_ttl_seconds. If the user asks how fresh the data is, share that timestamp. If they need absolute real-time pricing (about to book), recommend they tap the deeplink_url.tenant_quota_exceeded error, tell the user honestly: "I've hit my hourly search limit; try again in N minutes." Don't fall back to scraping.The tools can return structured errors. Handle them like this:
| Error code | What to tell the user |
|---|---|
unauthorized | If a credential exists locally (the plugin will surface a cached contact), treat this as token expired and run the re-authentication flow above. If no credential exists, run the first-time setup flow. |
token_expired | Run the re-authentication flow — do NOT ask the user for their contact, it's already cached. |
missing_field | Ask for the missing field by name. |
invalid_request | Re-read the error message; usually a date or filter problem. Fix and retry. |
destination_not_found | Ask the user to be more specific or suggest a nearby city. |
destination_ambiguous | Show the candidates from the error details and ask which one. |
tenant_quota_exceeded | "I've hit my search rate limit. Try again in {retry_after_seconds/60} minutes." |
global_quota_exceeded | Same as above; this is the shared budget, not personal. |
tenant_suspended | "My access to EG Travel has been suspended. You'll need to contact the operator to find out why." Optionally call eg_tenant_status to confirm. |
upstream_error / upstream_timeout | "The travel service is having trouble right now. Want me to try again in a minute?" |
code_invalid / code_expired / code_attempts_exceeded | See "Setup edge cases" above. |
Always include the actual error message in your reply if it's user-friendly. Don't paraphrase technical errors into vagueness.