{"id":"8a1d9a41-554a-46af-b7e4-a0a8ac3bc4de","entityType":"agent","slug":"clawhub-jason-vaughan-airbnb-gateway","name":"Airbnb Gateway","canonicalUrl":"https://www.xpersona.co/agent/clawhub-jason-vaughan-airbnb-gateway","canonicalPath":"/agent/clawhub-jason-vaughan-airbnb-gateway","generatedAt":"2026-10-11T10:52:48.678Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:49:16.466Z","emptyReason":null},"description":"A skill for safe, coherent Airbnb operations in OpenClaw-style agent environments. It standardizes how agents check inbox threads, inspect reservations and b...","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17b5fr2wfb8eecm8nk9kswmtd87hw31:airbnb-gateway","sourceUrl":"https://clawhub.ai/jason-vaughan/airbnb-gateway","homepage":"https://clawhub.ai/jason-vaughan/skills/airbnb-gateway","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/jason-vaughan/airbnb-gateway","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/jason-vaughan/skills/airbnb-gateway","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":61,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Airbnb Gateway technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:49:16.466Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:49:16.466Z","emptyReason":null},"stars":null,"forks":null,"downloads":1120,"packageName":null,"latestVersion":"0.2.1","tractionLabel":"1.1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:49:16.452Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T07:49:16.466Z","lastCrawledAt":"2026-10-11T07:49:16.452Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T07:49:16.452Z","lastVerifiedAt":null,"highlights":[{"version":"0.2.1","createdAt":"2026-07-09T21:20:40.243Z","changelog":"v0.2.1 — doc-consistency + safety patch (resolves SkillSpector findings): reconciled stale v1 'calendar read-only/reserved-for-v2' language to the v0.2 MUTATE-CAL (approval-gated) vs MUTATE-RESTRICTED (refused) split across all files; added a prominent 'changes live production inventory' warning to the calendar-mutation procedure. No behavior change.","fileCount":19,"zipByteSize":35990},{"version":"0.2.0","createdAt":"2026-07-04T21:29:40.368Z","changelog":"Approval-gated calendar mutations (MUTATE-CAL): block/open dates and nightly price with explicit per-operation operator approval, mandatory fresh-load verification, and reported inverse operation. MUTATE-RESTRICTED (listing edits, accept/decline, refunds) still refused. New live-verified calendar-mutation procedure reference and NVIDIA-format SKILL_CARD.md with release evidence.","fileCount":19,"zipByteSize":32025},{"version":"0.1.4","createdAt":"2026-06-04T20:30:59.046Z","changelog":"Add a friendly star call-to-action to the listing (SKILL.md/README/CLAWHUB.md). Content-only, no behavior change.","fileCount":17,"zipByteSize":26363},{"version":"0.1.3","createdAt":"2026-06-03T21:13:07.331Z","changelog":"Clean re-cut for review handoff; no content or doctrine change since 0.1.2.","fileCount":17,"zipByteSize":26164},{"version":"0.1.2","createdAt":"2026-06-03T20:33:47.466Z","changelog":"Pre-install polish (no doctrine change): explicit Minimum Environment Contract (read-only vs send-capable vs optional), renderer-safe command surface. Verified all references/examples ship in the artifact.","fileCount":17,"zipByteSize":26198},{"version":"0.1.1","createdAt":"2026-06-03T20:06:58.325Z","changelog":"Align published summary with the intended ClawHub listing copy (description rewrite only; no behavior change).","fileCount":17,"zipByteSize":25403},{"version":"0.1.0","createdAt":"2026-06-03T20:06:04.189Z","changelog":"Initial release: safe end-to-end Airbnb ops (inbox/thread reads, reservation lookup, booking summary, calendar inspection) + verified single-send state machine (drafted/attempted/confirmed/unconfirmed/failed, no auto-resend).","fileCount":17,"zipByteSize":25012}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17b5fr2wfb8eecm8nk9kswmtd87hw31:airbnb-gateway","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17b5fr2wfb8eecm8nk9kswmtd87hw31:airbnb-gateway` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/jason-vaughan/airbnb-gateway before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T10:52:48.674Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-jason-vaughan-airbnb-gateway/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T07:49:16.466Z","emptyReason":null},"readme":"Skill: Airbnb Gateway\n\nOwner: jason-vaughan\n\nSummary: A skill for safe, coherent Airbnb operations in OpenClaw-style agent environments. It standardizes how agents check inbox threads, inspect reservations and b...\n\nTags: airbnb:0.1.4, latest:0.2.1, messaging:0.1.4, operations:0.1.4, vacation-rental:0.1.4\n\nVersion history:\n\nv0.2.1 | 2026-07-09T21:20:40.243Z | user\n\nv0.2.1 — doc-consistency + safety patch (resolves SkillSpector findings): reconciled stale v1 'calendar read-only/reserved-for-v2' language to the v0.2 MUTATE-CAL (approval-gated) vs MUTATE-RESTRICTED (refused) split across all files; added a prominent 'changes live production inventory' warning to the calendar-mutation procedure. No behavior change.\n\nv0.2.0 | 2026-07-04T21:29:40.368Z | user\n\nApproval-gated calendar mutations (MUTATE-CAL): block/open dates and nightly price with explicit per-operation operator approval, mandatory fresh-load verification, and reported inverse operation. MUTATE-RESTRICTED (listing edits, accept/decline, refunds) still refused. New live-verified calendar-mutation procedure reference and NVIDIA-format SKILL_CARD.md with release evidence.\n\nv0.1.4 | 2026-06-04T20:30:59.046Z | user\n\nAdd a friendly star call-to-action to the listing (SKILL.md/README/CLAWHUB.md). Content-only, no behavior change.\n\nv0.1.3 | 2026-06-03T21:13:07.331Z | user\n\nClean re-cut for review handoff; no content or doctrine change since 0.1.2.\n\nv0.1.2 | 2026-06-03T20:33:47.466Z | user\n\nPre-install polish (no doctrine change): explicit Minimum Environment Contract (read-only vs send-capable vs optional), renderer-safe command surface. Verified all references/examples ship in the artifact.\n\nv0.1.1 | 2026-06-03T20:06:58.325Z | user\n\nAlign published summary with the intended ClawHub listing copy (description rewrite only; no behavior change).\n\nv0.1.0 | 2026-06-03T20:06:04.189Z | user\n\nInitial release: safe end-to-end Airbnb ops (inbox/thread reads, reservation lookup, booking summary, calendar inspection) + verified single-send state machine (drafted/attempted/confirmed/unconfirmed/failed, no auto-resend).\n\nArchive index:\n\nArchive v0.2.1: 19 files, 35990 bytes\n\nFiles: CHANGELOG.md (6270b), CLAWHUB.md (1406b), examples/calendar-inspection.md (2529b), examples/check-inbox.md (1745b), examples/read-thread.md (1678b), examples/reservation-lookup.md (2158b), examples/send-reply-with-verification.md (2886b), LICENSE (1070b), README.md (3372b), references/airbnb-message-state-machine.md (4677b), references/airbnb-safety-rules.md (4373b), references/airbnb-tool-priority.md (3317b), references/calendar-mutation-procedure.md (7426b), references/future-adapter-interface.md (3568b), SKILL_CARD.md (4966b), skill-card.md (2873b), SKILL.md (15193b), state/send-log.schema.json (1642b), _meta.json (133b)\n\nFile v0.2.1:SKILL.md\n\n---\nname: airbnb-gateway\ndescription: >\n  A skill for safe, coherent Airbnb operations in OpenClaw-style agent\n  environments. It standardizes how agents check inbox threads, inspect\n  reservations and booking state, review calendar context, draft guest replies,\n  send messages safely, verify whether a send actually appeared in the live\n  Airbnb thread, and make operator-approved, per-operation, independently-verified\n  calendar mutations (block/open dates, nightly price). It teaches a strict operating model: prefer Airbnb-native\n  endpoints before generic browser automation; treat send acknowledgments as\n  attempted, not automatically confirmed; verify outbound messages in the live\n  thread UI before declaring success; and never auto-resend from ambiguous or\n  unconfirmed state. Designed to reduce duplicate messages, normalize agent\n  behavior, and provide a safer foundation for Airbnb messaging, bookings, and\n  calendar workflows — useful standalone today and as a companion to a more\n  formal Airbnb adapter/tool layer tomorrow.\nversion: 0.2.1\nrisk: write-gated\ntags: [airbnb, vacation-rental, messaging, operations, openclaw]\nlicense: MIT\nhomepage: \"\"\nmaintainer: \"\"\n---\n\n# airbnb-gateway\n\n> ⭐ **Find this skill useful?** If `airbnb-gateway` saves you time, please\n> **star it** (the ⭐ at the top of this ClawHub page) — stars help other rental\n> operators discover it and keep it maintained. Thank you!\n\nYou are operating a live, revenue-bearing Airbnb account on behalf of a real\nhost. Guests are real people. A duplicate message, a wrong send, or a careless\ncalendar change has real consequences. This skill exists so that **every agent\nhandles Airbnb identically and safely**, instead of improvising tool calls per\nturn.\n\n> **Portability note.** This skill is environment-agnostic. It refers to tool\n> *roles* (an \"Airbnb messages endpoint\", an \"agent-browser\", a \"DevTools\n> bridge\", a \"Playwright fallback\") rather than hard-coded URLs. Your concrete\n> tool names live in `references/airbnb-tool-priority.md` — edit that one file\n> to map roles to the actual tools in your deployment. Everything else here is\n> universal.\n\n---\n\n## THE FIVE LAWS (non-negotiable)\n\n1. **Platform-first.** Always try the first-class Airbnb endpoint for an\n   operation before any browser automation. Browser weirdness is NOT evidence\n   that Airbnb access is down.\n2. **Read before write.** Never send a reply without first reading the live\n   thread in the *same* operation.\n3. **`sent: true` is `attempted`, not `confirmed`.** An endpoint success proves\n   the call returned — not that the guest can see the message. You MUST re-read\n   the thread and SEE the outbound message before marking `confirmed`.\n4. **Never auto-resend an `unconfirmed` send.** Duplicate guest messages are\n   worse than a late reply. Escalate to a human instead.\n5. **Writes gate; mutations gate harder.** Sending a message is approval-gated.\n   Calendar mutations (nightly price, block/open dates) are permitted ONLY under\n   the explicit per-operation approval gate below (see \"Approved calendar\n   mutations\") and are ALWAYS verified after. Listing edits and accept/decline\n   are NOT implemented — refuse and escalate.\n\nIf you are ever unsure, STOP and report. A paused agent is recoverable; a\nduplicated or wrong guest message is not.\n\n---\n\n## Minimum Environment Contract\n\nUse this to decide, before installing, whether your deployment can run the skill\nand at what level. Map each role to a real tool in\n`references/airbnb-tool-priority.md`. The skill degrades gracefully: if a\n**read-only** requirement is met but a **send** requirement is not, the skill\nruns in read-only mode and refuses sends rather than improvising.\n\n**Read-only mode — minimum to run safely (inbox / threads / reservations / calendar):**\n- At least one **Airbnb read path** — list/read messages, reservations, and\n  calendar (first-class endpoint preferred; agent-browser or DevTools read\n  acceptable as fallback).\n\nThat's the entire requirement for every READ verb. Nothing can be sent in this\nmode.\n\n**Send-capable mode — additional minimum to send a guest reply:**\n- An **Airbnb send path** — the first-class send endpoint (preferred) OR an\n  explicitly human-approved browser send.\n- A **thread re-read path** — to verify the outbound message after sending.\n  (The same read path from read-only mode satisfies this.)\n\nIf either is missing, do NOT send: stay in read-only mode and escalate.\n\n**Optional enhancements — improve safety / fallback depth when present:**\n- **Agent-browser** — navigate/inspect when an endpoint is missing or hard-down.\n- **DevTools bridge** — read-only DOM inspection to verify UI state.\n- **Playwright fallback** — last-resort automation for read-only ops.\n- **Persistent send ledger** — survives restarts; hardens duplicate prevention.\n- **Approval channel** — human/approver sign-off before a send.\n\nAbsent optional capabilities reduce fallback depth but never change the safety\nrules. A required capability that is absent for a given operation triggers an\nescalation, never an improvised workaround.\n\n---\n\n## Operating model — which tool path do I use?\n\nResolve top-down. Drop to the next tier ONLY when the current tier is genuinely\nunavailable, not merely \"looked weird once.\" Full detail in\n`references/airbnb-tool-priority.md`.\n\n```\nOPERATION\n  │\n  ├─ 1. AIRBNB ENDPOINT  (first-class, structured, auth-aware)\n  │       → DEFAULT for every supported operation. Always start here.\n  │\n  ├─ 2. AGENT-BROWSER  (navigate / status)\n  │       → ONLY when no endpoint covers the op, OR an endpoint returned a hard\n  │         transport failure (5xx/timeout) TWICE. First confirm the host\n  │         browser identity is alive before assuming auth loss.\n  │\n  ├─ 3. DEVTOOLS  (tabs / navigate / evaluate — READ-ONLY here)\n  │       → DOM inspection / UI verification when the endpoint read is\n  │         ambiguous. Never use evaluate to perform a write/click-send.\n  │\n  └─ 4. PLAYWRIGHT  (fallback)\n          → LAST RESORT. Only when 1–3 are unavailable AND the op is read-only\n            or an explicitly human-approved write.\n```\n\n---\n\n## Safety model\n\nClassify every operation before acting. Full table in\n`references/airbnb-safety-rules.md`.\n\n| Tier | Examples | Gate |\n|---|---|---|\n| **READ** | check inbox, read thread, lookup reservation, inspect calendar | None. Proceed. |\n| **WRITE** | send a guest reply | Approval if configured; ALWAYS verify after. |\n| **MUTATE-CAL** | change nightly price, block/open calendar dates | Explicit operator approval per operation (see \"Approved calendar mutations\"). ALWAYS verify after. |\n| **MUTATE-RESTRICTED** | edit listing content, accept/decline bookings, refunds, payouts | **NOT in this version.** Refuse + escalate. |\n\n**Escalate to a human when:** a send reaches `unconfirmed` or `failed`; you're\nasked to perform any MUTATE-RESTRICTED op, or a MUTATE-CAL op without the approval gate satisfied; the host browser identity reports unhealthy; two\nread paths disagree on a material fact (dates, guest count, price); or you would\nneed to send the same message twice for any reason.\n\n---\n\n## Message send — the state machine (the core procedure)\n\nStates: `drafted → attempted → (confirmed | unconfirmed | failed)`. Full machine\nwith timings in `references/airbnb-message-state-machine.md`.\n\n```\n[drafted]      reply composed; thread + dedupe-key recorded\n   │           (WRITE gate: get approval if required)\n   ▼\n[attempted]    ← send endpoint called EXACTLY ONCE; ledger written NOW\n   │             endpoint sent:true  →  ATTEMPTED, not done\n   │\n   ├─ verify: re-read the SAME thread (endpoint; DevTools if ambiguous)\n   │\n   ├─ outbound visibly present?  ── yes ──► [confirmed]   ✅ report\n   ├─ absent after verify window ── no  ──► [unconfirmed] ⚠️ DO NOT RESEND, escalate\n   └─ send call errored                  ► [failed]       ❌ re-read first, escalate, no blind resend\n```\n\n### Send procedure (follow exactly)\n1. **Read the live thread.** Capture the last inbound message + a dedupe-key\n   (`thread_id` + normalized hash of the draft text).\n2. **Check the send ledger.** If a `confirmed` (or recent `attempted`) send with\n   the same dedupe-key exists → STOP, already handled.\n3. **Draft** the reply → state `drafted`.\n4. **Approval gate** if required — present draft + thread context, wait for \"go\".\n5. **Send exactly once** via the first-class send endpoint → state `attempted`.\n   Write the ledger entry *before* verifying, so a crash mid-verify can't cause\n   a blind resend.\n6. **Verify** — re-read the thread, look for the outbound text/timestamp.\n7. **Confirm or not** — visible → `confirmed`; absent within window →\n   `unconfirmed`.\n8. **Report** final status: thread id, state, and the human action needed.\n\nThe duplicate-prevention contract: exactly ONE call to the send endpoint per\n`drafted` item, ever. `unconfirmed`/`failed` NEVER auto-transition to a new send.\nOnly a human, after reading the live thread, may authorize a retry.\n\n---\n\n\n## Approved calendar mutations (v0.2)\n\nCalendar blocks/opens and nightly-price changes are permitted ONLY when ALL of the following hold:\n\n1. **The operator (host) explicitly requested this specific operation** in the current conversation — naming the exact date(s) or field and the desired end state.\n2. **The request carries the word APPROVED** (or the operator replies APPROVE to your restatement of the operation). No approval word, no mutation — ask for it.\n3. **One operation per approval.** Never batch beyond exactly what was named. Never generalize (\"block June 16\" does not mean \"block that week\").\n4. **Execute via the preferred tool order** (native endpoint first, then agent-browser / DevTools per the tool-priority reference). For calendar mutations specifically, follow `references/calendar-mutation-procedure.md` step by step — it is the verified procedure; do not improvise.\n5. **Verify after, independently**: re-read the calendar (fresh page state) and report before-state, action taken, after-state. If verification is ambiguous, report `unconfirmed` and STOP — never retry a mutation from ambiguous state.\n6. **Report reversibility**: state the exact inverse operation so the operator can undo with one instruction.\n\nEverything in MUTATE-RESTRICTED stays forbidden regardless of approval wording — escalate to the host instead.\n\n## Reservation & calendar reads\n\nThese three verbs are strictly READ (tier READ, no gate). They never change the\ncalendar — reporting and flagging only:\n\n- **`booking_summary`** — list reservations sorted by check-in: guest, dates,\n  status, `reservation_id`, linked `thread_id`.\n- **`lookup_reservation`** — by id or guest; map reservation ↔ thread so a reply\n  ties to the right booking.\n- **`inspect_calendar`** — report blocked/open/booked spans; FLAG (don't fix)\n  high-risk states (double-booking, anomalous price, risky back-to-back gaps).\n\n*Changing* the calendar (block/open dates, nightly price) is a separate,\napproval-gated **MUTATE-CAL** operation — see \"Approved calendar mutations (v0.2)\"\nabove; it runs the read → propose → approve → do-once → verify → report shape and\nnever happens as a side effect of a read. **Accept/decline bookings and listing\nedits** remain out of scope: refuse + escalate.\n\n---\n\n## Command surface\n\nAgents speak only in these verbs; each maps to a canonical procedure. Format is\n`verb (TIER) — description`.\n\n**READ verbs (no gate):**\n- `check_inbox` **(READ)** — list threads needing attention, prioritized.\n- `read_thread <id>` **(READ)** — full live thread + guest-intent summary.\n- `lookup_reservation <id-or-guest>` **(READ)** — reservation details + linked thread.\n- `booking_summary` **(READ)** — upcoming bookings, sorted by check-in.\n- `inspect_calendar [range]` **(READ)** — calendar state + flagged risks.\n- `verify_sent <thread> <draft>` **(READ)** — re-check a thread for an outbound message.\n\n**WRITE verbs (gated + verified):**\n- `draft_reply <thread> <intent>` **(WRITE-pre)** — produce a reviewable draft; result state `drafted`.\n- `send_reply <thread> <draft>` **(WRITE)** — run the full send state machine.\n\n**Status:**\n- `report_status` — emit structured status of the last operation.\n\n`send_reply` is the only guest-messaging write, and it internally enforces\nread → draft → approve → send-once → verify → report. Agents must not decompose\nit into lower-level steps to skip verification. Calendar mutation (MUTATE-CAL) is\nnot a command verb here — it is the separate, approval-gated procedure in\n`references/calendar-mutation-procedure.md` (see \"Approved calendar mutations\n(v0.2)\"), which follows the same read → approve → do-once → verify → report shape.\n\n---\n\n## Future adapter compatibility\n\nThis skill is the *behavioral contract*. A future formal adapter/tool layer\nshould harden the *mechanical guarantees* so correctness doesn't depend on a\nmodel remembering a markdown rule. See `references/future-adapter-interface.md`.\n\nIdeal future adapter functions (call them if present, fall back to the manual\nprocedure if not):\n\n- `airbnb.sendVerified(thread_id, text, dedupe_key)` → returns\n  `{state, visible_at}` after doing send-once + ledger + verify atomically.\n- `airbnb.dedupeCheck(dedupe_key)` → `{already_sent: bool, last_state}`.\n- `airbnb.readThread(thread_id)` / `airbnb.listInbox()` /\n  `airbnb.listReservations()` / `airbnb.readCalendar(range)`.\n\nWhen `sendVerified` exists, `send_reply` delegates to it and simply reports the\nreturned state. When it doesn't, `send_reply` runs the manual procedure above.\n\n---\n\n## Anti-patterns (do NOT do these)\n\n- ❌ **Jumping to Playwright early.** Endpoints exist and are preferred; falling\n  to Playwright because something felt slow once is wrong.\n- ❌ **Treating `sent: true` as confirmed.** It's `attempted`. Always verify.\n- ❌ **Resending from `unconfirmed`.** This creates duplicate guest messages.\n  Escalate instead.\n- ❌ **\"Auth must be dead.\"** Missing local browser/session state is NOT proof —\n  auth is host-owned. Check browser status + an endpoint first.\n- ❌ **Mixing read-safe and write-risk behavior.** Classify every op; never let\n  a read workflow quietly perform a write.\n- ❌ **Improvising the send flow per turn.** There is exactly ONE send\n  procedure. Use it.\n\n---\n\n## Maintainer notes\n\n- **Customize per deployment:** `references/airbnb-tool-priority.md` (map roles\n  to real tool names), the approval-gate policy, and whether a persistent ledger\n  is wired. Examples under `examples/` are illustrative — adapt payloads to your\n  tools.\n- **Keep universal:** the Five Laws, the send state machine, the safety tiers,\n  and the command vocabulary. These are the portable core; don't fork them per\n  deployment.\n- **Evolving the skill:** add new operations by giving each its own gated\n  workflow in the same shape as the send machine. Never add a generic \"do\n  anything\" write. Bump `version`; record changes in a CHANGELOG if published.\n\nFile v0.2.1:README.md\n\n# airbnb-gateway\n\nA reusable OpenClaw / Codex-style **skill package** for safe, coherent,\nend-to-end Airbnb host operations: inbox checks, thread reading, reservation\nlookup, booking summaries, calendar inspection, draft replies, **verified**\nmessage sending, and disciplined escalation.\n\n> ⭐ **Find this useful?** If `airbnb-gateway` saves you time, please **star it on ClawHub** — stars help other operators discover it and keep it maintained. Thank you!\n\nIt does not add transport. It orchestrates whatever Airbnb tooling your\nenvironment already has — first-class Airbnb endpoints, agent-browser, DevTools,\nPlaywright — behind one consistent operating model so multiple agents behave\nidentically and never duplicate a guest message.\n\n## Why this exists\n\nTwo reliability lessons are baked into the design:\n\n1. **Browser weirdness ≠ Airbnb down.** Auth is host-owned; prefer platform-aware\n   endpoints before generic browser automation.\n2. **`sent: true` ≠ delivered.** A send is only `confirmed` after re-reading the\n   live thread and *seeing* the message. Endpoint success is just `attempted`,\n   and an `unconfirmed` send is **never** auto-resent.\n\n## Install / use\n\n1. Drop `skills/airbnb-gateway/` into your skills library.\n2. Edit **`references/airbnb-tool-priority.md`** — map the abstract tool roles to\n   the real tool names in your deployment. This is the only required\n   customization.\n3. (Optional) Set your approval policy and wire a persistent send ledger.\n4. Point your agents at the skill. They should speak only in the command verbs\n   (`check_inbox`, `read_thread`, `send_reply`, …) and never call low-level\n   Airbnb tools directly.\n\n## What's portable vs. deployment-specific\n\n| Portable (don't fork) | Deployment-specific (customize) |\n|---|---|\n| The Five Laws | role → tool name map |\n| Send state machine | approval policy |\n| Safety tiers (READ/WRITE/MUTATE) | persistent ledger wiring |\n| Command vocabulary | example payload shapes |\n\n## Layout\n\n```\nairbnb-gateway/\n├── SKILL.md                              # the operating contract (start here)\n├── README.md                             # this file\n├── CHANGELOG.md\n├── LICENSE\n├── references/\n│   ├── airbnb-tool-priority.md           # ← customize per deployment\n│   ├── airbnb-message-state-machine.md   # universal\n│   ├── airbnb-safety-rules.md            # universal\n│   └── future-adapter-interface.md       # how to pair with a code adapter later\n├── examples/\n│   ├── check-inbox.md\n│   ├── read-thread.md\n│   ├── send-reply-with-verification.md   # the critical path\n│   ├── reservation-lookup.md\n│   └── calendar-inspection.md\n└── state/\n    └── send-log.schema.json              # append-only dedupe ledger schema\n```\n\n## Status\n\nv0.2.x — read operations, verified single-send, and **approval-gated calendar\nmutations** (block/open dates, nightly price) under an explicit per-operation\napproval gate with mandatory fresh-load verification (tier MUTATE-CAL). Listing\nedits, accept/decline, and refunds remain intentionally **out of scope** (tier\nMUTATE-RESTRICTED — refuse + escalate).\n\n## License\n\nMIT. No private tokens, paths, or secrets are embedded — example tool names are\nillustrative and must be mapped to your environment.\n\nFile v0.2.1:_meta.json\n\n{\n  \"ownerId\": \"kn77gtjzdkfywnsbs5f349wjxd87hb0x\",\n  \"slug\": \"airbnb-gateway\",\n  \"version\": \"0.2.1\",\n  \"publishedAt\": 1783632040243\n}\n\nFile v0.2.1:references/airbnb-message-state-machine.md\n\n# Reference — Airbnb message send state machine\n\nThis is the authoritative definition of the send discipline. `SKILL.md` carries\nthe operational summary; this file carries the full machine, timings, and edge\ncases. **This logic is universal — do not fork it per deployment.**\n\n## States\n\n| State | Meaning | Terminal? |\n|---|---|---|\n| `drafted` | Reply text composed, thread + dedupe-key recorded. Nothing sent yet. | no |\n| `attempted` | Send endpoint called exactly once and returned. **Delivery NOT proven.** | no |\n| `confirmed` | Outbound message verified visibly present in the live thread. | yes ✅ |\n| `unconfirmed` | Send returned but the message could not be seen after the verify window. | yes ⚠️ |\n| `failed` | The send call itself errored (4xx/5xx/timeout). | yes ❌ |\n\n## Transitions\n\n```\n            approval (if required)              send endpoint\n  drafted ───────────────────────► (ready) ───── exactly once ─────► attempted\n                                                                         │\n                                              re-read same thread        │\n                                          ┌──────────────────────────────┤\n                                          │                              │\n                            visible ◄─────┘                              └─────► call errored\n                               │                                                     │\n                               ▼                                                     ▼\n                          confirmed ✅                                            failed ❌\n                                                                                     │\n                            absent after verify window                               │\n                               │                                                     │\n                               ▼                                                     ▼\n                         unconfirmed ⚠️  ◄──────── (treat like) ──────────── re-read thread\n                               │                                                     │\n                               └──────────► ESCALATE. No automatic resend. ◄─────────┘\n```\n\n## The dedupe-key\n\n`dedupe_key = hash(thread_id + \"\\n\" + normalize(draft_text))`\n\n`normalize` = trim, collapse internal whitespace, lowercase. The key makes \"the\nsame reply to the same thread\" idempotent regardless of retries.\n\n## The ledger (duplicate-prevention backbone)\n\nAn append-only record, ideally persisted (survives restarts). One row per\n`attempted`:\n\n```json\n{\n  \"dedupe_key\": \"…\",\n  \"thread_id\": \"…\",\n  \"attempted_at\": \"ISO-8601\",\n  \"final_state\": \"confirmed | unconfirmed | failed\",\n  \"verified_at\": \"ISO-8601 | null\",\n  \"operator\": \"agent-id | human-id\"\n}\n```\n\nRules:\n- Write the row at `attempted`, **before** verifying. A crash between send and\n  verify must never look like \"never sent.\"\n- Before any new send, check the ledger for the dedupe-key:\n  - `confirmed` recently → STOP, already handled.\n  - `attempted`/`unconfirmed` present → STOP, do NOT resend; escalate.\n- If no persistent ledger exists, keep an in-session ledger and treat a restart\n  as \"state unknown → verify by reading the thread before any send.\"\n\n## Verify window\n\nRe-read the thread at **t+2s, t+5s, t+10s** after `attempted` (Airbnb UI can\nlag). Match by outbound text + a timestamp newer than the attempt. If still\nabsent after the last poll → `unconfirmed`. Do not extend indefinitely; do not\nresend.\n\nWhen the endpoint read is ambiguous (e.g., returns cached/empty), escalate the\n*verification* (not the send) to a DevTools read of the live thread DOM. Reading\nto verify is always allowed; it is never a second send.\n\n## Edge cases\n\n| Situation | Correct handling |\n|---|---|\n| Endpoint `sent:true`, message never appears | `unconfirmed`. Escalate. Never resend. |\n| Send timed out, unknown if it landed | `failed`. Re-read thread. If the text is now present → reclassify `confirmed` (record it). If absent → escalate; do NOT blind-resend. |\n| Two replies queued for one thread | Process serially; each gets its own dedupe-key and full machine. |\n| Guest sends a new message mid-verify | Does not affect the outbound verification; verify your own message only. |\n| Approval denied at the gate | Stay `drafted`. Discard or revise; never send. |\n\nFile v0.2.1:references/airbnb-safety-rules.md\n\n# Reference — Airbnb safety rules\n\nThe portable safety contract. Universal — do not weaken per deployment. The only\ndeployment-specific knob is the **approval policy** (who approves, and whether\napproval is required for sends).\n\n## Operation tiers\n\n| Tier | Definition | Examples | Default gate |\n|---|---|---|---|\n| **READ** | Cannot change anything a guest or the host can see. | check inbox, read thread, lookup reservation, booking summary, inspect calendar, verify a sent message | None — proceed. |\n| **WRITE** | Produces something a guest sees, but bounded & verifiable. | send a guest reply | Approval (if configured) **+ mandatory verify**. |\n| **MUTATE-CAL** | Affects availability or pricing on the live listing; hard to reverse. | change nightly price, block/open calendar dates | **Explicit per-operation operator approval** (APPROVED keyword, one op per approval) **+ mandatory fresh-load verify + reported inverse.** See `calendar-mutation-procedure.md`. |\n| **MUTATE-RESTRICTED** | Affects listing content, booking decisions, or money movement; effectively irreversible. | edit listing, accept/decline a booking, issue a refund, payouts | **Not implemented — refuse + escalate.** (Each would get its own gated workflow before ever being enabled.) |\n\nClassify every operation into exactly one tier *before* acting. If an operation\nspans tiers, split it; never let a READ workflow quietly perform a WRITE.\n\n## Approval gate (WRITE)\n\n- Per-message, never standing. One approval authorizes exactly one send of one\n  draft to one thread.\n- Present to the approver: the target thread id, the last inbound guest message,\n  and the full draft text. Wait for explicit \"go\".\n- Deployment policy decides whether approval is *required*. Safe default:\n  required for all guest-facing sends. If disabled, verification is still\n  mandatory.\n\n## Irreversibility boundary\n\nTreat everything a guest can see, and anything touching money or availability,\nas irreversible in practice. You cannot reliably \"unsend\". This is why:\n- WRITE ops always verify after the fact.\n- MUTATE-CAL ops (calendar/price) run only under explicit per-operation approval\n  and are fresh-load verified after — never attempted optimistically.\n- MUTATE-RESTRICTED ops (listing edits, accept/decline, refunds, payouts) are\n  refused outright rather than attempted.\n\n## Ambiguous state — the default is caution\n\n| Ambiguity | Action |\n|---|---|\n| Can't tell if a send landed | `unconfirmed`. Do NOT resend. Escalate. |\n| Two read paths disagree on a material fact (dates, guest count, price) | Report BOTH, flag the discrepancy, escalate. Do not silently pick one. |\n| Browser identity health unclear | Check the browser health role; if unhealthy, escalate rather than assuming. |\n| Ledger state unknown after restart | Verify by reading the live thread before any send. |\n\n## Escalate to a human when\n\n- A send reaches `unconfirmed` or `failed`.\n- You are asked to perform any MUTATE-RESTRICTED operation, or a MUTATE-CAL\n  operation without the explicit per-operation approval gate satisfied.\n- A MUTATE-CAL verification is ambiguous (`unconfirmed`) — never retry from it.\n- The host browser identity reports unhealthy.\n- Two read paths disagree on a material fact.\n- You would need to send the same message twice for any reason.\n- A required tool role for the requested operation is missing.\n\n## Escalation report shape\n\nWhen escalating, emit enough for a human to act without re-investigating:\n\n```\nESCALATION\n  op:            send_reply\n  thread_id:     <id>\n  reservation:   <id | none>\n  state:         unconfirmed\n  what happened: send endpoint returned sent:true; message not visible after\n                 t+2/5/10s re-reads.\n  draft:         \"<the exact text>\"\n  do NOT:        resend automatically.\n  human action:  open the thread, confirm whether the message is present; if\n                 absent, decide whether to re-send manually.\n```\n\n## Observability\n\nEvery operation — read or write — emits a structured status:\n\n```json\n{\n  \"op\": \"send_reply\",\n  \"tier\": \"WRITE\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"thread_id\": \"…\",\n  \"reservation_id\": \"… | null\",\n  \"state\": \"confirmed | unconfirmed | failed | ok\",\n  \"dedupe_key\": \"… | null\",\n  \"human_action_needed\": false\n}\n```\n\nA human should be able to reconstruct exactly what happened from the report\nstream alone.\n\nFile v0.2.1:references/airbnb-tool-priority.md\n\n# Reference — Airbnb tool priority & role mapping\n\n**This is the one file you customize per deployment.** It maps abstract tool\n*roles* (used everywhere else in the skill) to the concrete tool names in your\nenvironment. Everything else in the package is portable; edit here only.\n\n## Tier order (universal — do not reorder)\n\n1. **Airbnb endpoint** — first-class, structured, auth-aware. Default for every\n   supported operation.\n2. **Agent-browser** — navigate/inspect when no endpoint covers the op or an\n   endpoint is hard-down.\n3. **DevTools** — read-only DOM inspection / UI verification.\n4. **Playwright** — last-resort automation for read-only or human-approved ops.\n\n## Role → tool map (EDIT THIS for your deployment)\n\n> The names below are the reference deployment's tools. Replace with yours.\n> If a role has no tool in your environment, leave it blank — the skill will\n> degrade to the next available tier and escalate if a *required* role is empty.\n\n| Role | Reference deployment tool | Tier |\n|---|---|---|\n| Airbnb: list/read messages | `/tools/airbnb/messages` | 1 |\n| Airbnb: list reservations | `/tools/airbnb/reservations` | 1 |\n| Airbnb: read calendar | `/tools/airbnb/calendar` | 1 |\n| Airbnb: send message | `/tools/airbnb/messages/send` | 1 |\n| Browser: health/identity | `/tools/browser/status` | 2 |\n| Browser: navigate | `/tools/browser/navigate` | 2 |\n| DevTools: list tabs | `/tools/devtools/tabs` | 3 |\n| DevTools: navigate | `/tools/devtools/navigate` | 3 |\n| DevTools: evaluate (read-only) | `/tools/devtools/evaluate` | 3 |\n| Playwright: fallback | (deployment-specific path) | 4 |\n\n## When to drop a tier (decision rules)\n\n- **Stay on tier 1** unless: (a) no tier-1 tool exists for this operation, or\n  (b) a tier-1 call returns a *hard transport failure* (5xx / connection error /\n  timeout) **twice in a row**. A single slow or empty response is not a drop\n  trigger — retry once on tier 1 first.\n- **Before dropping for \"auth\" reasons**, call the browser health/identity role.\n  Airbnb auth is host-owned (provided by the host browser identity). Missing\n  *local* browser/session state is NOT evidence the account is logged out.\n- **DevTools is read-only in this skill.** Use `evaluate` to read/verify DOM,\n  never to click a send button. A send happens only via the tier-1 send role (or\n  an explicitly human-approved browser send).\n- **Playwright only** when tiers 1–3 are all unavailable for the operation AND\n  the operation is read-only or has explicit human approval. Log that you used\n  it and why.\n\n## Graceful degradation matrix\n\n| Operation | tier-1 absent → | also tier-2 absent → | all absent → |\n|---|---|---|---|\n| read inbox / thread | agent-browser read | DevTools DOM read | escalate (required role missing) |\n| read reservations | agent-browser read | DevTools DOM read | escalate |\n| read calendar | agent-browser read | DevTools DOM read | escalate |\n| send reply | human-approved browser send only | — | escalate; do NOT improvise |\n| verify sent | re-read via any read role | DevTools DOM read | mark `unconfirmed`, escalate |\n\nSending is special: there is no silent fallback for a send. If the tier-1 send\nrole is unavailable, the only alternative is an explicitly human-approved\nbrowser send, and verification is still mandatory.\n\nFile v0.2.1:references/calendar-mutation-procedure.md\n\n# Reference — Calendar mutation procedure (multicalendar UI)\n\n> ⚠️ **THIS CHANGES LIVE PRODUCTION INVENTORY.** Every step below alters the real\n> Airbnb listing: blocking a date removes a night from sale; opening one exposes\n> it; a price change is live to every guest immediately. There is no \"preview\"\n> and no reliable undo beyond the stated inverse operation. **Do NOT run any step\n> of this procedure exploratorily, to \"see what happens\", or on your own\n> initiative.** It executes ONLY under the MUTATE-CAL gate in `SKILL.md` →\n> \"Approved calendar mutations (v0.2)\": the operator named the exact date(s)/field\n> and end state, gave explicit per-operation approval (APPROVED), one operation\n> only, with mandatory fresh-load verification and a reported inverse afterward.\n> No approval, or any ambiguity → STOP and escalate; never mutate from an\n> uncertain state.\n\nVerified end-to-end 2026-07-04 on the live listing (block + fresh-load verification).\nFollow these steps EXACTLY; the multicalendar is a virtualized, animated grid and\nimprovised clicking fails silently.\n\nAll calls: ClawBridge browser role (`/tools/browser/*`), header\n`Authorization: Bearer $CLAWBRIDGE_TOKEN`, base `http://host.docker.internal:3201`.\n\n## Context rule — targeted reads, NEVER repeated full snapshots\n\nA full `/browser/snapshot` is tens of thousands of characters. Pulling one\nrepeatedly floods your context and you WILL lose track of the task mid-procedure\n(observed live 2026-07-04: 15+ full snapshots, then a silent stall). Instead:\n- To check one cell's state, use a targeted eval that returns just the labels\n  (write to file, `-d @file`):\n  `{\"script\":\"(() => { const els=[...document.querySelectorAll('*')].filter(e=>e.childElementCount===0&&(e.textContent||'').includes('July 8, 2026')); return els.map(e=>e.textContent); })()\"}`\n- To search a snapshot, filter it server-side:\n  `curl -s -H \"Authorization: Bearer $CLAWBRIDGE_TOKEN\" http://host.docker.internal:3201/tools/browser/snapshot | grep -i -m5 \"blocked\\|Save\\|Selected dates\"`\n- Take a FULL snapshot only when you need fresh element refs for an action, and\n  at most once per step.\n\n## Completion rule — a MUTATE-CAL turn NEVER ends silently\n\nYou must end the turn with a report: before-state, actions taken, verified\nafter-state, screenshot paths — or an explicit `unconfirmed`/failure report.\nNO_REPLY is forbidden in a mutation turn. If you are unsure or stuck, report\nexactly where you stopped and what state the calendar was left in.\n\n## Transport rule — exec + curl ONLY, never the fetch/web_fetch tool\n\nClawBridge calls MUST go through the `exec` tool running `curl`. The built-in\n`fetch`/`web_fetch` tool CANNOT reach ClawBridge: it does not send the\n`Authorization: Bearer` header and internal hosts (`host.docker.internal`) are\nblocked for it (observed live 2026-07-04: `fetch …/tools/browser/snapshot` failed\nand killed the run at the verification step, twice in one day). To search a\nsnapshot for a string, pipe curl instead:\n`curl -s -H \"Authorization: Bearer $CLAWBRIDGE_TOKEN\" http://host.docker.internal:3201/tools/browser/snapshot | grep -i blocked`\n\n## Payload rule — NEVER inline JS into a quoted `-d` argument\n\nThe eval scripts below are full of single quotes. Wrapping them in `curl -d '...'`\nbreaks shell quoting (curl exits 3 \"URL malformed\" — observed live 2026-07-04, seven\nconsecutive failures). Instead, for EVERY `/tools/browser/eval` call:\n\n1. Use the `write` tool to save the JSON body to a file, e.g. `/tmp/eval.json`:\n   `{\"script\":\"(() => { ... })()\"}` — the write tool needs no shell escaping.\n2. Then one clean curl per exec:\n   `curl -s -X POST -H \"Authorization: Bearer $CLAWBRIDGE_TOKEN\" -H \"Content-Type: application/json\" -d @/tmp/eval.json http://host.docker.internal:3201/tools/browser/eval`\n\nNavigate/action/snapshot payloads contain no nested quotes and may stay inline.\n\n## Step 1 — Open the calendar\n\n`POST /tools/browser/navigate` `{\"url\":\"https://www.airbnb.com/multicalendar\"}` then\n`GET /tools/browser/snapshot`. Find the month selector:\n`combobox \"Select a month to view\" [ref=eNN]`.\n\n## Step 2 — Reach the target month (select + poll, re-fire if stalled)\n\n`POST /tools/browser/action` `{\"action\":\"select\",\"args\":[\"eNN\",\"June 2027\"]}`.\nThe grid *animates* through intervening months and loads lazily. Poll the snapshot\nevery ~8s for a gridcell whose label contains your target date (e.g. \"June 16, 2027\").\nIf the visible month stops advancing for 3+ polls, RE-FIRE the same select — each\nfiring advances a few months. Do not proceed until the target date's LISTING-ROW\ncell is present (label like \"<listing name>, June 16, 2027, Select as start date. $NNNN MXN\").\nThat label is your **before-state** — record it.\n\n## Step 3 — Select the date (JS event dispatch, NOT a plain ref click)\n\nPlain `click` on the cell ref reports ✓ but does not register. Use\n`POST /tools/browser/eval` with this script (substitute the date):\n\n```\n(() => { const leaf=[...document.querySelectorAll('*')].find(e=>e.childElementCount===0&&(e.textContent||'').includes('June 16, 2027, Select as')); if(!leaf) return 'NOT FOUND'; const cell=leaf.closest('button,[role=button],[role=gridcell]'); cell.scrollIntoView({block:'center',inline:'center'}); const r=cell.getBoundingClientRect(); const o={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2,view:window}; for(const t of ['pointerover','mouseover','pointerdown','mousedown','pointerup','mouseup','click']){cell.dispatchEvent(new MouseEvent(t,o));} return 'CLICKED '+leaf.textContent.slice(0,60); })()\n```\n\nRun it **twice** (start date, then end date = same cell for a single night). After the\nsecond run the side panel opens: \"Selected dates M/D/YYYY → M/D/YYYY\".\n\n## Step 4 — Set availability and save\n\nSnapshot → find `radio \"Available\"` / `radio \"Blocked\"` and `button \"Save\"`.\n- To block: `{\"action\":\"check\",\"args\":[\"<Blocked-radio-ref>\"]}`\n- To unblock: `{\"action\":\"check\",\"args\":[\"<Available-radio-ref>\"]}`\n\nRe-snapshot: confirm the radio flipped (`checked=true`) AND Save is no longer\n`[disabled]`. Then `{\"action\":\"click\",\"args\":[\"<Save-ref>\"]}`.\n\n## Step 5 — MANDATORY independent verification (fresh load)\n\nNever trust the in-page state. `POST /tools/browser/navigate` to the multicalendar\nagain (fresh load), redo Step 2, and read the target cell's label:\n- Blocked ⇒ label contains \"Unavailable\"\n- Available ⇒ label shows the nightly price again\n\nReport before-state, actions taken, after-state label, and the screenshot paths\n(every action response includes one). If the label is missing or ambiguous, report\n`unconfirmed` — do NOT retry the mutation.\n\n## Known traps (all observed live)\n\n- Cell refs go stale between snapshots — re-snapshot before every ref use.\n- The month select animates; clicking mid-animation hits \"Loading\" skeleton cells.\n- A ✓ Done response does NOT mean the UI registered the interaction — only the\n  side panel opening / radio flipping / fresh-load label proves anything.\n- Success claims without the Step-5 fresh-load check are the #1 failure mode.\n- Inlining eval JS into `curl -d '...'` mangles shell quoting — always use the\n  write-file + `-d @file` pattern (see Payload rule above).\n- Using the built-in `fetch`/`web_fetch` tool for ClawBridge endpoints always\n  fails (no auth header, internal host blocked) — exec + curl only (see\n  Transport rule above).\n\nFile v0.2.1:references/future-adapter-interface.md\n\n# Reference — future adapter interface\n\nThis skill is the **behavioral contract**. A later, more formal adapter/tool\nlayer should harden the **mechanical guarantees** so correctness doesn't depend\non a model remembering a markdown rule. This file describes the adapter the\nskill is designed to pair with, and how the skill should call it if/when it\nexists.\n\n## Design principle\n\n> Judgment stays in the skill; guarantees move to the adapter.\n\n- **Skill (prompt layer):** when to escalate, how to summarize guest intent,\n  whether a state is ambiguous — model reasoning.\n- **Adapter (code layer):** send-exactly-once, the dedupe ledger, the verify\n  poll, tier fallback — properties you want *enforced*, not *hoped for*.\n\n## Ideal adapter functions\n\nThe skill should prefer these when present, and fall back to its manual\nprocedures when they are not.\n\n### `airbnb.sendVerified(thread_id, text, dedupe_key) → { state, visible_at, error? }`\nAtomically: dedupe-check → send-once → write ledger → verify-poll. Returns a\nfinal state from the same vocabulary (`confirmed | unconfirmed | failed`). This\nis the single most valuable function — it makes the cardinal \"no duplicate\nsends\" rule a code-level guarantee.\n\nWhen present: `send_reply` delegates entirely and just reports `state`.\nWhen absent: `send_reply` runs the manual state machine from\n`airbnb-message-state-machine.md`.\n\n### `airbnb.dedupeCheck(dedupe_key) → { already_sent, last_state, last_at }`\nLets the skill short-circuit before drafting if a reply already went out.\n\n### Read functions\n- `airbnb.listInbox(filter?) → Thread[]`\n- `airbnb.readThread(thread_id) → { messages[], last_inbound, ... }`\n- `airbnb.listReservations(filter?) → Reservation[]`\n- `airbnb.readCalendar(range) → CalendarSpan[]`\n\nThese mirror the read verbs and let the skill avoid tier-selection logic when\nthe adapter already owns it.\n\n### Future MUTATE adapter functions (each gated)\nThese harden today's manual procedures into atomic adapter calls; the gate and\nverification requirements do not change when they arrive.\n- `airbnb.setCalendar(range, state, dedupe_key)` — block/open dates. **Available\n  today as the manual MUTATE-CAL procedure** (`calendar-mutation-procedure.md`)\n  under the per-operation approval gate; this is its future adapter form.\n- `airbnb.setPrice(range, amount, dedupe_key)` — pricing. Same MUTATE-CAL gate.\n- `airbnb.respondToBooking(reservation_id, decision, dedupe_key)` — accept/decline.\n  Still **MUTATE-RESTRICTED (not implemented)**; would require its own gated\n  workflow before enabling.\n\nEach MUTATE adapter function should follow the same shape as `sendVerified`:\ndo-once + ledger + verify, returning a final state. The skill wraps each in a\nread → propose → approve → call → report flow.\n\n## Capability detection\n\nThe skill should treat adapter functions as **optional**: detect availability,\nprefer them, and degrade to manual procedures otherwise. Pseudocode:\n\n```\nif has(airbnb.sendVerified):\n    result = airbnb.sendVerified(thread_id, text, dedupe_key)\n    report(result.state)\nelse:\n    run_manual_send_state_machine()   # per airbnb-message-state-machine.md\n```\n\n## Why both layers (defense in depth)\n\n- The **adapter** prevents a duplicate send even if an agent misbehaves.\n- The **skill** explains *why* and handles the judgment the adapter can't encode\n  (intent, tone, when to escalate).\n\nShipping the skill first is correct: it makes the contract usable today. The\nadapter promotes the riskiest guarantees into code as the deployment matures.\n\nFile v0.2.1:CHANGELOG.md\n\n# Changelog\n\nAll notable changes to the `airbnb-gateway` skill package. Format follows\n[Keep a Changelog](https://keepachangelog.com/); versions follow SemVer.\n\n## [Unreleased]\n\n### Fixed\n- v0.2.1: **Doc-consistency reconciliation (resolves NVIDIA SkillSpector\n  findings).** The v0.2.0 approval-gated calendar-mutation capability had left\n  stale v1 \"calendar is read-only / mutation reserved for v2 / refuse + escalate\"\n  language in ~6 places, which the scanner flagged as description-behavior\n  mismatch (×2), intent-code divergence (High), and a missing production-impact\n  warning. Propagated the MUTATE-CAL (gated, permitted) vs MUTATE-RESTRICTED\n  (refused) split everywhere: SKILL.md (frontmatter `description` now names the\n  gated calendar-mutation capability; Law #5; renamed \"Reservation & calendar\n  (READ-only in v1)\" → \"Reservation & calendar reads\" with a corrected scope\n  paragraph), `references/airbnb-safety-rules.md` (split the MUTATE tier row +\n  escalation triggers), `README.md` status, `examples/calendar-inspection.md`,\n  and `references/future-adapter-interface.md`. Added a prominent\n  **\"⚠️ THIS CHANGES LIVE PRODUCTION INVENTORY\"** warning to\n  `references/calendar-mutation-procedure.md`. No behavior change — the gated\n  write scope was already the intent; the docs now state it consistently.\n- v0.2.1: `references/calendar-mutation-procedure.md` — added a\n  **Context rule** (targeted evals / server-side grep instead of repeated full\n  snapshots; a live run pulled 15+ full snapshots and stalled silently mid-task)\n  and a **Completion rule** (a MUTATE-CAL turn must end with a report or an\n  explicit unconfirmed/failure statement; NO_REPLY forbidden).\n- v0.2.1: `references/calendar-mutation-procedure.md` — added a\n  **Transport rule**: ClawBridge calls go through exec + curl ONLY; the built-in\n  `fetch`/`web_fetch` tool can't send the Authorization header and internal\n  hosts are blocked for it — it killed two live runs on 2026-07-04 at the\n  verification step (agents drift to it under pressure; now explicitly\n  forbidden, with a curl-pipe-grep pattern for snapshot searches).\n- v0.2.1: `references/calendar-mutation-procedure.md` — added a\n  **Payload rule**: never inline eval JS into a quoted `curl -d '...'` argument;\n  write the JSON body to a file with the `write` tool and send `-d @file`.\n  Root cause of the 2026-07-04 failed round-trip test: the eval scripts' single\n  quotes broke shell quoting and curl exited 3 (\"URL malformed\") seven times in\n  a row — the requests never reached ClawBridge. Also listed under Known traps.\n\n### Changed\n- v0.2.0: **Approval-gated calendar mutations.** Split the MUTATE tier into\n  MUTATE-CAL (block/open dates, nightly price — permitted with explicit\n  per-operation operator approval: APPROVED keyword, one op per approval,\n  mandatory fresh-load verification after, reported inverse operation) and\n  MUTATE-RESTRICTED (listing edits, accept/decline, refunds — still refuse +\n  escalate). Added `references/calendar-mutation-procedure.md`, the live-verified\n  step-by-step multicalendar procedure (animation-safe month navigation,\n  JS event-dispatch date selection, availability radio + save, fresh-load\n  verification), and `SKILL_CARD.md` (NVIDIA skill-card trust format with\n  release evidence from the 2026-07-04 live round-trip test).\n- v0.1.4: Added a friendly \"star this skill\" call-to-action to `SKILL.md`,\n  `README.md`, and `CLAWHUB.md` (shown on the ClawHub listing) — content-only,\n  no behavior change.\n- v0.1.3: Clean re-cut for review handoff — no content or doctrine change since\n  v0.1.2; published to give the reviewing agent a fresh version number to pin\n  its install/audit against.\n- v0.1.2: Pre-install polish pass (no doctrine change). Replaced the\n  \"Capabilities this skill expects\" section with an explicit **Minimum\n  Environment Contract** (read-only minimum vs send-capable minimum vs optional\n  enhancements) so outside users can tell at a glance whether the skill can run.\n  Converted the **Command surface** table to a renderer-safe bullet list (the\n  `<id>`/`<thread>` placeholders rendered as collapsed on some markdown\n  platforms). Verified package completeness: all `references/` and `examples/`\n  files ship in the published artifact — the ClawHub web page previews only\n  `SKILL.md`, but installs are complete.\n- v0.1.1: Rewrote the `SKILL.md` frontmatter `description` to match the ClawHub\n  listing blurb (`CLAWHUB.md`), so the published summary reads in the intended\n  voice instead of the long internal one-liner.\n\n### Added\n- Initial `airbnb-gateway` skill package (v0.1.0):\n  - `SKILL.md` — operating contract: Five Laws, tier-based operating model,\n    safety tiers, send state machine, command surface, anti-patterns,\n    future-adapter section, maintainer notes.\n  - `references/airbnb-message-state-machine.md` — full send state machine,\n    dedupe key, ledger contract, verify window, edge cases.\n  - `references/airbnb-tool-priority.md` — the one per-deployment file: tier\n    order + role→tool map + degradation matrix.\n  - `references/airbnb-safety-rules.md` — READ/WRITE/MUTATE tiers, approval gate,\n    ambiguity handling, escalation report shape, observability.\n  - `references/future-adapter-interface.md` — ideal adapter functions and how\n    the skill should call them when present.\n  - `examples/` — check-inbox, read-thread, send-reply-with-verification\n    (incl. the no-resend `unconfirmed` path), reservation-lookup,\n    calendar-inspection.\n  - `state/send-log.schema.json` — append-only dedupe ledger schema.\n  - `README.md`, `LICENSE` (MIT) — Open Hub-friendly packaging.\n  - `CLAWHUB.md` — verbatim ClawHub/Open Hub listing description.\n\n### Notes\n- Current scope (as of v0.2.x): read operations, verified single-send, and\n  approval-gated **MUTATE-CAL** calendar mutations (block/open dates, nightly\n  price — explicit per-operation operator approval + mandatory fresh-load\n  verification + reported inverse). **MUTATE-RESTRICTED** operations (listing\n  edits, accept/decline bookings, refunds, payouts) remain intentionally refused\n  (escalate). (v0.1.0 originally shipped read + single-send only; calendar\n  mutation was added under its gate in v0.2.0.)\n\nFile v0.2.1:CLAWHUB.md\n\n# airbnb-gateway\n\n`airbnb-gateway` is a skill for safe, coherent Airbnb operations in OpenClaw-style agent environments.\n\nIt standardizes how agents:\n- check inbox threads\n- inspect reservations and booking state\n- review calendar context\n- draft guest replies\n- send messages safely\n- verify whether a send actually appeared in the live Airbnb thread\n- make operator-approved calendar changes (block/open dates, nightly price) — one\n  operation per explicit approval, always fresh-load verified after (listing\n  edits, accept/decline, and refunds remain out of scope)\n\nThe skill teaches a strict operating model:\n- prefer Airbnb-native endpoints before generic browser automation\n- treat send acknowledgments as `attempted`, not automatically `confirmed`\n- verify outbound messages in the live thread UI before declaring success\n- never auto-resend from ambiguous or unconfirmed state\n\n`airbnb-gateway` is designed to reduce duplicate messages, normalize agent behavior, and provide a safer foundation for Airbnb messaging, bookings, and calendar workflows.\n\nIt works well as:\n- a standalone operational skill today\n- a future companion to a more formal Airbnb adapter/tool layer tomorrow\n\n---\n\n⭐ **Find this useful?** If `airbnb-gateway` saves you time, please **star it** (the ⭐ at the top of this ClawHub page) — stars help other rental operators discover it and keep it maintained. Thank you!\n\nFile v0.2.1:examples/calendar-inspection.md\n\n# Example — `inspect_calendar`\n\nA READ operation. Report calendar state and **flag** high-risk patterns.\n`inspect_calendar` itself never changes the calendar — it is read-only, flag only.\n*Changing* the calendar (block/open dates, price) is a separate, approval-gated\nMUTATE-CAL operation (see SKILL.md → \"Approved calendar mutations\"); it is never a\nside effect of an inspection.\n\n## Procedure\n1. Tier 1: call the Airbnb calendar role for the requested range (default: next\n   60 days).\n2. Classify each day/span: `booked`, `blocked`, `open`.\n3. Run risk checks (flag, don't fix):\n   - **Double-booking:** two reservations overlapping the same night.\n   - **Anomalous price:** a night priced far outside the range's norm.\n   - **Risky back-to-back:** check-out and next check-in same day with no buffer\n     for turnover.\n   - **Unexpected open gap:** open nights between two bookings that usually fill.\n4. Emit state + a `flags` array. Recommend nothing irreversible.\n\n## Reference-deployment call\n```\nGET /tools/airbnb/calendar?from=2026-06-03&to=2026-08-02     # tier 1\n```\n\n## Sample output\n```json\n{\n  \"op\": \"inspect_calendar\",\n  \"tier\": \"READ\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"range\": { \"from\": \"2026-06-03\", \"to\": \"2026-08-02\" },\n  \"spans\": [\n    { \"from\":\"2026-06-06\", \"to\":\"2026-06-09\", \"state\":\"booked\", \"reservation_id\":\"r_5521\" },\n    { \"from\":\"2026-06-09\", \"to\":\"2026-06-11\", \"state\":\"open\" },\n    { \"from\":\"2026-06-11\", \"to\":\"2026-06-14\", \"state\":\"booked\", \"reservation_id\":\"r_5540\" }\n  ],\n  \"flags\": [\n    { \"type\":\"back_to_back\", \"date\":\"2026-06-09\",\n      \"detail\":\"r_5521 check-out and r_5540-adjacent open night; verify turnover time\" }\n  ],\n  \"human_action_needed\": false\n}\n```\n\n## When a flag is serious\nIf a flag implies guest-visible risk (e.g., an actual double-booking), escalate\nto a human immediately with the specifics. Do NOT attempt to resolve it by\nchanging the calendar during an inspection — that is a separate MUTATE-CAL\noperation that requires its own explicit per-operation operator approval.\n\n## Degradation\n- Tier-1 calendar role missing/hard-down twice → agent-browser read of the\n  calendar page → DevTools DOM read → escalate.\n\n## Anti-patterns\n- ❌ \"Fixing\" a double-booking by blocking dates *during an inspection*.\n  `inspect_calendar` only flags + escalates; blocking is a separate approval-gated\n  MUTATE-CAL op, never an automatic response to a flag.\n- ❌ Reporting calendar state from a stale cache without noting it; if the read\n  path is ambiguous, say so.\n\nFile v0.2.1:examples/check-inbox.md\n\n# Example — `check_inbox`\n\nA READ operation. No approval, no write. Goal: surface threads needing attention,\nprioritized, without changing anything.\n\n> Tool names below are from the reference deployment. Map them via\n> `references/airbnb-tool-priority.md`.\n\n## Procedure\n1. Tier 1: call the Airbnb messages-list role.\n2. For each thread, note: `thread_id`, guest name, last-message direction\n   (inbound/outbound), last-message time, unread flag, linked reservation if\n   available.\n3. Prioritize: unread inbound > inbound awaiting reply > everything else.\n   Within a bucket, sort by most recent.\n4. Emit a structured summary. Do not open/draft/send anything.\n\n## Reference-deployment call\n\n```\nGET /tools/airbnb/messages          # tier 1\n```\n\n## Sample output\n\n```json\n{\n  \"op\": \"check_inbox\",\n  \"tier\": \"READ\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"threads\": [\n    { \"thread_id\": \"t_88213\", \"guest\": \"Marco R.\", \"last\": \"inbound\",\n      \"at\": \"2026-06-03T14:22:00Z\", \"unread\": true, \"reservation_id\": \"r_5521\",\n      \"snippet\": \"Hi! Is early check-in possible on Friday?\" },\n    { \"thread_id\": \"t_88190\", \"guest\": \"Dana P.\", \"last\": \"outbound\",\n      \"at\": \"2026-06-03T09:10:00Z\", \"unread\": false, \"reservation_id\": \"r_5498\",\n      \"snippet\": \"Thanks, enjoy your stay!\" }\n  ],\n  \"needs_attention\": [\"t_88213\"],\n  \"human_action_needed\": false\n}\n```\n\n## Degradation\n- Tier-1 list role missing or hard-down twice → agent-browser navigate to the\n  inbox and read the rendered list (still READ). If that's also unavailable →\n  DevTools DOM read. If all absent → escalate (required read role missing).\n\n## Anti-patterns\n- ❌ Auto-drafting replies during an inbox check. `check_inbox` is read-only;\n  drafting is a separate, explicit step.\n\nArchive v0.2.0: 19 files, 32025 bytes\n\nFiles: CHANGELOG.md (3470b), CLAWHUB.md (1187b), examples/calendar-inspection.md (2145b), examples/check-inbox.md (1745b), examples/read-thread.md (1678b), examples/reservation-lookup.md (2158b), examples/send-reply-with-verification.md (2886b), LICENSE (1070b), README.md (3164b), references/airbnb-message-state-machine.md (4677b), references/airbnb-safety-rules.md (3607b), references/airbnb-tool-priority.md (3317b), references/calendar-mutation-procedure.md (3628b), references/future-adapter-interface.md (3134b), SKILL_CARD.md (4864b), skill-card.md (3053b), SKILL.md (14356b), state/send-log.schema.json (1642b), _meta.json (133b)\n\nFile v0.2.0:SKILL.md\n\n---\nname: airbnb-gateway\ndescription: >\n  A skill for safe, coherent Airbnb operations in OpenClaw-style agent\n  environments. It standardizes how agents check inbox threads, inspect\n  reservations and booking state, review calendar context, draft guest replies,\n  send messages safely, and verify whether a send actually appeared in the live\n  Airbnb thread. It teaches a strict operating model: prefer Airbnb-native\n  endpoints before generic browser automation; treat send acknowledgments as\n  attempted, not automatically confirmed; verify outbound messages in the live\n  thread UI before declaring success; and never auto-resend from ambiguous or\n  unconfirmed state. Designed to reduce duplicate messages, normalize agent\n  behavior, and provide a safer foundation for Airbnb messaging, bookings, and\n  calendar workflows — useful standalone today and as a companion to a more\n  formal Airbnb adapter/tool layer tomorrow.\nversion: 0.2.0\nrisk: write-gated\ntags: [airbnb, vacation-rental, messaging, operations, openclaw]\nlicense: MIT\nhomepage: \"\"\nmaintainer: \"\"\n---\n\n# airbnb-gateway\n\n> ⭐ **Find this skill useful?** If `airbnb-gateway` saves you time, please\n> **star it** (the ⭐ at the top of this ClawHub page) — stars help other rental\n> operators discover it and keep it maintained. Thank you!\n\nYou are operating a live, revenue-bearing Airbnb account on behalf of a real\nhost. Guests are real people. A duplicate message, a wrong send, or a careless\ncalendar change has real consequences. This skill exists so that **every agent\nhandles Airbnb identically and safely**, instead of improvising tool calls per\nturn.\n\n> **Portability note.** This skill is environment-agnostic. It refers to tool\n> *roles* (an \"Airbnb messages endpoint\", an \"agent-browser\", a \"DevTools\n> bridge\", a \"Playwright fallback\") rather than hard-coded URLs. Your concrete\n> tool names live in `references/airbnb-tool-priority.md` — edit that one file\n> to map roles to the actual tools in your deployment. Everything else here is\n> universal.\n\n---\n\n## THE FIVE LAWS (non-negotiable)\n\n1. **Platform-first.** Always try the first-class Airbnb endpoint for an\n   operation before any browser automation. Browser weirdness is NOT evidence\n   that Airbnb access is down.\n2. **Read before write.** Never send a reply without first reading the live\n   thread in the *same* operation.\n3. **`sent: true` is `attempted`, not `confirmed`.** An endpoint success proves\n   the call returned — not that the guest can see the message. You MUST re-read\n   the thread and SEE the outbound message before marking `confirmed`.\n4. **Never auto-resend an `unconfirmed` send.** Duplicate guest messages are\n   worse than a late reply. Escalate to a human instead.\n5. **Writes gate; mutations stop.** Sending a message is approval-gated.\n   Pricing, availability, listing edits, and accept/decline are NOT implemented\n   in v1 — refuse and escalate.\n\nIf you are ever unsure, STOP and report. A paused agent is recoverable; a\nduplicated or wrong guest message is not.\n\n---\n\n## Minimum Environment Contract\n\nUse this to decide, before installing, whether your deployment can run the skill\nand at what level. Map each role to a real tool in\n`references/airbnb-tool-priority.md`. The skill degrades gracefully: if a\n**read-only** requirement is met but a **send** requirement is not, the skill\nruns in read-only mode and refuses sends rather than improvising.\n\n**Read-only mode — minimum to run safely (inbox / threads / reservations / calendar):**\n- At least one **Airbnb read path** — list/read messages, reservations, and\n  calendar (first-class endpoint preferred; agent-browser or DevTools read\n  acceptable as fallback).\n\nThat's the entire requirement for every READ verb. Nothing can be sent in this\nmode.\n\n**Send-capable mode — additional minimum to send a guest reply:**\n- An **Airbnb send path** — the first-class send endpoint (preferred) OR an\n  explicitly human-approved browser send.\n- A **thread re-read path** — to verify the outbound message after sending.\n  (The same read path from read-only mode satisfies this.)\n\nIf either is missing, do NOT send: stay in read-only mode and escalate.\n\n**Optional enhancements — improve safety / fallback depth when present:**\n- **Agent-browser** — navigate/inspect when an endpoint is missing or hard-down.\n- **DevTools bridge** — read-only DOM inspection to verify UI state.\n- **Playwright fallback** — last-resort automation for read-only ops.\n- **Persistent send ledger** — survives restarts; hardens duplicate prevention.\n- **Approval channel** — human/approver sign-off before a send.\n\nAbsent optional capabilities reduce fallback depth but never change the safety\nrules. A required capability that is absent for a given operation triggers an\nescalation, never an improvised workaround.\n\n---\n\n## Operating model — which tool path do I use?\n\nResolve top-down. Drop to the next tier ONLY when the current tier is genuinely\nunavailable, not merely \"looked weird once.\" Full detail in\n`references/airbnb-tool-priority.md`.\n\n```\nOPERATION\n  │\n  ├─ 1. AIRBNB ENDPOINT  (first-class, structured, auth-aware)\n  │       → DEFAULT for every supported operation. Always start here.\n  │\n  ├─ 2. AGENT-BROWSER  (navigate / status)\n  │       → ONLY when no endpoint covers the op, OR an endpoint returned a hard\n  │         transport failure (5xx/timeout) TWICE. First confirm the host\n  │         browser identity is alive before assuming auth loss.\n  │\n  ├─ 3. DEVTOOLS  (tabs / navigate / evaluate — READ-ONLY here)\n  │       → DOM inspection / UI verification when the endpoint read is\n  │         ambiguous. Never use evaluate to perform a write/click-send.\n  │\n  └─ 4. PLAYWRIGHT  (fallback)\n          → LAST RESORT. Only when 1–3 are unavailable AND the op is read-only\n            or an explicitly human-approved write.\n```\n\n---\n\n## Safety model\n\nClassify every operation before acting. Full table in\n`references/airbnb-safety-rules.md`.\n\n| Tier | Examples | Gate |\n|---|---|---|\n| **READ** | check inbox, read thread, lookup reservation, inspect calendar | None. Proceed. |\n| **WRITE** | send a guest reply | Approval if configured; ALWAYS verify after. |\n| **MUTATE-CAL** | change nightly price, block/open calendar dates | Explicit operator approval per operation (see \"Approved calendar mutations\"). ALWAYS verify after. |\n| **MUTATE-RESTRICTED** | edit listing content, accept/decline bookings, refunds, payouts | **NOT in this version.** Refuse + escalate. |\n\n**Escalate to a human when:** a send reaches `unconfirmed` or `failed`; you're\nasked to perform any MUTATE-RESTRICTED op, or a MUTATE-CAL op without the approval gate satisfied; the host browser identity reports unhealthy; two\nread paths disagree on a material fact (dates, guest count, price); or you would\nneed to send the same message twice for any reason.\n\n---\n\n## Message send — the state machine (the core procedure)\n\nStates: `drafted → attempted → (confirmed | unconfirmed | failed)`. Full machine\nwith timings in `references/airbnb-message-state-machine.md`.\n\n```\n[drafted]      reply composed; thread + dedupe-key recorded\n   │           (WRITE gate: get approval if required)\n   ▼\n[attempted]    ← send endpoint called EXACTLY ONCE; ledger written NOW\n   │             endpoint sent:true  →  ATTEMPTED, not done\n   │\n   ├─ verify: re-read the SAME thread (endpoint; DevTools if ambiguous)\n   │\n   ├─ outbound visibly present?  ── yes ──► [confirmed]   ✅ report\n   ├─ absent after verify window ── no  ──► [unconfirmed] ⚠️ DO NOT RESEND, escalate\n   └─ send call errored                  ► [failed]       ❌ re-read first, escalate, no blind resend\n```\n\n### Send procedure (follow exactly)\n1. **Read the live thread.** Capture the last inbound message + a dedupe-key\n   (`thread_id` + normalized hash of the draft text).\n2. **Check the send ledger.** If a `confirmed` (or recent `attempted`) send with\n   the same dedupe-key exists → STOP, already handled.\n3. **Draft** the reply → state `drafted`.\n4. **Approval gate** if required — present draft + thread context, wait for \"go\".\n5. **Send exactly once** via the first-class send endpoint → state `attempted`.\n   Write the ledger entry *before* verifying, so a crash mid-verify can't cause\n   a blind resend.\n6. **Verify** — re-read the thread, look for the outbound text/timestamp.\n7. **Confirm or not** — visible → `confirmed`; absent within window →\n   `unconfirmed`.\n8. **Report** final status: thread id, state, and the human action needed.\n\nThe duplicate-prevention contract: exactly ONE call to the send endpoint per\n`drafted` item, ever. `unconfirmed`/`failed` NEVER auto-transition to a new send.\nOnly a human, after reading the live thread, may authorize a retry.\n\n---\n\n\n## Approved calendar mutations (v0.2)\n\nCalendar blocks/opens and nightly-price changes are permitted ONLY when ALL of the following hold:\n\n1. **The operator (host) explicitly requested this specific operation** in the current conversation — naming the exact date(s) or field and the desired end state.\n2. **The request carries the word APPROVED** (or the operator replies APPROVE to your restatement of the operation). No approval word, no mutation — ask for it.\n3. **One operation per approval.** Never batch beyond exactly what was named. Never generalize (\"block June 16\" does not mean \"block that week\").\n4. **Execute via the preferred tool order** (native endpoint first, then agent-browser / DevTools per the tool-priority reference). For calendar mutations specifically, follow `references/calendar-mutation-procedure.md` step by step — it is the verified procedure; do not improvise.\n5. **Verify after, independently**: re-read the calendar (fresh page state) and report before-state, action taken, after-state. If verification is ambiguous, report `unconfirmed` and STOP — never retry a mutation from ambiguous state.\n6. **Report reversibility**: state the exact inverse operation so the operator can undo with one instruction.\n\nEverything in MUTATE-RESTRICTED stays forbidden regardless of approval wording — escalate to the host instead.\n\n## Reservation & calendar (READ-only in v1)\n\n- **`booking_summary`** — list reservations sorted by check-in: guest, dates,\n  status, `reservation_id`, linked `thread_id`.\n- **`lookup_reservation`** — by id or guest; map reservation ↔ thread so a reply\n  ties to the right booking.\n- **`inspect_calendar`** — report blocked/open/booked spans; FLAG (don't fix)\n  high-risk states (double-booking, anomalous price, risky back-to-back gaps).\n\nCalendar mutation, pricing, accept/decline, and listing edits are reserved for\nv2 and get their own gated workflows mirroring the send machine\n(read → propose → approve → do-once → verify → report). Until then: refuse +\nescalate.\n\n---\n\n## Command surface\n\nAgents speak only in these verbs; each maps to a canonical procedure. Format is\n`verb (TIER) — description`.\n\n**READ verbs (no gate):**\n- `check_inbox` **(READ)** — list threads needing attention, prioritized.\n- `read_thread <id>` **(READ)** — full live thread + guest-intent summary.\n- `lookup_reservation <id-or-guest>` **(READ)** — reservation details + linked thread.\n- `booking_summary` **(READ)** — upcoming bookings, sorted by check-in.\n- `inspect_calendar [range]` **(READ)** — calendar state + flagged risks.\n- `verify_sent <thread> <draft>` **(READ)** — re-check a thread for an outbound message.\n\n**WRITE verbs (gated + verified):**\n- `draft_reply <thread> <intent>` **(WRITE-pre)** — produce a reviewable draft; result state `drafted`.\n- `send_reply <thread> <draft>` **(WRITE)** — run the full send state machine.\n\n**Status:**\n- `report_status` — emit structured status of the last operation.\n\n`send_reply` is the ONLY write, and it internally enforces\nread → draft → approve → send-once → verify → report. Agents must not decompose\nit into lower-level steps to skip verification.\n\n---\n\n## Future adapter compatibility\n\nThis skill is the *behavioral contract*. A future formal adapter/tool layer\nshould harden the *mechanical guarantees* so correctness doesn't depend on a\nmodel remembering a markdown rule. See `references/future-adapter-interface.md`.\n\nIdeal future adapter functions (call them if present, fall back to the manual\nprocedure if not):\n\n- `airbnb.sendVerified(thread_id, text, dedupe_key)` → returns\n  `{state, visible_at}` after doing send-once + ledger + verify atomically.\n- `airbnb.dedupeCheck(dedupe_key)` → `{already_sent: bool, last_state}`.\n- `airbnb.readThread(thread_id)` / `airbnb.listInbox()` /\n  `airbnb.listReservations()` / `airbnb.readCalendar(range)`.\n\nWhen `sendVerified` exists, `send_reply` delegates to it and simply reports the\nreturned state. When it doesn't, `send_reply` runs the manual procedure above.\n\n---\n\n## Anti-patterns (do NOT do these)\n\n- ❌ **Jumping to Playwright early.** Endpoints exist and are preferred; falling\n  to Playwright because something felt slow once is wrong.\n- ❌ **Treating `sent: true` as confirmed.** It's `attempted`. Always verify.\n- ❌ **Resending from `unconfirmed`.** This creates duplicate guest messages.\n  Escalate instead.\n- ❌ **\"Auth must be dead.\"** Missing local browser/session state is NOT proof —\n  auth is host-owned. Check browser status + an endpoint first.\n- ❌ **Mixing read-safe and write-risk behavior.** Classify every op; never let\n  a read workflow quietly perform a write.\n- ❌ **Improvising the send flow per turn.** There is exactly ONE send\n  procedure. Use it.\n\n---\n\n## Maintainer notes\n\n- **Customize per deployment:** `references/airbnb-tool-priority.md` (map roles\n  to real tool names), the approval-gate policy, and whether a persistent ledger\n  is wired. Examples under `examples/` are illustrative — adapt payloads to your\n  tools.\n- **Keep universal:** the Five Laws, the send state machine, the safety tiers,\n  and the command vocabulary. These are the portable core; don't fork them per\n  deployment.\n- **Evolving the skill:** add new operations by giving each its own gated\n  workflow in the same shape as the send machine. Never add a generic \"do\n  anything\" write. Bump `version`; record changes in a CHANGELOG if published.\n\nFile v0.2.0:README.md\n\n# airbnb-gateway\n\nA reusable OpenClaw / Codex-style **skill package** for safe, coherent,\nend-to-end Airbnb host operations: inbox checks, thread reading, reservation\nlookup, booking summaries, calendar inspection, draft replies, **verified**\nmessage sending, and disciplined escalation.\n\n> ⭐ **Find this useful?** If `airbnb-gateway` saves you time, please **star it on ClawHub** — stars help other operators discover it and keep it maintained. Thank you!\n\nIt does not add transport. It orchestrates whatever Airbnb tooling your\nenvironment already has — first-class Airbnb endpoints, agent-browser, DevTools,\nPlaywright — behind one consistent operating model so multiple agents behave\nidentically and never duplicate a guest message.\n\n## Why this exists\n\nTwo reliability lessons are baked into the design:\n\n1. **Browser weirdness ≠ Airbnb down.** Auth is host-owned; prefer platform-aware\n   endpoints before generic browser automation.\n2. **`sent: true` ≠ delivered.** A send is only `confirmed` after re-reading the\n   live thread and *seeing* the message. Endpoint success is just `attempted`,\n   and an `unconfirmed` send is **never** auto-resent.\n\n## Install / use\n\n1. Drop `skills/airbnb-gateway/` into your skills library.\n2. Edit **`references/airbnb-tool-priority.md`** — map the abstract tool roles to\n   the real tool names in your deployment. This is the only required\n   customization.\n3. (Optional) Set your approval policy and wire a persistent send ledger.\n4. Point your agents at the skill. They should speak only in the command verbs\n   (`check_inbox`, `read_thread`, `send_reply`, …) and never call low-level\n   Airbnb tools directly.\n\n## What's portable vs. deployment-specific\n\n| Portable (don't fork) | Deployment-specific (customize) |\n|---|---|\n| The Five Laws | role → tool name map |\n| Send state machine | approval policy |\n| Safety tiers (READ/WRITE/MUTATE) | persistent ledger wiring |\n| Command vocabulary | example payload shapes |\n\n## Layout\n\n```\nairbnb-gateway/\n├── SKILL.md                              # the operating contract (start here)\n├── README.md                             # this file\n├── CHANGELOG.md\n├── LICENSE\n├── references/\n│   ├── airbnb-tool-priority.md           # ← customize per deployment\n│   ├── airbnb-message-state-machine.md   # universal\n│   ├── airbnb-safety-rules.md            # universal\n│   └── future-adapter-interface.md       # how to pair with a code adapter later\n├── examples/\n│   ├── check-inbox.md\n│   ├── read-thread.md\n│   ├── send-reply-with-verification.md   # the critical path\n│   ├── reservation-lookup.md\n│   └── calendar-inspection.md\n└── state/\n    └── send-log.schema.json              # append-only dedupe ledger schema\n```\n\n## Status\n\nv0.1.0 — read operations + verified single-send. Calendar/pricing/listing\nmutations are intentionally **out of scope** until v2 (refuse + escalate).\n\n## License\n\nMIT. No private tokens, paths, or secrets are embedded — example tool names are\nillustrative and must be mapped to your environment.\n\nFile v0.2.0:_meta.json\n\n{\n  \"ownerId\": \"kn77gtjzdkfywnsbs5f349wjxd87hb0x\",\n  \"slug\": \"airbnb-gateway\",\n  \"version\": \"0.2.0\",\n  \"publishedAt\": 1783200580368\n}\n\nFile v0.2.0:references/airbnb-message-state-machine.md\n\n# Reference — Airbnb message send state machine\n\nThis is the authoritative definition of the send discipline. `SKILL.md` carries\nthe operational summary; this file carries the full machine, timings, and edge\ncases. **This logic is universal — do not fork it per deployment.**\n\n## States\n\n| State | Meaning | Terminal? |\n|---|---|---|\n| `drafted` | Reply text composed, thread + dedupe-key recorded. Nothing sent yet. | no |\n| `attempted` | Send endpoint called exactly once and returned. **Delivery NOT proven.** | no |\n| `confirmed` | Outbound message verified visibly present in the live thread. | yes ✅ |\n| `unconfirmed` | Send returned but the message could not be seen after the verify window. | yes ⚠️ |\n| `failed` | The send call itself errored (4xx/5xx/timeout). | yes ❌ |\n\n## Transitions\n\n```\n            approval (if required)              send endpoint\n  drafted ───────────────────────► (ready) ───── exactly once ─────► attempted\n                                                                         │\n                                              re-read same thread        │\n                                          ┌──────────────────────────────┤\n                                          │                              │\n                            visible ◄─────┘                              └─────► call errored\n                               │                                                     │\n                               ▼                                                     ▼\n                          confirmed ✅                                            failed ❌\n                                                                                     │\n                            absent after verify window                               │\n                               │                                                     │\n                               ▼                                                     ▼\n                         unconfirmed ⚠️  ◄──────── (treat like) ──────────── re-read thread\n                               │                                                     │\n                               └──────────► ESCALATE. No automatic resend. ◄─────────┘\n```\n\n## The dedupe-key\n\n`dedupe_key = hash(thread_id + \"\\n\" + normalize(draft_text))`\n\n`normalize` = trim, collapse internal whitespace, lowercase. The key makes \"the\nsame reply to the same thread\" idempotent regardless of retries.\n\n## The ledger (duplicate-prevention backbone)\n\nAn append-only record, ideally persisted (survives restarts). One row per\n`attempted`:\n\n```json\n{\n  \"dedupe_key\": \"…\",\n  \"thread_id\": \"…\",\n  \"attempted_at\": \"ISO-8601\",\n  \"final_state\": \"confirmed | unconfirmed | failed\",\n  \"verified_at\": \"ISO-8601 | null\",\n  \"operator\": \"agent-id | human-id\"\n}\n```\n\nRules:\n- Write the row at `attempted`, **before** verifying. A crash between send and\n  verify must never look like \"never sent.\"\n- Before any new send, check the ledger for the dedupe-key:\n  - `confirmed` recently → STOP, already handled.\n  - `attempted`/`unconfirmed` present → STOP, do NOT resend; escalate.\n- If no persistent ledger exists, keep an in-session ledger and treat a restart\n  as \"state unknown → verify by reading the thread before any send.\"\n\n## Verify window\n\nRe-read the thread at **t+2s, t+5s, t+10s** after `attempted` (Airbnb UI can\nlag). Match by outbound text + a timestamp newer than the attempt. If still\nabsent after the last poll → `unconfirmed`. Do not extend indefinitely; do not\nresend.\n\nWhen the endpoint read is ambiguous (e.g., returns cached/empty), escalate the\n*verification* (not the send) to a DevTools read of the live thread DOM. Reading\nto verify is always allowed; it is never a second send.\n\n## Edge cases\n\n| Situation | Correct handling |\n|---|---|\n| Endpoint `sent:true`, message never appears | `unconfirmed`. Escalate. Never resend. |\n| Send timed out, unknown if it landed | `failed`. Re-read thread. If the text is now present → reclassify `confirmed` (record it). If absent → escalate; do NOT blind-resend. |\n| Two replies queued for one thread | Process serially; each gets its own dedupe-key and full machine. |\n| Guest sends a new message mid-verify | Does not affect the outbound verification; verify your own message only. |\n| Approval denied at the gate | Stay `drafted`. Discard or revise; never send. |\n\nFile v0.2.0:references/airbnb-safety-rules.md\n\n# Reference — Airbnb safety rules\n\nThe portable safety contract. Universal — do not weaken per deployment. The only\ndeployment-specific knob is the **approval policy** (who approves, and whether\napproval is required for sends).\n\n## Operation tiers\n\n| Tier | Definition | Examples | Default gate |\n|---|---|---|---|\n| **READ** | Cannot change anything a guest or the host can see. | check inbox, read thread, lookup reservation, booking summary, inspect calendar, verify a sent message | None — proceed. |\n| **WRITE** | Produces something a guest sees, but bounded & verifiable. | send a guest reply | Approval (if configured) **+ mandatory verify**. |\n| **MUTATE** | Affects money, availability, or listing state; effectively irreversible. | change price, block/open dates, edit listing, accept/decline a booking, issue a refund | **v1: refuse + escalate.** (v2: own gated workflow.) |\n\nClassify every operation into exactly one tier *before* acting. If an operation\nspans tiers, split it; never let a READ workflow quietly perform a WRITE.\n\n## Approval gate (WRITE)\n\n- Per-message, never standing. One approval authorizes exactly one send of one\n  draft to one thread.\n- Present to the approver: the target thread id, the last inbound guest message,\n  and the full draft text. Wait for explicit \"go\".\n- Deployment policy decides whether approval is *required*. Safe default:\n  required for all guest-facing sends. If disabled, verification is still\n  mandatory.\n\n## Irreversibility boundary\n\nTreat everything a guest can see, and anything touching money or availability,\nas irreversible in practice. You cannot reliably \"unsend\". This is why:\n- WRITE ops always verify after the fact.\n- MUTATE ops are refused in v1 rather than attempted optimistically.\n\n## Ambiguous state — the default is caution\n\n| Ambiguity | Action |\n|---|---|\n| Can't tell if a send landed | `unconfirmed`. Do NOT resend. Escalate. |\n| Two read paths disagree on a material fact (dates, guest count, price) | Report BOTH, flag the discrepancy, escalate. Do not silently pick one. |\n| Browser identity health unclear | Check the browser health role; if unhealthy, escalate rather than assuming. |\n| Ledger state unknown after restart | Verify by reading the live thread before any send. |\n\n## Escalate to a human when\n\n- A send reaches `unconfirmed` or `failed`.\n- You are asked to perform any MUTATE operation.\n- The host browser identity reports unhealthy.\n- Two read paths disagree on a material fact.\n- You would need to send the same message twice for any reason.\n- A required tool role for the requested operation is missing.\n\n## Escalation report shape\n\nWhen escalating, emit enough for a human to act without re-investigating:\n\n```\nESCALATION\n  op:            send_reply\n  thread_id:     <id>\n  reservation:   <id | none>\n  state:         unconfirmed\n  what happened: send endpoint returned sent:true; message not visible after\n                 t+2/5/10s re-reads.\n  draft:         \"<the exact text>\"\n  do NOT:        resend automatically.\n  human action:  open the thread, confirm whether the message is present; if\n                 absent, decide whether to re-send manually.\n```\n\n## Observability\n\nEvery operation — read or write — emits a structured status:\n\n```json\n{\n  \"op\": \"send_reply\",\n  \"tier\": \"WRITE\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"thread_id\": \"…\",\n  \"reservation_id\": \"… | null\",\n  \"state\": \"confirmed | unconfirmed | failed | ok\",\n  \"dedupe_key\": \"… | null\",\n  \"human_action_needed\": false\n}\n```\n\nA human should be able to reconstruct exactly what happened from the report\nstream alone.\n\nFile v0.2.0:references/airbnb-tool-priority.md\n\n# Reference — Airbnb tool priority & role mapping\n\n**This is the one file you customize per deployment.** It maps abstract tool\n*roles* (used everywhere else in the skill) to the concrete tool names in your\nenvironment. Everything else in the package is portable; edit here only.\n\n## Tier order (universal — do not reorder)\n\n1. **Airbnb endpoint** — first-class, structured, auth-aware. Default for every\n   supported operation.\n2. **Agent-browser** — navigate/inspect when no endpoint covers the op or an\n   endpoint is hard-down.\n3. **DevTools** — read-only DOM inspection / UI verification.\n4. **Playwright** — last-resort automation for read-only or human-approved ops.\n\n## Role → tool map (EDIT THIS for your deployment)\n\n> The names below are the reference deployment's tools. Replace with yours.\n> If a role has no tool in your environment, leave it blank — the skill will\n> degrade to the next available tier and escalate if a *required* role is empty.\n\n| Role | Reference deployment tool | Tier |\n|---|---|---|\n| Airbnb: list/read messages | `/tools/airbnb/messages` | 1 |\n| Airbnb: list reservations | `/tools/airbnb/reservations` | 1 |\n| Airbnb: read calendar | `/tools/airbnb/calendar` | 1 |\n| Airbnb: send message | `/tools/airbnb/messages/send` | 1 |\n| Browser: health/identity | `/tools/browser/status` | 2 |\n| Browser: navigate | `/tools/browser/navigate` | 2 |\n| DevTools: list tabs | `/tools/devtools/tabs` | 3 |\n| DevTools: navigate | `/tools/devtools/navigate` | 3 |\n| DevTools: evaluate (read-only) | `/tools/devtools/evaluate` | 3 |\n| Playwright: fallback | (deployment-specific path) | 4 |\n\n## When to drop a tier (decision rules)\n\n- **Stay on tier 1** unless: (a) no tier-1 tool exists for this operation, or\n  (b) a tier-1 call returns a *hard transport failure* (5xx / connection error /\n  timeout) **twice in a row**. A single slow or empty response is not a drop\n  trigger — retry once on tier 1 first.\n- **Before dropping for \"auth\" reasons**, call the browser health/identity role.\n  Airbnb auth is host-owned (provided by the host browser identity). Missing\n  *local* browser/session state is NOT evidence the account is logged out.\n- **DevTools is read-only in this skill.** Use `evaluate` to read/verify DOM,\n  never to click a send button. A send happens only via the tier-1 send role (or\n  an explicitly human-approved browser send).\n- **Playwright only** when tiers 1–3 are all unavailable for the operation AND\n  the operation is read-only or has explicit human approval. Log that you used\n  it and why.\n\n## Graceful degradation matrix\n\n| Operation | tier-1 absent → | also tier-2 absent → | all absent → |\n|---|---|---|---|\n| read inbox / thread | agent-browser read | DevTools DOM read | escalate (required role missing) |\n| read reservations | agent-browser read | DevTools DOM read | escalate |\n| read calendar | agent-browser read | DevTools DOM read | escalate |\n| send reply | human-approved browser send only | — | escalate; do NOT improvise |\n| verify sent | re-read via any read role | DevTools DOM read | mark `unconfirmed`, escalate |\n\nSending is special: there is no silent fallback for a send. If the tier-1 send\nrole is unavailable, the only alternative is an explicitly human-approved\nbrowser send, and verification is still mandatory.\n\nFile v0.2.0:references/calendar-mutation-procedure.md\n\n# Reference — Calendar mutation procedure (multicalendar UI)\n\nVerified end-to-end 2026-07-04 on the live listing (block + fresh-load verification).\nFollow these steps EXACTLY; the multicalendar is a virtualized, animated grid and\nimprovised clicking fails silently.\n\nAll calls: ClawBridge browser role (`/tools/browser/*`), header\n`Authorization: Bearer $CLAWBRIDGE_TOKEN`, base `http://host.docker.internal:3201`.\n\n## Step 1 — Open the calendar\n\n`POST /tools/browser/navigate` `{\"url\":\"https://www.airbnb.com/multicalendar\"}` then\n`GET /tools/browser/snapshot`. Find the month selector:\n`combobox \"Select a month to view\" [ref=eNN]`.\n\n## Step 2 — Reach the target month (select + poll, re-fire if stalled)\n\n`POST /tools/browser/action` `{\"action\":\"select\",\"args\":[\"eNN\",\"June 2027\"]}`.\nThe grid *animates* through intervening months and loads lazily. Poll the snapshot\nevery ~8s for a gridcell whose label contains your target date (e.g. \"June 16, 2027\").\nIf the visible month stops advancing for 3+ polls, RE-FIRE the same select — each\nfiring advances a few months. Do not proceed until the target date's LISTING-ROW\ncell is present (label like \"<listing name>, June 16, 2027, Select as start date. $NNNN MXN\").\nThat label is your **before-state** — record it.\n\n## Step 3 — Select the date (JS event dispatch, NOT a plain ref click)\n\nPlain `click` on the cell ref reports ✓ but does not register. Use\n`POST /tools/browser/eval` with this script (substitute the date):\n\n```\n(() => { const leaf=[...document.querySelectorAll('*')].find(e=>e.childElementCount===0&&(e.textContent||'').includes('June 16, 2027, Select as')); if(!leaf) return 'NOT FOUND'; const cell=leaf.closest('button,[role=button],[role=gridcell]'); cell.scrollIntoView({block:'center',inline:'center'}); const r=cell.getBoundingClientRect(); const o={bubbles:true,cancelable:true,clientX:r.x+r.width/2,clientY:r.y+r.height/2,view:window}; for(const t of ['pointerover','mouseover','pointerdown','mousedown','pointerup','mouseup','click']){cell.dispatchEvent(new MouseEvent(t,o));} return 'CLICKED '+leaf.textContent.slice(0,60); })()\n```\n\nRun it **twice** (start date, then end date = same cell for a single night). After the\nsecond run the side panel opens: \"Selected dates M/D/YYYY → M/D/YYYY\".\n\n## Step 4 — Set availability and save\n\nSnapshot → find `radio \"Available\"` / `radio \"Blocked\"` and `button \"Save\"`.\n- To block: `{\"action\":\"check\",\"args\":[\"<Blocked-radio-ref>\"]}`\n- To unblock: `{\"action\":\"check\",\"args\":[\"<Available-radio-ref>\"]}`\n\nRe-snapshot: confirm the radio flipped (`checked=true`) AND Save is no longer\n`[disabled]`. Then `{\"action\":\"click\",\"args\":[\"<Save-ref>\"]}`.\n\n## Step 5 — MANDATORY independent verification (fresh load)\n\nNever trust the in-page state. `POST /tools/browser/navigate` to the multicalendar\nagain (fresh load), redo Step 2, and read the target cell's label:\n- Blocked ⇒ label contains \"Unavailable\"\n- Available ⇒ label shows the nightly price again\n\nReport before-state, actions taken, after-state label, and the screenshot paths\n(every action response includes one). If the label is missing or ambiguous, report\n`unconfirmed` — do NOT retry the mutation.\n\n## Known traps (all observed live)\n\n- Cell refs go stale between snapshots — re-snapshot before every ref use.\n- The month select animates; clicking mid-animation hits \"Loading\" skeleton cells.\n- A ✓ Done response does NOT mean the UI registered the interaction — only the\n  side panel opening / radio flipping / fresh-load label proves anything.\n- Success claims without the Step-5 fresh-load check are the #1 failure mode.\n\nFile v0.2.0:references/future-adapter-interface.md\n\n# Reference — future adapter interface\n\nThis skill is the **behavioral contract**. A later, more formal adapter/tool\nlayer should harden the **mechanical guarantees** so correctness doesn't depend\non a model remembering a markdown rule. This file describes the adapter the\nskill is designed to pair with, and how the skill should call it if/when it\nexists.\n\n## Design principle\n\n> Judgment stays in the skill; guarantees move to the adapter.\n\n- **Skill (prompt layer):** when to escalate, how to summarize guest intent,\n  whether a state is ambiguous — model reasoning.\n- **Adapter (code layer):** send-exactly-once, the dedupe ledger, the verify\n  poll, tier fallback — properties you want *enforced*, not *hoped for*.\n\n## Ideal adapter functions\n\nThe skill should prefer these when present, and fall back to its manual\nprocedures when they are not.\n\n### `airbnb.sendVerified(thread_id, text, dedupe_key) → { state, visible_at, error? }`\nAtomically: dedupe-check → send-once → write ledger → verify-poll. Returns a\nfinal state from the same vocabulary (`confirmed | unconfirmed | failed`). This\nis the single most valuable function — it makes the cardinal \"no duplicate\nsends\" rule a code-level guarantee.\n\nWhen present: `send_reply` delegates entirely and just reports `state`.\nWhen absent: `send_reply` runs the manual state machine from\n`airbnb-message-state-machine.md`.\n\n### `airbnb.dedupeCheck(dedupe_key) → { already_sent, last_state, last_at }`\nLets the skill short-circuit before drafting if a reply already went out.\n\n### Read functions\n- `airbnb.listInbox(filter?) → Thread[]`\n- `airbnb.readThread(thread_id) → { messages[], last_inbound, ... }`\n- `airbnb.listReservations(filter?) → Reservation[]`\n- `airbnb.readCalendar(range) → CalendarSpan[]`\n\nThese mirror the read verbs and let the skill avoid tier-selection logic when\nthe adapter already owns it.\n\n### Future MUTATE functions (v2, each gated)\n- `airbnb.setCalendar(range, state, dedupe_key)` — block/open dates.\n- `airbnb.setPrice(range, amount, dedupe_key)` — pricing.\n- `airbnb.respondToBooking(reservation_id, decision, dedupe_key)` — accept/decline.\n\nEach MUTATE adapter function should follow the same shape as `sendVerified`:\ndo-once + ledger + verify, returning a final state. The skill wraps each in a\nread → propose → approve → call → report flow.\n\n## Capability detection\n\nThe skill should treat adapter functions as **optional**: detect availability,\nprefer them, and degrade to manual procedures otherwise. Pseudocode:\n\n```\nif has(airbnb.sendVerified):\n    result = airbnb.sendVerified(thread_id, text, dedupe_key)\n    report(result.state)\nelse:\n    run_manual_send_state_machine()   # per airbnb-message-state-machine.md\n```\n\n## Why both layers (defense in depth)\n\n- The **adapter** prevents a duplicate send even if an agent misbehaves.\n- The **skill** explains *why* and handles the judgment the adapter can't encode\n  (intent, tone, when to escalate).\n\nShipping the skill first is correct: it makes the contract usable today. The\nadapter promotes the riskiest guarantees into code as the deployment matures.\n\nFile v0.2.0:CHANGELOG.md\n\n# Changelog\n\nAll notable changes to the `airbnb-gateway` skill package. Format follows\n[Keep a Changelog](https://keepachangelog.com/); versions follow SemVer.\n\n## [Unreleased]\n\n### Changed\n- v0.2.0: **Approval-gated calendar mutations.** Split the MUTATE tier into\n  MUTATE-CAL (block/open dates, nightly price — permitted with explicit\n  per-operation operator approval: APPROVED keyword, one op per approval,\n  mandatory fresh-load verification after, reported inverse operation) and\n  MUTATE-RESTRICTED (listing edits, accept/decline, refunds — still refuse +\n  escalate). Added `references/calendar-mutation-procedure.md`, the live-verified\n  step-by-step multicalendar procedure (animation-safe month navigation,\n  JS event-dispatch date selection, availability radio + save, fresh-load\n  verification), and `SKILL_CARD.md` (NVIDIA skill-card trust format with\n  release evidence from the 2026-07-04 live round-trip test).\n- v0.1.4: Added a friendly \"star this skill\" call-to-action to `SKILL.md`,\n  `README.md`, and `CLAWHUB.md` (shown on the ClawHub listing) — content-only,\n  no behavior change.\n- v0.1.3: Clean re-cut for review handoff — no content or doctrine change since\n  v0.1.2; published to give the reviewing agent a fresh version number to pin\n  its install/audit against.\n- v0.1.2: Pre-install polish pass (no doctrine change). Replaced the\n  \"Capabilities this skill expects\" section with an explicit **Minimum\n  Environment Contract** (read-only minimum vs send-capable minimum vs optional\n  enhancements) so outside users can tell at a glance whether the skill can run.\n  Converted the **Command surface** table to a renderer-safe bullet list (the\n  `<id>`/`<thread>` placeholders rendered as collapsed on some markdown\n  platforms). Verified package completeness: all `references/` and `examples/`\n  files ship in the published artifact — the ClawHub web page previews only\n  `SKILL.md`, but installs are complete.\n- v0.1.1: Rewrote the `SKILL.md` frontmatter `description` to match the ClawHub\n  listing blurb (`CLAWHUB.md`), so the published summary reads in the intended\n  voice instead of the long internal one-liner.\n\n### Added\n- Initial `airbnb-gateway` skill package (v0.1.0):\n  - `SKILL.md` — operating contract: Five Laws, tier-based operating model,\n    safety tiers, send state machine, command surface, anti-patterns,\n    future-adapter section, maintainer notes.\n  - `references/airbnb-message-state-machine.md` — full send state machine,\n    dedupe key, ledger contract, verify window, edge cases.\n  - `references/airbnb-tool-priority.md` — the one per-deployment file: tier\n    order + role→tool map + degradation matrix.\n  - `references/airbnb-safety-rules.md` — READ/WRITE/MUTATE tiers, approval gate,\n    ambiguity handling, escalation report shape, observability.\n  - `references/future-adapter-interface.md` — ideal adapter functions and how\n    the skill should call them when present.\n  - `examples/` — check-inbox, read-thread, send-reply-with-verification\n    (incl. the no-resend `unconfirmed` path), reservation-lookup,\n    calendar-inspection.\n  - `state/send-log.schema.json` — append-only dedupe ledger schema.\n  - `README.md`, `LICENSE` (MIT) — Open Hub-friendly packaging.\n  - `CLAWHUB.md` — verbatim ClawHub/Open Hub listing description.\n\n### Notes\n- Scope is read operations + verified single-send. Calendar/pricing/listing\n  mutations are intentionally refused (escalate) until v2.\n\nFile v0.2.0:CLAWHUB.md\n\n# airbnb-gateway\n\n`airbnb-gateway` is a skill for safe, coherent Airbnb operations in OpenClaw-style agent environments.\n\nIt standardizes how agents:\n- check inbox threads\n- inspect reservations and booking state\n- review calendar context\n- draft guest replies\n- send messages safely\n- verify whether a send actually appeared in the live Airbnb thread\n\nThe skill teaches a strict operating model:\n- prefer Airbnb-native endpoints before generic browser automation\n- treat send acknowledgments as `attempted`, not automatically `confirmed`\n- verify outbound messages in the live thread UI before declaring success\n- never auto-resend from ambiguous or unconfirmed state\n\n`airbnb-gateway` is designed to reduce duplicate messages, normalize agent behavior, and provide a safer foundation for Airbnb messaging, bookings, and calendar workflows.\n\nIt works well as:\n- a standalone operational skill today\n- a future companion to a more formal Airbnb adapter/tool layer tomorrow\n\n---\n\n⭐ **Find this useful?** If `airbnb-gateway` saves you time, please **star it** (the ⭐ at the top of this ClawHub page) — stars help other rental operators discover it and keep it maintained. Thank you!\n\nFile v0.2.0:examples/calendar-inspection.md\n\n# Example — `inspect_calendar`\n\nA READ operation. Report calendar state and **flag** high-risk patterns. In v1\nthe skill never changes the calendar — flagging is read-only; fixing is a MUTATE\nop reserved for v2.\n\n## Procedure\n1. Tier 1: call the Airbnb calendar role for the requested range (default: next\n   60 days).\n2. Classify each day/span: `booked`, `blocked`, `open`.\n3. Run risk checks (flag, don't fix):\n   - **Double-booking:** two reservations overlapping the same night.\n   - **Anomalous price:** a night priced far outside the range's norm.\n   - **Risky back-to-back:** check-out and next check-in same day with no buffer\n     for turnover.\n   - **Unexpected open gap:** open nights between two bookings that usually fill.\n4. Emit state + a `flags` array. Recommend nothing irreversible.\n\n## Reference-deployment call\n```\nGET /tools/airbnb/calendar?from=2026-06-03&to=2026-08-02     # tier 1\n```\n\n## Sample output\n```json\n{\n  \"op\": \"inspect_calendar\",\n  \"tier\": \"READ\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"range\": { \"from\": \"2026-06-03\", \"to\": \"2026-08-02\" },\n  \"spans\": [\n    { \"from\":\"2026-06-06\", \"to\":\"2026-06-09\", \"state\":\"booked\", \"reservation_id\":\"r_5521\" },\n    { \"from\":\"2026-06-09\", \"to\":\"2026-06-11\", \"state\":\"open\" },\n    { \"from\":\"2026-06-11\", \"to\":\"2026-06-14\", \"state\":\"booked\", \"reservation_id\":\"r_5540\" }\n  ],\n  \"flags\": [\n    { \"type\":\"back_to_back\", \"date\":\"2026-06-09\",\n      \"detail\":\"r_5521 check-out and r_5540-adjacent open night; verify turnover time\" }\n  ],\n  \"human_action_needed\": false\n}\n```\n\n## When a flag is serious\nIf a flag implies guest-visible risk (e.g., an actual double-booking), escalate\nto a human immediately with the specifics. Do NOT attempt to resolve it by\nchanging the calendar — that's a MUTATE op and is out of scope in v1.\n\n## Degradation\n- Tier-1 calendar role missing/hard-down twice → agent-browser read of the\n  calendar page → DevTools DOM read → escalate.\n\n## Anti-patterns\n- ❌ \"Fixing\" a double-booking by blocking dates. Flag + escalate only in v1.\n- ❌ Reporting calendar state from a stale cache without noting it; if the read\n  path is ambiguous, say so.\n\nFile v0.2.0:examples/check-inbox.md\n\n# Example — `check_inbox`\n\nA READ operation. No approval, no write. Goal: surface threads needing attention,\nprioritized, without changing anything.\n\n> Tool names below are from the reference deployment. Map them via\n> `references/airbnb-tool-priority.md`.\n\n## Procedure\n1. Tier 1: call the Airbnb messages-list role.\n2. For each thread, note: `thread_id`, guest name, last-message direction\n   (inbound/outbound), last-message time, unread flag, linked reservation if\n   available.\n3. Prioritize: unread inbound > inbound awaiting reply > everything else.\n   Within a bucket, sort by most recent.\n4. Emit a structured summary. Do not open/draft/send anything.\n\n## Reference-deployment call\n\n```\nGET /tools/airbnb/messages          # tier 1\n```\n\n## Sample output\n\n```json\n{\n  \"op\": \"check_inbox\",\n  \"tier\": \"READ\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"threads\": [\n    { \"thread_id\": \"t_88213\", \"guest\": \"Marco R.\", \"last\": \"inbound\",\n      \"at\": \"2026-06-03T14:22:00Z\", \"unread\": true, \"reservation_id\": \"r_5521\",\n      \"snippet\": \"Hi! Is early check-in possible on Friday?\" },\n    { \"thread_id\": \"t_88190\", \"guest\": \"Dana P.\", \"last\": \"outbound\",\n      \"at\": \"2026-06-03T09:10:00Z\", \"unread\": false, \"reservation_id\": \"r_5498\",\n      \"snippet\": \"Thanks, enjoy your stay!\" }\n  ],\n  \"needs_attention\": [\"t_88213\"],\n  \"human_action_needed\": false\n}\n```\n\n## Degradation\n- Tier-1 list role missing or hard-down twice → agent-browser navigate to the\n  inbox and read the rendered list (still READ). If that's also unavailable →\n  DevTools DOM read. If all absent → escalate (required read role missing).\n\n## Anti-patterns\n- ❌ Auto-drafting replies during an inbox check. `check_inbox` is read-only;\n  drafting is a separate, explicit step.\n\nArchive v0.1.4: 17 files, 26363 bytes\n\nFiles: CHANGELOG.md (2727b), CLAWHUB.md (1187b), examples/calendar-inspection.md (2145b), examples/check-inbox.md (1745b), examples/read-thread.md (1678b), examples/reservation-lookup.md (2158b), examples/send-reply-with-verification.md (2886b), LICENSE (1070b), README.md (3164b), references/airbnb-message-state-machine.md (4677b), references/airbnb-safety-rules.md (3607b), references/airbnb-tool-priority.md (3317b), references/future-adapter-interface.md (3134b), skill-card.md (2732b), SKILL.md (12742b), state/send-log.schema.json (1642b), _meta.json (133b)\n\nFile v0.1.4:SKILL.md\n\n---\nname: airbnb-gateway\ndescription: >\n  A skill for safe, coherent Airbnb operations in OpenClaw-style agent\n  environments. It standardizes how agents check inbox threads, inspect\n  reservations and booking state, review calendar context, draft guest replies,\n  send messages safely, and verify whether a send actually appeared in the live\n  Airbnb thread. It teaches a strict operating model: prefer Airbnb-native\n  endpoints before generic browser automation; treat send acknowledgments as\n  attempted, not automatically confirmed; verify outbound messages in the live\n  thread UI before declaring success; and never auto-resend from ambiguous or\n  unconfirmed state. Designed to reduce duplicate messages, normalize agent\n  behavior, and provide a safer foundation for Airbnb messaging, bookings, and\n  calendar workflows — useful standalone today and as a companion to a more\n  formal Airbnb adapter/tool layer tomorrow.\nversion: 0.1.4\nrisk: write-gated\ntags: [airbnb, vacation-rental, messaging, operations, openclaw]\nlicense: MIT\nhomepage: \"\"\nmaintainer: \"\"\n---\n\n# airbnb-gateway\n\n> ⭐ **Find this skill useful?** If `airbnb-gateway` saves you time, please\n> **star it** (the ⭐ at the top of this ClawHub page) — stars help other rental\n> operators discover it and keep it maintained. Thank you!\n\nYou are operating a live, revenue-bearing Airbnb account on behalf of a real\nhost. Guests are real people. A duplicate message, a wrong send, or a careless\ncalendar change has real consequences. This skill exists so that **every agent\nhandles Airbnb identically and safely**, instead of improvising tool calls per\nturn.\n\n> **Portability note.** This skill is environment-agnostic. It refers to tool\n> *roles* (an \"Airbnb messages endpoint\", an \"agent-browser\", a \"DevTools\n> bridge\", a \"Playwright fallback\") rather than hard-coded URLs. Your concrete\n> tool names live in `references/airbnb-tool-priority.md` — edit that one file\n> to map roles to the actual tools in your deployment. Everything else here is\n> universal.\n\n---\n\n## THE FIVE LAWS (non-negotiable)\n\n1. **Platform-first.** Always try the first-class Airbnb endpoint for an\n   operation before any browser automation. Browser weirdness is NOT evidence\n   that Airbnb access is down.\n2. **Read before write.** Never send a reply without first reading the live\n   thread in the *same* operation.\n3. **`sent: true` is `attempted`, not `confirmed`.** An endpoint success proves\n   the call returned — not that the guest can see the message. You MUST re-read\n   the thread and SEE the outbound message before marking `confirmed`.\n4. **Never auto-resend an `unconfirmed` send.** Duplicate guest messages are\n   worse than a late reply. Escalate to a human instead.\n5. **Writes gate; mutations stop.** Sending a message is approval-gated.\n   Pricing, availability, listing edits, and accept/decline are NOT implemented\n   in v1 — refuse and escalate.\n\nIf you are ever unsure, STOP and report. A paused agent is recoverable; a\nduplicated or wrong guest message is not.\n\n---\n\n## Minimum Environment Contract\n\nUse this to decide, before installing, whether your deployment can run the skill\nand at what level. Map each role to a real tool in\n`references/airbnb-tool-priority.md`. The skill degrades gracefully: if a\n**read-only** requirement is met but a **send** requirement is not, the skill\nruns in read-only mode and refuses sends rather than improvising.\n\n**Read-only mode — minimum to run safely (inbox / threads / reservations / calendar):**\n- At least one **Airbnb read path** — list/read messages, reservations, and\n  calendar (first-class endpoint preferred; agent-browser or DevTools read\n  acceptable as fallback).\n\nThat's the entire requirement for every READ verb. Nothing can be sent in this\nmode.\n\n**Send-capable mode — additional minimum to send a guest reply:**\n- An **Airbnb send path** — the first-class send endpoint (preferred) OR an\n  explicitly human-approved browser send.\n- A **thread re-read path** — to verify the outbound message after sending.\n  (The same read path from read-only mode satisfies this.)\n\nIf either is missing, do NOT send: stay in read-only mode and escalate.\n\n**Optional enhancements — improve safety / fallback depth when present:**\n- **Agent-browser** — navigate/inspect when an endpoint is missing or hard-down.\n- **DevTools bridge** — read-only DOM inspection to verify UI state.\n- **Playwright fallback** — last-resort automation for read-only ops.\n- **Persistent send ledger** — survives restarts; hardens duplicate prevention.\n- **Approval channel** — human/approver sign-off before a send.\n\nAbsent optional capabilities reduce fallback depth but never change the safety\nrules. A required capability that is absent for a given operation triggers an\nescalation, never an improvised workaround.\n\n---\n\n## Operating model — which tool path do I use?\n\nResolve top-down. Drop to the next tier ONLY when the current tier is genuinely\nunavailable, not merely \"looked weird once.\" Full detail in\n`references/airbnb-tool-priority.md`.\n\n```\nOPERATION\n  │\n  ├─ 1. AIRBNB ENDPOINT  (first-class, structured, auth-aware)\n  │       → DEFAULT for every supported operation. Always start here.\n  │\n  ├─ 2. AGENT-BROWSER  (navigate / status)\n  │       → ONLY when no endpoint covers the op, OR an endpoint returned a hard\n  │         transport failure (5xx/timeout) TWICE. First confirm the host\n  │         browser identity is alive before assuming auth loss.\n  │\n  ├─ 3. DEVTOOLS  (tabs / navigate / evaluate — READ-ONLY here)\n  │       → DOM inspection / UI verification when the endpoint read is\n  │         ambiguous. Never use evaluate to perform a write/click-send.\n  │\n  └─ 4. PLAYWRIGHT  (fallback)\n          → LAST RESORT. Only when 1–3 are unavailable AND the op is read-only\n            or an explicitly human-approved write.\n```\n\n---\n\n## Safety model\n\nClassify every operation before acting. Full table in\n`references/airbnb-safety-rules.md`.\n\n| Tier | Examples | Gate |\n|---|---|---|\n| **READ** | check inbox, read thread, lookup reservation, inspect calendar | None. Proceed. |\n| **WRITE** | send a guest reply | Approval if configured; ALWAYS verify after. |\n| **MUTATE** | change price, block/open dates, edit listing, accept/decline, refund | **NOT in v1.** Refuse + escalate. |\n\n**Escalate to a human when:** a send reaches `unconfirmed` or `failed`; you're\nasked to perform any MUTATE op; the host browser identity reports unhealthy; two\nread paths disagree on a material fact (dates, guest count, price); or you would\nneed to send the same message twice for any reason.\n\n---\n\n## Message send — the state machine (the core procedure)\n\nStates: `drafted → attempted → (confirmed | unconfirmed | failed)`. Full machine\nwith timings in `references/airbnb-message-state-machine.md`.\n\n```\n[drafted]      reply composed; thread + dedupe-key recorded\n   │           (WRITE gate: get approval if required)\n   ▼\n[attempted]    ← send endpoint called EXACTLY ONCE; ledger written NOW\n   │             endpoint sent:true  →  ATTEMPTED, not done\n   │\n   ├─ verify: re-read the SAME thread (endpoint; DevTools if ambiguous)\n   │\n   ├─ outbound visibly present?  ── yes ──► [confirmed]   ✅ report\n   ├─ absent after verify window ── no  ──► [unconfirmed] ⚠️ DO NOT RESEND, escalate\n   └─ send call errored                  ► [failed]       ❌ re-read first, escalate, no blind resend\n```\n\n### Send procedure (follow exactly)\n1. **Read the live thread.** Capture the last inbound message + a dedupe-key\n   (`thread_id` + normalized hash of the draft text).\n2. **Check the send ledger.** If a `confirmed` (or recent `attempted`) send with\n   the same dedupe-key exists → STOP, already handled.\n3. **Draft** the reply → state `drafted`.\n4. **Approval gate** if required — present draft + thread context, wait for \"go\".\n5. **Send exactly once** via the first-class send endpoint → state `attempted`.\n   Write the ledger entry *before* verifying, so a crash mid-verify can't cause\n   a blind resend.\n6. **Verify** — re-read the thread, look for the outbound text/timestamp.\n7. **Confirm or not** — visible → `confirmed`; absent within window →\n   `unconfirmed`.\n8. **Report** final status: thread id, state, and the human action needed.\n\nThe duplicate-prevention contract: exactly ONE call to the send endpoint per\n`drafted` item, ever. `unconfirmed`/`failed` NEVER auto-transition to a new send.\nOnly a human, after reading the live thread, may authorize a retry.\n\n---\n\n## Reservation & calendar (READ-only in v1)\n\n- **`booking_summary`** — list reservations sorted by check-in: guest, dates,\n  status, `reservation_id`, linked `thread_id`.\n- **`lookup_reservation`** — by id or guest; map reservation ↔ thread so a reply\n  ties to the right booking.\n- **`inspect_calendar`** — report blocked/open/booked spans; FLAG (don't fix)\n  high-risk states (double-booking, anomalous price, risky back-to-back gaps).\n\nCalendar mutation, pricing, accept/decline, and listing edits are reserved for\nv2 and get their own gated workflows mirroring the send machine\n(read → propose → approve → do-once → verify → report). Until then: refuse +\nescalate.\n\n---\n\n## Command surface\n\nAgents speak only in these verbs; each maps to a canonical procedure. Format is\n`verb (TIER) — description`.\n\n**READ verbs (no gate):**\n- `check_inbox` **(READ)** — list threads needing attention, prioritized.\n- `read_thread <id>` **(READ)** — full live thread + guest-intent summary.\n- `lookup_reservation <id-or-guest>` **(READ)** — reservation details + linked thread.\n- `booking_summary` **(READ)** — upcoming bookings, sorted by check-in.\n- `inspect_calendar [range]` **(READ)** — calendar state + flagged risks.\n- `verify_sent <thread> <draft>` **(READ)** — re-check a thread for an outbound message.\n\n**WRITE verbs (gated + verified):**\n- `draft_reply <thread> <intent>` **(WRITE-pre)** — produce a reviewable draft; result state `drafted`.\n- `send_reply <thread> <draft>` **(WRITE)** — run the full send state machine.\n\n**Status:**\n- `report_status` — emit structured status of the last operation.\n\n`send_reply` is the ONLY write, and it internally enforces\nread → draft → approve → send-once → verify → report. Agents must not decompose\nit into lower-level steps to skip verification.\n\n---\n\n## Future adapter compatibility\n\nThis skill is the *behavioral contract*. A future formal adapter/tool layer\nshould harden the *mechanical guarantees* so correctness doesn't depend on a\nmodel remembering a markdown rule. See `references/future-adapter-interface.md`.\n\nIdeal future adapter functions (call them if present, fall back to the manual\nprocedure if not):\n\n- `airbnb.sendVerified(thread_id, text, dedupe_key)` → returns\n  `{state, visible_at}` after doing send-once + ledger + verify atomically.\n- `airbnb.dedupeCheck(dedupe_key)` → `{already_sent: bool, last_state}`.\n- `airbnb.readThread(thread_id)` / `airbnb.listInbox()` /\n  `airbnb.listReservations()` / `airbnb.readCalendar(range)`.\n\nWhen `sendVerified` exists, `send_reply` delegates to it and simply reports the\nreturned state. When it doesn't, `send_reply` runs the manual procedure above.\n\n---\n\n## Anti-patterns (do NOT do these)\n\n- ❌ **Jumping to Playwright early.** Endpoints exist and are preferred; falling\n  to Playwright because something felt slow once is wrong.\n- ❌ **Treating `sent: true` as confirmed.** It's `attempted`. Always verify.\n- ❌ **Resending from `unconfirmed`.** This creates duplicate guest messages.\n  Escalate instead.\n- ❌ **\"Auth must be dead.\"** Missing local browser/session state is NOT proof —\n  auth is host-owned. Check browser status + an endpoint first.\n- ❌ **Mixing read-safe and write-risk behavior.** Classify every op; never let\n  a read workflow quietly perform a write.\n- ❌ **Improvising the send flow per turn.** There is exactly ONE send\n  procedure. Use it.\n\n---\n\n## Maintainer notes\n\n- **Customize per deployment:** `references/airbnb-tool-priority.md` (map roles\n  to real tool names), the approval-gate policy, and whether a persistent ledger\n  is wired. Examples under `examples/` are illustrative — adapt payloads to your\n  tools.\n- **Keep universal:** the Five Laws, the send state machine, the safety tiers,\n  and the command vocabulary. These are the portable core; don't fork them per\n  deployment.\n- **Evolving the skill:** add new operations by giving each its own gated\n  workflow in the same shape as the send machine. Never add a generic \"do\n  anything\" write. Bump `version`; record changes in a CHANGELOG if published.\n\nFile v0.1.4:README.md\n\n# airbnb-gateway\n\nA reusable OpenClaw / Codex-style **skill package** for safe, coherent,\nend-to-end Airbnb host operations: inbox checks, thread reading, reservation\nlookup, booking summaries, calendar inspection, draft replies, **verified**\nmessage sending, and disciplined escalation.\n\n> ⭐ **Find this useful?** If `airbnb-gateway` saves you time, please **star it on ClawHub** — stars help other operators discover it and keep it maintained. Thank you!\n\nIt does not add transport. It orchestrates whatever Airbnb tooling your\nenvironment already has — first-class Airbnb endpoints, agent-browser, DevTools,\nPlaywright — behind one consistent operating model so multiple agents behave\nidentically and never duplicate a guest message.\n\n## Why this exists\n\nTwo reliability lessons are baked into the design:\n\n1. **Browser weirdness ≠ Airbnb down.** Auth is host-owned; prefer platform-aware\n   endpoints before generic browser automation.\n2. **`sent: true` ≠ delivered.** A send is only `confirmed` after re-reading the\n   live thread and *seeing* the message. Endpoint success is just `attempted`,\n   and an `unconfirmed` send is **never** auto-resent.\n\n## Install / use\n\n1. Drop `skills/airbnb-gateway/` into your skills library.\n2. Edit **`references/airbnb-tool-priority.md`** — map the abstract tool roles to\n   the real tool names in your deployment. This is the only required\n   customization.\n3. (Optional) Set your approval policy and wire a persistent send ledger.\n4. Point your agents at the skill. They should speak only in the command verbs\n   (`check_inbox`, `read_thread`, `send_reply`, …) and never call low-level\n   Airbnb tools directly.\n\n## What's portable vs. deployment-specific\n\n| Portable (don't fork) | Deployment-specific (customize) |\n|---|---|\n| The Five Laws | role → tool name map |\n| Send state machine | approval policy |\n| Safety tiers (READ/WRITE/MUTATE) | persistent ledger wiring |\n| Command vocabulary | example payload shapes |\n\n## Layout\n\n```\nairbnb-gateway/\n├── SKILL.md                              # the operating contract (start here)\n├── README.md                             # this file\n├── CHANGELOG.md\n├── LICENSE\n├── references/\n│   ├── airbnb-tool-priority.md           # ← customize per deployment\n│   ├── airbnb-message-state-machine.md   # universal\n│   ├── airbnb-safety-rules.md            # universal\n│   └── future-adapter-interface.md       # how to pair with a code adapter later\n├── examples/\n│   ├── check-inbox.md\n│   ├── read-thread.md\n│   ├── send-reply-with-verification.md   # the critical path\n│   ├── reservation-lookup.md\n│   └── calendar-inspection.md\n└── state/\n    └── send-log.schema.json              # append-only dedupe ledger schema\n```\n\n## Status\n\nv0.1.0 — read operations + verified single-send. Calendar/pricing/listing\nmutations are intentionally **out of scope** until v2 (refuse + escalate).\n\n## License\n\nMIT. No private tokens, paths, or secrets are embedded — example tool names are\nillustrative and must be mapped to your environment.\n\nFile v0.1.4:_meta.json\n\n{\n  \"ownerId\": \"kn77gtjzdkfywnsbs5f349wjxd87hb0x\",\n  \"slug\": \"airbnb-gateway\",\n  \"version\": \"0.1.4\",\n  \"publishedAt\": 1780605059046\n}\n\nFile v0.1.4:references/airbnb-message-state-machine.md\n\n# Reference — Airbnb message send state machine\n\nThis is the authoritative definition of the send discipline. `SKILL.md` carries\nthe operational summary; this file carries the full machine, timings, and edge\ncases. **This logic is universal — do not fork it per deployment.**\n\n## States\n\n| State | Meaning | Terminal? |\n|---|---|---|\n| `drafted` | Reply text composed, thread + dedupe-key recorded. Nothing sent yet. | no |\n| `attempted` | Send endpoint called exactly once and returned. **Delivery NOT proven.** | no |\n| `confirmed` | Outbound message verified visibly present in the live thread. | yes ✅ |\n| `unconfirmed` | Send returned but the message could not be seen after the verify window. | yes ⚠️ |\n| `failed` | The send call itself errored (4xx/5xx/timeout). | yes ❌ |\n\n## Transitions\n\n```\n            approval (if required)              send endpoint\n  drafted ───────────────────────► (ready) ───── exactly once ─────► attempted\n                                                                         │\n                                              re-read same thread        │\n                                          ┌──────────────────────────────┤\n                                          │                              │\n                            visible ◄─────┘                              └─────► call errored\n                               │                                                     │\n                               ▼                                                     ▼\n                          confirmed ✅                                            failed ❌\n                                                                                     │\n                            absent after verify window                               │\n                               │                                                     │\n                               ▼                                                     ▼\n                         unconfirmed ⚠️  ◄──────── (treat like) ──────────── re-read thread\n                               │                                                     │\n                               └──────────► ESCALATE. No automatic resend. ◄─────────┘\n```\n\n## The dedupe-key\n\n`dedupe_key = hash(thread_id + \"\\n\" + normalize(draft_text))`\n\n`normalize` = trim, collapse internal whitespace, lowercase. The key makes \"the\nsame reply to the same thread\" idempotent regardless of retries.\n\n## The ledger (duplicate-prevention backbone)\n\nAn append-only record, ideally persisted (survives restarts). One row per\n`attempted`:\n\n```json\n{\n  \"dedupe_key\": \"…\",\n  \"thread_id\": \"…\",\n  \"attempted_at\": \"ISO-8601\",\n  \"final_state\": \"confirmed | unconfirmed | failed\",\n  \"verified_at\": \"ISO-8601 | null\",\n  \"operator\": \"agent-id | human-id\"\n}\n```\n\nRules:\n- Write the row at `attempted`, **before** verifying. A crash between send and\n  verify must never look like \"never sent.\"\n- Before any new send, check the ledger for the dedupe-key:\n  - `confirmed` recently → STOP, already handled.\n  - `attempted`/`unconfirmed` present → STOP, do NOT resend; escalate.\n- If no persistent ledger exists, keep an in-session ledger and treat a restart\n  as \"state unknown → verify by reading the thread before any send.\"\n\n## Verify window\n\nRe-read the thread at **t+2s, t+5s, t+10s** after `attempted` (Airbnb UI can\nlag). Match by outbound text + a timestamp newer than the attempt. If still\nabsent after the last poll → `unconfirmed`. Do not extend indefinitely; do not\nresend.\n\nWhen the endpoint read is ambiguous (e.g., returns cached/empty), escalate the\n*verification* (not the send) to a DevTools read of the live thread DOM. Reading\nto verify is always allowed; it is never a second send.\n\n## Edge cases\n\n| Situation | Correct handling |\n|---|---|\n| Endpoint `sent:true`, message never appears | `unconfirmed`. Escalate. Never resend. |\n| Send timed out, unknown if it landed | `failed`. Re-read thread. If the text is now present → reclassify `confirmed` (record it). If absent → escalate; do NOT blind-resend. |\n| Two replies queued for one thread | Process serially; each gets its own dedupe-key and full machine. |\n| Guest sends a new message mid-verify | Does not affect the outbound verification; verify your own message only. |\n| Approval denied at the gate | Stay `drafted`. Discard or revise; never send. |\n\nFile v0.1.4:references/airbnb-safety-rules.md\n\n# Reference — Airbnb safety rules\n\nThe portable safety contract. Universal — do not weaken per deployment. The only\ndeployment-specific knob is the **approval policy** (who approves, and whether\napproval is required for sends).\n\n## Operation tiers\n\n| Tier | Definition | Examples | Default gate |\n|---|---|---|---|\n| **READ** | Cannot change anything a guest or the host can see. | check inbox, read thread, lookup reservation, booking summary, inspect calendar, verify a sent message | None — proceed. |\n| **WRITE** | Produces something a guest sees, but bounded & verifiable. | send a guest reply | Approval (if configured) **+ mandatory verify**. |\n| **MUTATE** | Affects money, availability, or listing state; effectively irreversible. | change price, block/open dates, edit listing, accept/decline a booking, issue a refund | **v1: refuse + escalate.** (v2: own gated workflow.) |\n\nClassify every operation into exactly one tier *before* acting. If an operation\nspans tiers, split it; never let a READ workflow quietly perform a WRITE.\n\n## Approval gate (WRITE)\n\n- Per-message, never standing. One approval authorizes exactly one send of one\n  draft to one thread.\n- Present to the approver: the target thread id, the last inbound guest message,\n  and the full draft text. Wait for explicit \"go\".\n- Deployment policy decides whether approval is *required*. Safe default:\n  required for all guest-facing sends. If disabled, verification is still\n  mandatory.\n\n## Irreversibility boundary\n\nTreat everything a guest can see, and anything touching money or availability,\nas irreversible in practice. You cannot reliably \"unsend\". This is why:\n- WRITE ops always verify after the fact.\n- MUTATE ops are refused in v1 rather than attempted optimistically.\n\n## Ambiguous state — the default is caution\n\n| Ambiguity | Action |\n|---|---|\n| Can't tell if a send landed | `unconfirmed`. Do NOT resend. Escalate. |\n| Two read paths disagree on a material fact (dates, guest count, price) | Report BOTH, flag the discrepancy, escalate. Do not silently pick one. |\n| Browser identity health unclear | Check the browser health role; if unhealthy, escalate rather than assuming. |\n| Ledger state unknown after restart | Verify by reading the live thread before any send. |\n\n## Escalate to a human when\n\n- A send reaches `unconfirmed` or `failed`.\n- You are asked to perform any MUTATE operation.\n- The host browser identity reports unhealthy.\n- Two read paths disagree on a material fact.\n- You would need to send the same message twice for any reason.\n- A required tool role for the requested operation is missing.\n\n## Escalation report shape\n\nWhen escalating, emit enough for a human to act without re-investigating:\n\n```\nESCALATION\n  op:            send_reply\n  thread_id:     <id>\n  reservation:   <id | none>\n  state:         unconfirmed\n  what happened: send endpoint returned sent:true; message not visible after\n                 t+2/5/10s re-reads.\n  draft:         \"<the exact text>\"\n  do NOT:        resend automatically.\n  human action:  open the thread, confirm whether the message is present; if\n                 absent, decide whether to re-send manually.\n```\n\n## Observability\n\nEvery operation — read or write — emits a structured status:\n\n```json\n{\n  \"op\": \"send_reply\",\n  \"tier\": \"WRITE\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"thread_id\": \"…\",\n  \"reservation_id\": \"… | null\",\n  \"state\": \"confirmed | unconfirmed | failed | ok\",\n  \"dedupe_key\": \"… | null\",\n  \"human_action_needed\": false\n}\n```\n\nA human should be able to reconstruct exactly what happened from the report\nstream alone.\n\nFile v0.1.4:references/airbnb-tool-priority.md\n\n# Reference — Airbnb tool priority & role mapping\n\n**This is the one file you customize per deployment.** It maps abstract tool\n*roles* (used everywhere else in the skill) to the concrete tool names in your\nenvironment. Everything else in the package is portable; edit here only.\n\n## Tier order (universal — do not reorder)\n\n1. **Airbnb endpoint** — first-class, structured, auth-aware. Default for every\n   supported operation.\n2. **Agent-browser** — navigate/inspect when no endpoint covers the op or an\n   endpoint is hard-down.\n3. **DevTools** — read-only DOM inspection / UI verification.\n4. **Playwright** — last-resort automation for read-only or human-approved ops.\n\n## Role → tool map (EDIT THIS for your deployment)\n\n> The names below are the reference deployment's tools. Replace with yours.\n> If a role has no tool in your environment, leave it blank — the skill will\n> degrade to the next available tier and escalate if a *required* role is empty.\n\n| Role | Reference deployment tool | Tier |\n|---|---|---|\n| Airbnb: list/read messages | `/tools/airbnb/messages` | 1 |\n| Airbnb: list reservations | `/tools/airbnb/reservations` | 1 |\n| Airbnb: read calendar | `/tools/airbnb/calendar` | 1 |\n| Airbnb: send message | `/tools/airbnb/messages/send` | 1 |\n| Browser: health/identity | `/tools/browser/status` | 2 |\n| Browser: navigate | `/tools/browser/navigate` | 2 |\n| DevTools: list tabs | `/tools/devtools/tabs` | 3 |\n| DevTools: navigate | `/tools/devtools/navigate` | 3 |\n| DevTools: evaluate (read-only) | `/tools/devtools/evaluate` | 3 |\n| Playwright: fallback | (deployment-specific path) | 4 |\n\n## When to drop a tier (decision rules)\n\n- **Stay on tier 1** unless: (a) no tier-1 tool exists for this operation, or\n  (b) a tier-1 call returns a *hard transport failure* (5xx / connection error /\n  timeout) **twice in a row**. A single slow or empty response is not a drop\n  trigger — retry once on tier 1 first.\n- **Before dropping for \"auth\" reasons**, call the browser health/identity role.\n  Airbnb auth is host-owned (provided by the host browser identity). Missing\n  *local* browser/session state is NOT evidence the account is logged out.\n- **DevTools is read-only in this skill.** Use `evaluate` to read/verify DOM,\n  never to click a send button. A send happens only via the tier-1 send role (or\n  an explicitly human-approved browser send).\n- **Playwright only** when tiers 1–3 are all unavailable for the operation AND\n  the operation is read-only or has explicit human approval. Log that you used\n  it and why.\n\n## Graceful degradation matrix\n\n| Operation | tier-1 absent → | also tier-2 absent → | all absent → |\n|---|---|---|---|\n| read inbox / thread | agent-browser read | DevTools DOM read | escalate (required role missing) |\n| read reservations | agent-browser read | DevTools DOM read | escalate |\n| read calendar | agent-browser read | DevTools DOM read | escalate |\n| send reply | human-approved browser send only | — | escalate; do NOT improvise |\n| verify sent | re-read via any read role | DevTools DOM read | mark `unconfirmed`, escalate |\n\nSending is special: there is no silent fallback for a send. If the tier-1 send\nrole is unavailable, the only alternative is an explicitly human-approved\nbrowser send, and verification is still mandatory.\n\nFile v0.1.4:references/future-adapter-interface.md\n\n# Reference — future adapter interface\n\nThis skill is the **behavioral contract**. A later, more formal adapter/tool\nlayer should harden the **mechanical guarantees** so correctness doesn't depend\non a model remembering a markdown rule. This file describes the adapter the\nskill is designed to pair with, and how the skill should call it if/when it\nexists.\n\n## Design principle\n\n> Judgment stays in the skill; guarantees move to the adapter.\n\n- **Skill (prompt layer):** when to escalate, how to summarize guest intent,\n  whether a state is ambiguous — model reasoning.\n- **Adapter (code layer):** send-exactly-once, the dedupe ledger, the verify\n  poll, tier fallback — properties you want *enforced*, not *hoped for*.\n\n## Ideal adapter functions\n\nThe skill should prefer these when present, and fall back to its manual\nprocedures when they are not.\n\n### `airbnb.sendVerified(thread_id, text, dedupe_key) → { state, visible_at, error? }`\nAtomically: dedupe-check → send-once → write ledger → verify-poll. Returns a\nfinal state from the same vocabulary (`confirmed | unconfirmed | failed`). This\nis the single most valuable function — it makes the cardinal \"no duplicate\nsends\" rule a code-level guarantee.\n\nWhen present: `send_reply` delegates entirely and just reports `state`.\nWhen absent: `send_reply` runs the manual state machine from\n`airbnb-message-state-machine.md`.\n\n### `airbnb.dedupeCheck(dedupe_key) → { already_sent, last_state, last_at }`\nLets the skill short-circuit before drafting if a reply already went out.\n\n### Read functions\n- `airbnb.listInbox(filter?) → Thread[]`\n- `airbnb.readThread(thread_id) → { messages[], last_inbound, ... }`\n- `airbnb.listReservations(filter?) → Reservation[]`\n- `airbnb.readCalendar(range) → CalendarSpan[]`\n\nThese mirror the read verbs and let the skill avoid tier-selection logic when\nthe adapter already owns it.\n\n### Future MUTATE functions (v2, each gated)\n- `airbnb.setCalendar(range, state, dedupe_key)` — block/open dates.\n- `airbnb.setPrice(range, amount, dedupe_key)` — pricing.\n- `airbnb.respondToBooking(reservation_id, decision, dedupe_key)` — accept/decline.\n\nEach MUTATE adapter function should follow the same shape as `sendVerified`:\ndo-once + ledger + verify, returning a final state. The skill wraps each in a\nread → propose → approve → call → report flow.\n\n## Capability detection\n\nThe skill should treat adapter functions as **optional**: detect availability,\nprefer them, and degrade to manual procedures otherwise. Pseudocode:\n\n```\nif has(airbnb.sendVerified):\n    result = airbnb.sendVerified(thread_id, text, dedupe_key)\n    report(result.state)\nelse:\n    run_manual_send_state_machine()   # per airbnb-message-state-machine.md\n```\n\n## Why both layers (defense in depth)\n\n- The **adapter** prevents a duplicate send even if an agent misbehaves.\n- The **skill** explains *why* and handles the judgment the adapter can't encode\n  (intent, tone, when to escalate).\n\nShipping the skill first is correct: it makes the contract usable today. The\nadapter promotes the riskiest guarantees into code as the deployment matures.\n\nFile v0.1.4:CHANGELOG.md\n\n# Changelog\n\nAll notable changes to the `airbnb-gateway` skill package. Format follows\n[Keep a Changelog](https://keepachangelog.com/); versions follow SemVer.\n\n## [Unreleased]\n\n### Changed\n- v0.1.4: Added a friendly \"star this skill\" call-to-action to `SKILL.md`,\n  `README.md`, and `CLAWHUB.md` (shown on the ClawHub listing) — content-only,\n  no behavior change.\n- v0.1.3: Clean re-cut for review handoff — no content or doctrine change since\n  v0.1.2; published to give the reviewing agent a fresh version number to pin\n  its install/audit against.\n- v0.1.2: Pre-install polish pass (no doctrine change). Replaced the\n  \"Capabilities this skill expects\" section with an explicit **Minimum\n  Environment Contract** (read-only minimum vs send-capable minimum vs optional\n  enhancements) so outside users can tell at a glance whether the skill can run.\n  Converted the **Command surface** table to a renderer-safe bullet list (the\n  `<id>`/`<thread>` placeholders rendered as collapsed on some markdown\n  platforms). Verified package completeness: all `references/` and `examples/`\n  files ship in the published artifact — the ClawHub web page previews only\n  `SKILL.md`, but installs are complete.\n- v0.1.1: Rewrote the `SKILL.md` frontmatter `description` to match the ClawHub\n  listing blurb (`CLAWHUB.md`), so the published summary reads in the intended\n  voice instead of the long internal one-liner.\n\n### Added\n- Initial `airbnb-gateway` skill package (v0.1.0):\n  - `SKILL.md` — operating contract: Five Laws, tier-based operating model,\n    safety tiers, send state machine, command surface, anti-patterns,\n    future-adapter section, maintainer notes.\n  - `references/airbnb-message-state-machine.md` — full send state machine,\n    dedupe key, ledger contract, verify window, edge cases.\n  - `references/airbnb-tool-priority.md` — the one per-deployment file: tier\n    order + role→tool map + degradation matrix.\n  - `references/airbnb-safety-rules.md` — READ/WRITE/MUTATE tiers, approval gate,\n    ambiguity handling, escalation report shape, observability.\n  - `references/future-adapter-interface.md` — ideal adapter functions and how\n    the skill should call them when present.\n  - `examples/` — check-inbox, read-thread, send-reply-with-verification\n    (incl. the no-resend `unconfirmed` path), reservation-lookup,\n    calendar-inspection.\n  - `state/send-log.schema.json` — append-only dedupe ledger schema.\n  - `README.md`, `LICENSE` (MIT) — Open Hub-friendly packaging.\n  - `CLAWHUB.md` — verbatim ClawHub/Open Hub listing description.\n\n### Notes\n- Scope is read operations + verified single-send. Calendar/pricing/listing\n  mutations are intentionally refused (escalate) until v2.\n\nFile v0.1.4:CLAWHUB.md\n\n# airbnb-gateway\n\n`airbnb-gateway` is a skill for safe, coherent Airbnb operations in OpenClaw-style agent environments.\n\nIt standardizes how agents:\n- check inbox threads\n- inspect reservations and booking state\n- review calendar context\n- draft guest replies\n- send messages safely\n- verify whether a send actually appeared in the live Airbnb thread\n\nThe skill teaches a strict operating model:\n- prefer Airbnb-native endpoints before generic browser automation\n- treat send acknowledgments as `attempted`, not automatically `confirmed`\n- verify outbound messages in the live thread UI before declaring success\n- never auto-resend from ambiguous or unconfirmed state\n\n`airbnb-gateway` is designed to reduce duplicate messages, normalize agent behavior, and provide a safer foundation for Airbnb messaging, bookings, and calendar workflows.\n\nIt works well as:\n- a standalone operational skill today\n- a future companion to a more formal Airbnb adapter/tool layer tomorrow\n\n---\n\n⭐ **Find this useful?** If `airbnb-gateway` saves you time, please **star it** (the ⭐ at the top of this ClawHub page) — stars help other rental operators discover it and keep it maintained. Thank you!\n\nFile v0.1.4:examples/calendar-inspection.md\n\n# Example — `inspect_calendar`\n\nA READ operation. Report calendar state and **flag** high-risk patterns. In v1\nthe skill never changes the calendar — flagging is read-only; fixing is a MUTATE\nop reserved for v2.\n\n## Procedure\n1. Tier 1: call the Airbnb calendar role for the requested range (default: next\n   60 days).\n2. Classify each day/span: `booked`, `blocked`, `open`.\n3. Run risk checks (flag, don't fix):\n   - **Double-booking:** two reservations overlapping the same night.\n   - **Anomalous price:** a night priced far outside the range's norm.\n   - **Risky back-to-back:** check-out and next check-in same day with no buffer\n     for turnover.\n   - **Unexpected open gap:** open nights between two bookings that usually fill.\n4. Emit state + a `flags` array. Recommend nothing irreversible.\n\n## Reference-deployment call\n```\nGET /tools/airbnb/calendar?from=2026-06-03&to=2026-08-02     # tier 1\n```\n\n## Sample output\n```json\n{\n  \"op\": \"inspect_calendar\",\n  \"tier\": \"READ\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"range\": { \"from\": \"2026-06-03\", \"to\": \"2026-08-02\" },\n  \"spans\": [\n    { \"from\":\"2026-06-06\", \"to\":\"2026-06-09\", \"state\":\"booked\", \"reservation_id\":\"r_5521\" },\n    { \"from\":\"2026-06-09\", \"to\":\"2026-06-11\", \"state\":\"open\" },\n    { \"from\":\"2026-06-11\", \"to\":\"2026-06-14\", \"state\":\"booked\", \"reservation_id\":\"r_5540\" }\n  ],\n  \"flags\": [\n    { \"type\":\"back_to_back\", \"date\":\"2026-06-09\",\n      \"detail\":\"r_5521 check-out and r_5540-adjacent open night; verify turnover time\" }\n  ],\n  \"human_action_needed\": false\n}\n```\n\n## When a flag is serious\nIf a flag implies guest-visible risk (e.g., an actual double-booking), escalate\nto a human immediately with the specifics. Do NOT attempt to resolve it by\nchanging the calendar — that's a MUTATE op and is out of scope in v1.\n\n## Degradation\n- Tier-1 calendar role missing/hard-down twice → agent-browser read of the\n  calendar page → DevTools DOM read → escalate.\n\n## Anti-patterns\n- ❌ \"Fixing\" a double-booking by blocking dates. Flag + escalate only in v1.\n- ❌ Reporting calendar state from a stale cache without noting it; if the read\n  path is ambiguous, say so.\n\nFile v0.1.4:examples/check-inbox.md\n\n# Example — `check_inbox`\n\nA READ operation. No approval, no write. Goal: surface threads needing attention,\nprioritized, without changing anything.\n\n> Tool names below are from the reference deployment. Map them via\n> `references/airbnb-tool-priority.md`.\n\n## Procedure\n1. Tier 1: call the Airbnb messages-list role.\n2. For each thread, note: `thread_id`, guest name, last-message direction\n   (inbound/outbound), last-message time, unread flag, linked reservation if\n   available.\n3. Prioritize: unread inbound > inbound awaiting reply > everything else.\n   Within a bucket, sort by most recent.\n4. Emit a structured summary. Do not open/draft/send anything.\n\n## Reference-deployment call\n\n```\nGET /tools/airbnb/messages          # tier 1\n```\n\n## Sample output\n\n```json\n{\n  \"op\": \"check_inbox\",\n  \"tier\": \"READ\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"threads\": [\n    { \"thread_id\": \"t_88213\", \"guest\": \"Marco R.\", \"last\": \"inbound\",\n      \"at\": \"2026-06-03T14:22:00Z\", \"unread\": true, \"reservation_id\": \"r_5521\",\n      \"snippet\": \"Hi! Is early check-in possible on Friday?\" },\n    { \"thread_id\": \"t_88190\", \"guest\": \"Dana P.\", \"last\": \"outbound\",\n      \"at\": \"2026-06-03T09:10:00Z\", \"unread\": false, \"reservation_id\": \"r_5498\",\n      \"snippet\": \"Thanks, enjoy your stay!\" }\n  ],\n  \"needs_attention\": [\"t_88213\"],\n  \"human_action_needed\": false\n}\n```\n\n## Degradation\n- Tier-1 list role missing or hard-down twice → agent-browser navigate to the\n  inbox and read the rendered list (still READ). If that's also unavailable →\n  DevTools DOM read. If all absent → escalate (required read role missing).\n\n## Anti-patterns\n- ❌ Auto-drafting replies during an inbox check. `check_inbox` is read-only;\n  drafting is a separate, explicit step.\n\nFile v0.1.4:examples/read-thread.md\n\n# Example — `read_thread`\n\nA READ operation. Open one thread, read the live messages, summarize guest\nintent. No drafting, no sending.\n\n## Procedure\n1. Tier 1: call the Airbnb thread-read role for `thread_id`.\n2. Capture the full message list in order; identify the **last inbound** guest\n   message (this is what a reply would answer).\n3. Summarize guest intent in one line (question / request / complaint / logistics\n   / smalltalk).\n4. Map to a reservation if a link is available (helps later replies cite dates).\n5. Emit a structured summary.\n\n## Reference-deployment call\n\n```\nGET /tools/airbnb/messages?thread_id=t_88213     # tier 1\n```\n\n## Sample output\n\n```json\n{\n  \"op\": \"read_thread\",\n  \"tier\": \"READ\",\n  \"path_used\": \"airbnb-endpoint\",\n  \"thread_id\": \"t_88213\",\n  \"reservation_id\": \"r_5521\",\n  \"last_inbound\": {\n    \"at\": \"2026-06-03T14:22:00Z\",\n    \"text\": \"Hi! Is early check-in possible on Friday? We land at 10am.\"\n  },\n  \"intent\": \"request: early check-in on Friday (arrival ~10am)\",\n  \"messages_count\": 6,\n  \"human_action_needed\": false\n}\n```\n\n## Verification re-use\nThe same read path is what `verify_sent` and the send state machine use to\nconfirm an outbound message. Reading a thread is always safe and never counts as\na send.\n\n## Degradation\n- Tier-1 read missing/hard-down twice → agent-browser navigate to the thread and\n  read rendered messages → DevTools DOM read → escalate.\n- If two read paths disagree on dates/guest count, report both and flag (see\n  `references/airbnb-safety-rules.md`).\n\n## Anti-patterns\n- ❌ Summarizing intent from the inbox snippet without opening the thread —\n  snippets truncate and you may miss the actual ask.\n\nArchive v0.1.3: 17 files, 26164 bytes\n\nFiles: CHANGELOG.md (2549b), CLAWHUB.md (973b), examples/calendar-inspection.md (2145b), examples/check-inbox.md (1745b), examples/read-thread.md (1678b), examples/reservation-lookup.md (2158b), examples/send-reply-with-verification.md (2886b), LICENSE (1070b), README.md (2991b), references/airbnb-message-state-machine.md (4677b), references/airbnb-safety-rules.md (3607b), references/airbnb-tool-priority.md (3317b), references/future-adapter-interface.md (3134b), skill-card.md (3235b), SKILL.md (12521b), state/send-log.schema.json (1642b), _meta.json (133b)\n\nFile v0.1.3:SKILL.md\n\n---\nname: airbnb-gateway\ndescription: >\n  A skill for safe, coherent Airbnb operations in OpenClaw-style agent\n  environments. It standardizes how agents check inbox threads, inspect\n  reservations and booking state, review calendar context, draft guest replies,\n  send messages safely, and verify whether a send actually appeared in the live\n  Airbnb thread. It teaches a strict operating model: prefer Airbnb-native\n  endpoints before generic browser automation; treat send acknowledgments as\n  attempted, not automatically confirmed; verify outbound messages in the live\n  thread UI before declaring success; and never auto-resend from ambiguous or\n  unconfirmed state. Designed to reduce duplicate messages, normalize agent\n  behavior, and provide a safer foundation for Airbnb messaging, bookings, and\n  calendar workflows — useful standalone today and as a companion to a more\n  formal Airbnb adapter/tool layer tomorrow.\nversion: 0.1.3\nrisk: write-gated\ntags: [airbnb, vacation-rental, messaging, operations, openclaw]\nlicense: MIT\nhomepage: \"\"\nmaintainer: \"\"\n---\n\n# airbnb-gateway\n\nYou are operating a live, revenue-bearing Airbnb account on behalf of a real\nhost. Guests are real people. A duplicate message, a wrong send, or a careless\ncalendar change has real consequences. This skill exists so that **every agent\nhandles Airbnb identically and safely**, instead of improvising tool calls per\nturn.\n\n> **Portability note.** This skill is environment-agnostic. It refers to tool\n> *roles* (an \"Airbnb messages endpoint\", an \"agent-browser\", a \"DevTools\n> bridge\", a \"Playwright fallback\") rather than hard-coded URLs. Your concrete\n> tool names live in `references/airbnb-tool-priority.md` — edit that one file\n> to map roles to the actual tools in your deployment. Everything else here is\n> universal.\n\n---\n\n## THE FIVE LAWS (non-negotiable)\n\n1. **Platform-first.** Always try the first-class Airbnb endpoint for an\n   operation before any browser automation. Browser weirdness is NOT evidence\n   that Airbnb access is down.\n2. **Read before write.** Never send a reply without first reading the live\n   thread in the *same* operation.\n3. **`sent: true` is `attempted`, not `confirmed`.** An endpoint success proves\n   the call returned — not that the guest can see the message. You MUST re-read\n   the thread and SEE the outbound message before marking `confirmed`.\n4. **Never auto-resend an `unconfirmed` send.** Duplicate guest messages are\n   worse than a late reply. Escalate to a human instead.\n5. **Writes gate; mutations stop.** Sending a message is approval-gated.\n   Pricing, availability, listing edits, and accept/decline are NOT implemented\n   in v1 — refuse and escalate.\n\nIf you are ever unsure, STOP and report. A paused agent is recoverable; a\nduplicated or wrong guest message is not.\n\n---\n\n## Minimum Environment Contract\n\nUse this to decide, before installing, whether your deployment can run the skill\nand at what level. Map each role to a real tool in\n`references/airbnb-tool-priority.md`. The skill degrades gracefully: if a\n**read-only** requirement is met but a **send** requirement is not, the skill\nruns in read-only mode and refuses sends rather than improvising.\n\n**Read-only mode — minimum to run safely (inbox / threads / reservations / calendar):**\n- At least one **Airbnb read path** — list/read messages, reservations, and\n  calendar (first-class endpoint preferred; agent-browser or DevTools read\n  acceptable as fallback).\n\nThat's the entire requirement for every READ verb. Nothing can be sent in this\nmode.\n\n**Send-capable mode — additional minimum to send a guest reply:**\n- An **Airbnb send path** — the first-class send endpoint (preferred) OR an\n  explicitly human-approved browser send.\n- A **thread re-read path** — to verify the outbound message after sending.\n  (The same read path from read-only mode satisfies this.)\n\nIf either is missing, do NOT send: st\n\nArchive v0.1.2: 17 files, 26198 bytes\n\nFiles: CHANGELOG.md (2360b), CLAWHUB.md (973b), examples/calendar-inspection.md (2145b), examples/check-inbox.md (1745b), examples/read-thread.md (1678b), examples/reservation-lookup.md (2158b), examples/send-reply-with-verification.md (2886b), LICENSE (1070b), README.md (2991b), references/airbnb-message-state-machine.md (4677b), references/airbnb-safety-rules.md (3607b), references/airbnb-tool-priority.md (3317b), references/future-adapter-interface.md (3134b), skill-card.md (3518b), SKILL.md (12521b), state/send-log.schema.json (1642b), _meta.json (133b)\n\nArchive v0.1.1: 17 files, 25403 bytes\n\nFiles: CHANGELOG.md (1711b), CLAWHUB.md (973b), examples/calendar-inspection.md (2145b), examples/check-inbox.md (1745b), examples/read-thread.md (1678b), examples/reservation-lookup.md (2158b), examples/send-reply-with-verification.md (2886b), LICENSE (1070b), README.md (2991b), references/airbnb-message-state-machine.md (4677b), references/airbnb-safety-rules.md (3607b), references/airbnb-tool-priority.md (3317b), references/future-adapter-interface.md (3134b), skill-card.md (3027b), SKILL.md (11756b), state/send-log.schema.json (1642b), _meta.json (133b)\n\nArchive v0.1.0: 17 files, 25012 bytes\n\nFiles: CHANGELOG.md (1491b), CLAWHUB.md (973b), examples/calendar-inspection.md (2145b), examples/check-inbox.md (1745b), examples/read-thread.md (1678b), examples/reservation-lookup.md (2158b), examples/send-reply-with-verification.md (2886b), LICENSE (1070b), README.md (2991b), references/airbnb-message-state-machine.md (4677b), references/airbnb-safety-rules.md (3607b), references/airbnb-tool-priority.md (3317b), references/future-adapter-interface.md (3134b), skill-card.md (2717b), SKILL.md (11406b), state/send-log.schema.json (1642b), _meta.json (133b)","readmeExcerpt":"Skill: Airbnb Gateway Owner: jason-vaughan Summary: A skill for safe, coherent Airbnb operations in OpenClaw-style agent environments. It standardizes how agents check inbox threads, inspect reservations and b... Tags: airbnb:0.1.4, latest:0.2.1, messaging:0.1.4, operations:0.1.4, vacation-rental:0.1.4 Version history: v0.2.1 | 2026-07-09T21:20:40.243Z | user v0.2.1 — doc-consistency + safety patch (resolves SkillSpe","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"OPERATION\n  │\n  ├─ 1. AIRBNB ENDPOINT  (first-class, structured, auth-aware)\n  │       → DEFAULT for every supported operation. Always start here.\n  │\n  ├─ 2. AGENT-BROWSER  (navigate / status)\n  │       → ONLY when no endpoint covers the op, OR an endpoint returned a hard\n  │         transport failure (5xx/timeout) TWICE. First confirm the host\n  │         browser identity is alive before assuming auth loss.\n  │\n  ├─ 3. DEVTOOLS  (tabs / navigate / evaluate — READ-ONLY here)\n  │       → DOM inspection / UI verification when the endpoint read is\n  │         ambiguous. Never use evaluate to perform a write/click-send.\n  │\n  └─ 4. PLAYWRIGHT  (fallback)\n          → LAST RESORT. Only when 1–3 are unavailable AND the op is read-only\n            or an explicitly human-approved write."},{"language":"text","snippet":"[drafted]      reply composed; thread + dedupe-key recorded\n   │           (WRITE gate: get approval if required)\n   ▼\n[attempted]    ← send endpoint called EXACTLY ONCE; ledger written NOW\n   │             endpoint sent:true  →  ATTEMPTED, not done\n   │\n   ├─ verify: re-read the SAME thread (endpoint; DevTools if ambiguous)\n   │\n   ├─ outbound visibly present?  ── yes ──► [confirmed]   ✅ report\n   ├─ absent after verify window ── no  ──► [unconfirmed] ⚠️ DO NOT RESEND, escalate\n   └─ send call errored                  ► [failed]       ❌ re-read first, escalate, no blind resend"},{"language":"text","snippet":"airbnb-gateway/\n├── SKILL.md                              # the operating contract (start here)\n├── README.md                             # this file\n├── CHANGELOG.md\n├── LICENSE\n├── references/\n│   ├── airbnb-tool-priority.md           # ← customize per deployment\n│   ├── airbnb-message-state-machine.md   # universal\n│   ├── airbnb-safety-rules.md            # universal\n│   └── future-adapter-interface.md       # how to pair with a code adapter later\n├── examples/\n│   ├── check-inbox.md\n│   ├── read-thread.md\n│   ├── send-reply-with-verification.md   # the critical path\n│   ├── reservation-lookup.md\n│   └── calendar-inspection.md\n└── state/\n    └── send-log.schema.json              # append-only dedupe ledger schema"},{"language":"text","snippet":"approval (if required)              send endpoint\n  drafted ───────────────────────► (ready) ───── exactly once ─────► attempted\n                                                                         │\n                                              re-read same thread        │\n                                          ┌──────────────────────────────┤\n                                          │                              │\n                            visible ◄─────┘                              └─────► call errored\n                               │                                                     │\n                               ▼                                                     ▼\n                          confirmed ✅                                            failed ❌\n                                                                                     │\n                            absent after verify window                               │\n                               │                                                     │\n                               ▼                                                     ▼\n                         unconfirmed ⚠️  ◄──────── (treat like) ──────────── re-read thread\n                               │                                                     │\n                               └──────────► ESCALATE. No automatic resend. ◄─────────┘"},{"language":"json","snippet":"{\n  \"dedupe_key\": \"…\",\n  \"thread_id\": \"…\",\n  \"attempted_at\": \"ISO-8601\",\n  \"final_state\": \"confirmed | unconfirmed | failed\",\n  \"verified_at\": \"ISO-8601 | null\",\n  \"operator\": \"agent-id | human-id\"\n}"},{"language":"text","snippet":"ESCALATION\n  op:            send_reply\n  thread_id:     <id>\n  reservation:   <id | none>\n  state:         unconfirmed\n  what happened: send endpoint returned sent:true; message not visible after\n                 t+2/5/10s re-reads.\n  draft:         \"<the exact text>\"\n  do NOT:        resend automatically.\n  human action:  open the thread, confirm whether the message is present; if\n                 absent, decide whether to re-send manually."}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: airbnb-gateway\ndescription: >\n  A skill for safe, coherent Airbnb operations in OpenClaw-style agent\n  environments. It standardizes how agents check inbox threads, inspect\n  reservations and booking state, review calendar context, draft guest replies,\n  send messages safely, verify whether a send actually appeared in the live\n  Airbnb thread, and make operator-approved, per-operation, independently-verified\n  calendar mutations (block/open dates, nightly price). It teaches a strict operating model: prefer Airbnb-native\n  endpoints before generic browser automation; treat send acknowledgments as\n  attempted, not automatically confirmed; verify outbound messages in the live\n  thread UI before declaring success; and never auto-resend from ambiguous or\n  unconfirmed state. Designed to reduce duplicate messages, normalize agent\n  behavior, and provide a safer foundation for Airbnb messaging, bookings, and\n  calendar workflows — useful standalone today and as a companion to a more\n  formal Airbnb adapter/tool layer tomorrow.\nversion: 0.2.1\nrisk: write-gated\ntags: [airbnb, vacation-rental, messaging, operations, openclaw]\nlicense: MIT\nhomepage: \"\"\nmaintainer: \"\"\n---\n\n# airbnb-gateway\n\n> ⭐ **Find this skill useful?** If `airbnb-gateway` saves you time, please\n> **star it** (the ⭐ at the top of this ClawHub page) — stars help other rental\n> operators discover it and keep it maintained. Thank you!\n\nYou are operating a live, revenue-bearing Airbnb account on behalf of a real\nhost. Guests are real people. A duplicate message, a wrong send, or a careless\ncalendar change has real consequences. This skill exists so that **every agent\nhandles Airbnb identically and safely**, instead of improvising tool calls per\nturn.\n\n> **Portability note.** This skill is environment-agnostic. It refers to tool\n> *roles* (an \"Airbnb messages endpoint\", an \"agent-browser\", a \"DevTools\n> bridge\", a \"Playwright fallback\") rather than hard-coded URLs. Your concrete\n> tool names live in `references/airbnb-tool-priority.md` — edit that one file\n> to map roles to the actual tools in your deployment. Everything else here is\n> universal.\n\n---\n\n## THE FIVE LAWS (non-negotiable)\n\n1. **Platform-first.** Always try the first-class Airbnb endpoint for an\n   operation before any browser automation. Browser weirdness is NOT evidence\n   that Airbnb access is down.\n2. **Read before write.** Never send a reply without first reading the live\n   thread in the *same* operation.\n3. **`sent: true` is `attempted`, not `confirmed`.** An endpoint success proves\n   the call returned — not that the guest can see the message. You MUST re-read\n   the thread and SEE the outbound message before marking `confirmed`.\n4. **Never auto-resend an `unconfirmed` send.** Duplicate guest messages are\n   worse than a late reply. Escalate to a human instead.\n5. **Writes gate; mutations gate harder.** Sending a message is approval-gated.\n   Calendar mutations (nightly price, block/open dates) are permitted ONLY "},{"path":"README.md","content":"# airbnb-gateway\n\nA reusable OpenClaw / Codex-style **skill package** for safe, coherent,\nend-to-end Airbnb host operations: inbox checks, thread reading, reservation\nlookup, booking summaries, calendar inspection, draft replies, **verified**\nmessage sending, and disciplined escalation.\n\n> ⭐ **Find this useful?** If `airbnb-gateway` saves you time, please **star it on ClawHub** — stars help other operators discover it and keep it maintained. Thank you!\n\nIt does not add transport. It orchestrates whatever Airbnb tooling your\nenvironment already has — first-class Airbnb endpoints, agent-browser, DevTools,\nPlaywright — behind one consistent operating model so multiple agents behave\nidentically and never duplicate a guest message.\n\n## Why this exists\n\nTwo reliability lessons are baked into the design:\n\n1. **Browser weirdness ≠ Airbnb down.** Auth is host-owned; prefer platform-aware\n   endpoints before generic browser automation.\n2. **`sent: true` ≠ delivered.** A send is only `confirmed` after re-reading the\n   live thread and *seeing* the message. Endpoint success is just `attempted`,\n   and an `unconfirmed` send is **never** auto-resent.\n\n## Install / use\n\n1. Drop `skills/airbnb-gateway/` into your skills library.\n2. Edit **`references/airbnb-tool-priority.md`** — map the abstract tool roles to\n   the real tool names in your deployment. This is the only required\n   customization.\n3. (Optional) Set your approval policy and wire a persistent send ledger.\n4. Point your agents at the skill. They should speak only in the command verbs\n   (`check_inbox`, `read_thread`, `send_reply`, …) and never call low-level\n   Airbnb tools directly.\n\n## What's portable vs. deployment-specific\n\n| Portable (don't fork) | Deployment-specific (customize) |\n|---|---|\n| The Five Laws | role → tool name map |\n| Send state machine | approval policy |\n| Safety tiers (READ/WRITE/MUTATE) | persistent ledger wiring |\n| Command vocabulary | example payload shapes |\n\n## Layout\n\n```\nairbnb-gateway/\n├── SKILL.md                              # the operating contract (start here)\n├── README.md                             # this file\n├── CHANGELOG.md\n├── LICENSE\n├── references/\n│   ├── airbnb-tool-priority.md           # ← customize per deployment\n│   ├── airbnb-message-state-machine.md   # universal\n│   ├── airbnb-safety-rules.md            # universal\n│   └── future-adapter-interface.md       # how to pair with a code adapter later\n├── examples/\n│   ├── check-inbox.md\n│   ├── read-thread.md\n│   ├── send-reply-with-verification.md   # the critical path\n│   ├── reservation-lookup.md\n│   └── calendar-inspection.md\n└── state/\n    └── send-log.schema.json              # append-only dedupe ledger schema\n```\n\n## Status\n\nv0.2.x — read operations, verified single-send, and **approval-gated calendar\nmutations** (block/open dates, nightly price) under an explicit per-operation\napproval gate with mandatory fresh-load verification (tier MUTATE-CAL). Listing\nedits, accept/decline, and refunds rema"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn77gtjzdkfywnsbs5f349wjxd87hb0x\",\n  \"slug\": \"airbnb-gateway\",\n  \"version\": \"0.2.1\",\n  \"publishedAt\": 1783632040243\n}"},{"path":"references/airbnb-message-state-machine.md","content":"# Reference — Airbnb message send state machine\n\nThis is the authoritative definition of the send discipline. `SKILL.md` carries\nthe operational summary; this file carries the full machine, timings, and edge\ncases. **This logic is universal — do not fork it per deployment.**\n\n## States\n\n| State | Meaning | Terminal? |\n|---|---|---|\n| `drafted` | Reply text composed, thread + dedupe-key recorded. Nothing sent yet. | no |\n| `attempted` | Send endpoint called exactly once and returned. **Delivery NOT proven.** | no |\n| `confirmed` | Outbound message verified visibly present in the live thread. | yes ✅ |\n| `unconfirmed` | Send returned but the message could not be seen after the verify window. | yes ⚠️ |\n| `failed` | The send call itself errored (4xx/5xx/timeout). | yes ❌ |\n\n## Transitions\n\n```\n            approval (if required)              send endpoint\n  drafted ───────────────────────► (ready) ───── exactly once ─────► attempted\n                                                                         │\n                                              re-read same thread        │\n                                          ┌──────────────────────────────┤\n                                          │                              │\n                            visible ◄─────┘                              └─────► call errored\n                               │                                                     │\n                               ▼                                                     ▼\n                          confirmed ✅                                            failed ❌\n                                                                                     │\n                            absent after verify window                               │\n                               │                                                     │\n                               ▼                                                     ▼\n                         unconfirmed ⚠️  ◄──────── (treat like) ──────────── re-read thread\n                               │                                                     │\n                               └──────────► ESCALATE. No automatic resend. ◄─────────┘\n```\n\n## The dedupe-key\n\n`dedupe_key = hash(thread_id + \"\\n\" + normalize(draft_text))`\n\n`normalize` = trim, collapse internal whitespace, lowercase. The key makes \"the\nsame reply to the same thread\" idempotent regardless of retries.\n\n## The ledger (duplicate-prevention backbone)\n\nAn append-only record, ideally persisted (survives restarts). One row per\n`attempted`:\n\n```json\n{\n  \"dedupe_key\": \"…\",\n  \"thread_id\": \"…\",\n  \"attempted_at\": \"ISO-8601\",\n  \"final_state\": \"confirmed | unconfirmed | failed\",\n  \"verified_at\": \"ISO-8601 | null\",\n  \"operator\": \"agent-id | human-id\"\n}\n```\n\nRules:\n- Write the row at `attempted`, **before** verifying. A crash between send and\n  verify must never look like \"never sent.\"\n- Before any new send, check the ledger for the dedupe-key:\n  - `confirmed"},{"path":"references/airbnb-safety-rules.md","content":"# Reference — Airbnb safety rules\n\nThe portable safety contract. Universal — do not weaken per deployment. The only\ndeployment-specific knob is the **approval policy** (who approves, and whether\napproval is required for sends).\n\n## Operation tiers\n\n| Tier | Definition | Examples | Default gate |\n|---|---|---|---|\n| **READ** | Cannot change anything a guest or the host can see. | check inbox, read thread, lookup reservation, booking summary, inspect calendar, verify a sent message | None — proceed. |\n| **WRITE** | Produces something a guest sees, but bounded & verifiable. | send a guest reply | Approval (if configured) **+ mandatory verify**. |\n| **MUTATE-CAL** | Affects availability or pricing on the live listing; hard to reverse. | change nightly price, block/open calendar dates | **Explicit per-operation operator approval** (APPROVED keyword, one op per approval) **+ mandatory fresh-load verify + reported inverse.** See `calendar-mutation-procedure.md`. |\n| **MUTATE-RESTRICTED** | Affects listing content, booking decisions, or money movement; effectively irreversible. | edit listing, accept/decline a booking, issue a refund, payouts | **Not implemented — refuse + escalate.** (Each would get its own gated workflow before ever being enabled.) |\n\nClassify every operation into exactly one tier *before* acting. If an operation\nspans tiers, split it; never let a READ workflow quietly perform a WRITE.\n\n## Approval gate (WRITE)\n\n- Per-message, never standing. One approval authorizes exactly one send of one\n  draft to one thread.\n- Present to the approver: the target thread id, the last inbound guest message,\n  and the full draft text. Wait for explicit \"go\".\n- Deployment policy decides whether approval is *required*. Safe default:\n  required for all guest-facing sends. If disabled, verification is still\n  mandatory.\n\n## Irreversibility boundary\n\nTreat everything a guest can see, and anything touching money or availability,\nas irreversible in practice. You cannot reliably \"unsend\". This is why:\n- WRITE ops always verify after the fact.\n- MUTATE-CAL ops (calendar/price) run only under explicit per-operation approval\n  and are fresh-load verified after — never attempted optimistically.\n- MUTATE-RESTRICTED ops (listing edits, accept/decline, refunds, payouts) are\n  refused outright rather than attempted.\n\n## Ambiguous state — the default is caution\n\n| Ambiguity | Action |\n|---|---|\n| Can't tell if a send landed | `unconfirmed`. Do NOT resend. Escalate. |\n| Two read paths disagree on a material fact (dates, guest count, price) | Report BOTH, flag the discrepancy, escalate. Do not silently pick one. |\n| Browser identity health unclear | Check the browser health role; if unhealthy, escalate rather than assuming. |\n| Ledger state unknown after restart | Verify by reading the live thread before any send. |\n\n## Escalate to a human when\n\n- A send reaches `unconfirmed` or `failed`.\n- You are asked to perform any MUTATE-RESTRICTED operation, or a MUTATE-CAL\n  opera"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1897,"uniquenessScore":44,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T07:49:16.466Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T07:49:16.466Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T10:52:48.678Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}