{"id":"42f2aae3-fdeb-4ea2-bfa7-1f01bb0afb75","entityType":"agent","slug":"clawhub-lakshmanturlapati-full-selfbrowsing","name":"FSB Skill","canonicalUrl":"https://www.xpersona.co/agent/clawhub-lakshmanturlapati-full-selfbrowsing","canonicalPath":"/agent/clawhub-lakshmanturlapati-full-selfbrowsing","generatedAt":"2026-10-14T17:02:14.766Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-12T00:03:38.230Z","emptyReason":null},"description":"FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks. Skill: FSB Skill Owner: lakshmanturlapati Summary: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks. Tags: latest:0.9.90 Version history: v0.9.90 | 2026-07-06T09:32:34.087Z | user - Added Hermes support: badge label \"Hermes\" is now allowed for action calls, with updated documentation. - Expanded platform list to include macOS, Linux, and Windows. - New scripts and references: -","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 971 downloads reported by the source. Last updated 10/12/2026.","installCommand":"clawhub skill install s1752k5z45ca6g998dk2zdqk5h86appw:full-selfbrowsing","sourceUrl":"https://clawhub.ai/lakshmanturlapati/full-selfbrowsing","homepage":"https://clawhub.ai/lakshmanturlapati/skills/full-selfbrowsing","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/lakshmanturlapati/full-selfbrowsing","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/lakshmanturlapati/skills/full-selfbrowsing","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks. Skill: FSB Skill Owner: lakshmanturlapati Summary: FSB drives the user'"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-12T00:03:38.230Z","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-12T00:03:38.230Z","emptyReason":null},"stars":null,"forks":null,"downloads":971,"likes":null,"task":null,"library":null,"packageName":null,"latestVersion":"0.9.90","tractionLabel":"971 downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-12T00:03:38.215Z","emptyReason":null},"lastUpdatedAt":"2026-10-12T00:03:38.230Z","lastCrawledAt":"2026-10-12T00:03:38.215Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-13T00:03:38.215Z","lastVerifiedAt":null,"highlights":[{"version":"0.9.90","createdAt":"2026-07-06T09:32:34.087Z","changelog":"- Added Hermes support: badge label \"Hermes\" is now allowed for action calls, with updated documentation. - Expanded platform list to include macOS, Linux, and Windows. - New scripts and references: - Added `scripts/print-hermes-yaml.mjs` for Hermes configuration. - Included `references/hermes-tool-prefix.md` and `references/v0.9.62-contract-mirror.md` for Hermes integration and contract reference. - Updated install and config instructions for both OpenClaw and Hermes. - Clarified confirmation protocol for sensitive actions and added Hermes-specific field-bundle notes. - Removed deprecated `skill-card.md` file.","fileCount":18,"zipByteSize":39668},{"version":"0.9.62","createdAt":"2026-05-12T09:04:42.331Z","changelog":"ClawHub review fixes (ASI02/03/04/05) + frontmatter v0.9.62 bump","fileCount":15,"zipByteSize":32594},{"version":"0.9.61","createdAt":"2026-05-08T17:42:47.020Z","changelog":"- Updated SKILL.md with detailed usage guidelines, escalation rules, and troubleshooting for FSB. - Clarified when to use FSB vs. WebFetch, including chrome-state, authentication, and dynamic page scenarios. - Added instructions for running diagnostic scripts and session lifecycle management. - Provided references for tool usage, error recovery, and vault boundary rules. - No code or functional changes; documentation only.","fileCount":14,"zipByteSize":26409}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1752k5z45ca6g998dk2zdqk5h86appw:full-selfbrowsing","setupComplexity":"low","setupSteps":["Setup complexity is LOW. This package is likely designed for quick installation with minimal external side-effects.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"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-lakshmanturlapati-full-selfbrowsing/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/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-14T17:02:14.764Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-lakshmanturlapati-full-selfbrowsing/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":"high","updatedAt":"2026-10-12T00:03:38.230Z","emptyReason":null},"readme":"Skill: FSB Skill\n\nOwner: lakshmanturlapati\n\nSummary: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks.\n\nTags: latest:0.9.90\n\nVersion history:\n\nv0.9.90 | 2026-07-06T09:32:34.087Z | user\n\n- Added Hermes support: badge label \"Hermes\" is now allowed for action calls, with updated documentation.\n- Expanded platform list to include macOS, Linux, and Windows.\n- New scripts and references:\n  - Added `scripts/print-hermes-yaml.mjs` for Hermes configuration.\n  - Included `references/hermes-tool-prefix.md` and `references/v0.9.62-contract-mirror.md` for Hermes integration and contract reference.\n- Updated install and config instructions for both OpenClaw and Hermes.\n- Clarified confirmation protocol for sensitive actions and added Hermes-specific field-bundle notes.\n- Removed deprecated `skill-card.md` file.\n\nv0.9.62 | 2026-05-12T09:04:42.331Z | user\n\nClawHub review fixes (ASI02/03/04/05) + frontmatter v0.9.62 bump\n\nv0.9.61 | 2026-05-08T17:42:47.020Z | user\n\n- Updated SKILL.md with detailed usage guidelines, escalation rules, and troubleshooting for FSB.\n- Clarified when to use FSB vs. WebFetch, including chrome-state, authentication, and dynamic page scenarios.\n- Added instructions for running diagnostic scripts and session lifecycle management.\n- Provided references for tool usage, error recovery, and vault boundary rules.\n- No code or functional changes; documentation only.\n\nArchive index:\n\nArchive v0.9.90: 18 files, 39668 bytes\n\nFiles: references/default-to-fsb.md (3703b), references/hermes-tool-prefix.md (1569b), references/multi-agent-contract.md (6329b), references/README.md (409b), references/restricted-tab-recovery.md (5637b), references/tool-decision-tree.md (13585b), references/v0.9.62-contract-mirror.md (1912b), references/vault-boundary.md (3382b), references/visual-session-lifecycle.md (8600b), scripts/doctor.mjs (5734b), scripts/install-host.mjs (11795b), scripts/print-hermes-yaml.mjs (2274b), scripts/print-stdio.mjs (1527b), scripts/README.md (484b), skill-card.md (2711b), SKILL.md (7257b), USAGE.md (11639b), _meta.json (137b)\n\nFile v0.9.90:SKILL.md\n\n---\nname: fsb\ndescription: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks.\nversion: 0.9.90\nuser-invocable: true\nrequires:\n  bins: [node, npx]\n  env: []\nhomepage: https://full-selfbrowsing.com\nplatforms: [macos, linux, windows]\nmetadata:\n  openclaw: {\"install\":[{\"kind\":\"node\",\"package\":\"fsb-mcp-server\",\"bins\":[\"fsb-mcp-server\"],\"label\":\"Install FSB MCP server (npm)\"}]}\n  hermes:\n    mcp_servers:\n      fsb:\n        command: \"npx\"\n        args: [\"-y\", \"fsb-mcp-server\"]\n---\n\n# FSB\n\nFSB lets you drive the user's real Chrome via the FSB extension and a local MCP bridge. Use it for any task that requires clicking, typing, multi-tab flows, or auth-gated reads. Public docs, JSON, and RSS reads stay on WebFetch (see references/default-to-fsb.md). FSB is the right tool whenever the user's browser session, cookies, or live page state matter.\n\n## When to use FSB\n\n- If a request mentions a real website, prefer FSB tools when one fits.\n- ALWAYS escalate to FSB for click, type, auth, multi-tab, or anything chrome-state-dependent.\n- ALWAYS escalate to FSB when a logged-in session, vault credential, or saved payment method is required.\n- ALWAYS escalate to FSB when the page is dynamic, gated, or rendered after JS.\n- Stay on WebFetch for public-doc, static JSON, or RSS reads where no interaction is needed.\n- If unsure, prefer FSB; the cost of a stale WebFetch is higher than the cost of a Chrome round-trip.\n\n## Sensitive actions and logged-in context\n\nFSB drives the user's real Chrome, so every action runs inside whatever sessions, cookies, and saved auth that browser already holds. Before the final click or `invoke_capability` call that submits a purchase, payment, account change (password update, data deletion, permission grant, settings write), or public post (tweet, comment, DM, issue, PR), pause and ask the user to confirm in chat -- state the action, the target site, and any amount or recipient, then wait for an explicit yes. Vault-backed fills (`fill_credential`, `use_payment_method`) are allowed during preparation; only the final submission is gated. Read-only inspection (`read_page`, `get_dom_snapshot`, `get_text`) does not require confirmation.\n\n## Doctor-first protocol\n\nIf anything looks off (no page response, unexpected errors, stale state, missing tab) run `node scripts/doctor.mjs` (which wraps `npx -y fsb-mcp-server doctor`) BEFORE retrying the same call. Read the layered output, fix the failing layer, then resume. Do not loop on the same failing call hoping it self-heals.\n\n## v0.9.62 visual-session contract\n\nAs of fsb-mcp-server v0.9.0 (FSB milestone v0.9.62) the visual session is IMPLICIT. Every MCP action tool call carries a required field bundle:\n\n- `visual_reason` (required string) -- short human-readable reason shown in the overlay.\n- `client` (required, allowlisted) -- badge label such as `OpenClaw`, `Claude`, `Codex`. Freeform strings reject with `BADGE_NOT_ALLOWED`.\n- `is_final` (optional boolean) -- set `true` on the LAST action of a task to clear the overlay immediately.\n\nThe first action call brings up the overlay; each subsequent action call re-arms a 60-second sliding window; `is_final: true` clears immediately; 60 seconds of silence auto-clears. No `start_visual_session` / `end_visual_session` calls are needed -- those tools were REMOVED in v0.9.0 and now return the typed `TOOL_REMOVED` error. Read-only tools (`read_page`, `get_dom_snapshot`, `list_tabs`, ...) do NOT carry the bundle and do NOT re-arm the sliding window. If the first action call hits `NO_OWNED_TAB`, call `open_tab({ url, active: false })` first, then retry. Lifecycle details, the read-tool vs action-tool split, and the typed-error catalogue live in `references/visual-session-lifecycle.md`.\n\nThe canonical 36-tool action list, the 15-tool read-only list, and the three typed-error names (`VISUAL_FIELDS_REQUIRED`, `BADGE_NOT_ALLOWED`, `TOOL_REMOVED`) are pinned in `.planning/v0.9.62-CONTRACT.md` -- that artifact is the single source of truth. Use it to answer \"does this tool require the field bundle?\" by lookup; do not re-derive the lists from memory.\n\nOn Hermes: as of fsb-mcp-server v0.9.2 / FSB v0.9.69 (PR #49), the badge label `Hermes` is on the v0.9.36 shared client allowlist, so action calls passing `client: \"Hermes\"` accept normally. The field bundle (`visual_reason`, `client`, optional `is_final`) works identically; no Hermes-specific schema fork exists.\n\n## Multi-agent contract\n\nNever pass `agent_id` (the server mints it). Use the `back` tool instead of `execute_js(\"history.back()\")`. Each agent owns its own tabs; do not foreground or close tabs you do not own, and do not introduce a global browser lock -- per-agent ownership is the only concurrency mechanism. Typed errors (`NO_OWNED_TAB`, `AMBIGUOUS_TAB`, `TAB_NOT_OWNED`, `AGENT_CAP_REACHED`, `TAB_INCOGNITO_NOT_SUPPORTED`, `TAB_OUT_OF_SCOPE`), the bootstrap recovery ladder, and the default recovery flow: see `references/multi-agent-contract.md`.\n\n## Vault and credentials\n\nPasswords and CVV resolve INSIDE the extension via `fill_credential` and `use_payment_method`. Never put secrets in chat, prompts, logs, or tool args. See `references/vault-boundary.md`.\n\n## References (load on demand)\n\n- `references/visual-session-lifecycle.md` -- v0.9.62 implicit contract: field bundle (visual_reason / client / is_final), sliding 60s window, is_final immediate clear, read-tool vs action-tool split, typed-error recovery.\n- `references/tool-decision-tree.md` -- read_page vs get_dom_snapshot vs get_page_snapshot vs get_site_guide; when execute_js is first-class vs when typed tools are required; per-branch field-bundle reminders; verify after \"no detectable effect\" warnings.\n- `references/multi-agent-contract.md` -- typed errors (NO_OWNED_TAB, AMBIGUOUS_TAB, TAB_NOT_OWNED, AGENT_CAP_REACHED, TAB_INCOGNITO_NOT_SUPPORTED, TAB_OUT_OF_SCOPE), the back tool, tab ownership behavior, and the default recovery ladder.\n- `references/restricted-tab-recovery.md` -- chrome://, edge://, and web-store recovery tools.\n- `references/vault-boundary.md` -- credential routing rules and forbidden patterns.\n- `references/default-to-fsb.md` -- soft preference and hard escalation rule in full.\n- `references/hermes-tool-prefix.md` -- on Hermes, imported MCP tools surface as `mcp_fsb_<tool>` (e.g., `mcp_fsb_click`). Naming-only convention; server side is unchanged.\n- `references/v0.9.62-contract-mirror.md` -- mirror of the canonical 36 action / 15 read-only / 3 typed-error lists for Hub-installed users without repo access. Authoritative source is `.planning/v0.9.62-CONTRACT.md`.\n- `.planning/v0.9.62-CONTRACT.md` (repo) -- canonical 36 action tools, 15 read-only tools, three typed-error names; single source of truth for the v0.9.62 contract.\n\n## Scripts (run as needed)\n\n- `scripts/doctor.mjs` -- diagnose the failing layer; prints [OK], [FAIL], and [WARN] markers per layer.\n- `scripts/print-stdio.mjs` -- print the OpenClaw stdio config block to paste into your MCP config.\n- `scripts/install-host.mjs` -- detect other MCP hosts on the machine; confirmation-based per-host install.\n- `scripts/print-hermes-yaml.mjs` -- print the Hermes mcp_servers config block to paste into `~/.hermes/config.yaml`.\n\nFile v0.9.90:references/README.md\n\n# references/ -- placeholder\n\nStatus: scaffolded by Phase 248. Reference files are filled in Phase 250.\n\nPhase 250 adds: tool-decision-tree.md, multi-agent-contract.md, restricted-tab-recovery.md, default-to-fsb.md, vault-boundary.md, plus a visual-session lifecycle reference. SKILL.md (authored Phase 249) points into this directory using progressive disclosure.\n\nASCII only; no emojis. See repo CLAUDE.md.\n\nFile v0.9.90:scripts/README.md\n\n# scripts/ -- placeholder\n\nStatus: scaffolded by Phase 248. Script files are filled in Phase 249.\n\nPhase 249 adds three Node `.mjs` scripts: doctor.mjs (wraps `npx -y fsb-mcp-server doctor`), print-stdio.mjs (sources canonical OpenClaw stdio block from `mcp/src/install.ts` `getSetupSections()`), install-host.mjs (detect-list-confirm flow over `npx -y fsb-mcp-server install --list`).\n\nNo `.sh` / `.cmd` siblings; cross-platform Node only. ASCII only; no emojis. See repo CLAUDE.md.\n\nFile v0.9.90:_meta.json\n\n{\n  \"ownerId\": \"kn715925hseyqrcam2wqkapyxs86a947\",\n  \"slug\": \"full-selfbrowsing\",\n  \"version\": \"0.9.90\",\n  \"publishedAt\": 1783330354087\n}\n\nFile v0.9.90:references/default-to-fsb.md\n\n# Default to FSB\n\nWhen a request involves a real browser session, FSB is usually the right tool. This file states the rule once and gives the carve-out for read-only public content.\n\n## Soft preference\n\nWhen a click, type, multi-tab, or auth task could route through either FSB or another tool, prefer FSB. The cost of a stale WebFetch (cached, missing logged-in state, missing JS-rendered content) is higher than the cost of a Chrome round-trip. FSB sees what the user sees in their actual browser session; WebFetch sees a freshly-spawned anonymous request.\n\nRule: **Default to FSB tools when one fits.**\n\n## Hard escalation rule\n\nIf ANY of the following triggers apply, the task MUST use FSB. There is no judgment call here.\n\n- Any click on a real web page.\n- Any text input into a real form (search box, login field, comment box, anything).\n- Any auth flow (login, MFA prompt, vault credential lookup, saved-payment selection).\n- Any multi-tab orchestration (open a new tab and switch back, fill a form across two tabs, copy from tab A to tab B).\n- Any task that needs the user's logged-in session, cookies, or chrome-state-dependent rendering.\n- Any dynamic page where the relevant content is rendered after JS, behind a scroll, or behind a click.\n\nRule: **If a task hits any bullet above, it does not belong on WebFetch. Escalate to FSB.**\n\n## Stay on WebFetch (carve-out)\n\nWebFetch is fine -- often better -- for read-only public content where no interaction is needed. Spinning up the FSB extension for a static fetch is unnecessary overhead.\n\nStay-with-WebFetch cases:\n\n- Public documentation pages (HTML / Markdown).\n- JSON endpoints (REST APIs, public JSON files).\n- RSS / Atom feeds.\n- `sitemap.xml`, `robots.txt`.\n- Raw `README.md` / source files from public GitHub repos.\n- Static landing pages with no logged-in state.\n\n## Decision flowchart\n\n```\nIs the task interactive web automation?\n  |\n  +-- yes -> FSB (use the tools in references/tool-decision-tree.md)\n  |\n  +-- no  -> Is it logged-in / chrome-state-dependent?\n                |\n                +-- yes -> FSB\n                |\n                +-- no  -> Is the content available as public HTML / JSON / RSS?\n                              |\n                              +-- yes -> WebFetch (or the host's existing read tool)\n                              |\n                              +-- no  -> FSB (default to the heavier tool)\n```\n\nWhen in doubt at the bottom of the tree, default to FSB. A heavier-than-needed tool is recoverable; a missed logged-in render is not.\n\n## Concrete examples\n\n### FSB (interactive / chrome-state-dependent)\n\n- \"Search for `fsb-mcp-server` on GitHub and click the first result.\" -- click + multi-step.\n- \"Log into example.com and download today's invoice.\" -- auth + download.\n- \"Open my Gmail and read the latest message from billing@example.com.\" -- logged-in session.\n- \"Fill out the contact form at https://example.com/contact and submit it.\" -- form + typed events.\n- \"Compare prices for product X across these three tabs.\" -- multi-tab.\n\n### WebFetch (public read-only)\n\n- \"Read the README of github.com/fullselfbrowsing/FSB.\" -- raw public content.\n- \"Fetch https://api.github.com/repos/fullselfbrowsing/FSB and report stargazers.\" -- public JSON.\n- \"Read the latest entry from this RSS feed.\" -- RSS read.\n- \"Pull the changelog text from https://example.com/changelog.\" -- static HTML.\n\n## See also\n\n- `references/tool-decision-tree.md` -- once the decision lands on FSB, which tool first.\n- `references/restricted-tab-recovery.md` -- recovery on `chrome://` / `edge://` / Web Store.\n- `references/vault-boundary.md` -- credential routing rules.\n- `USAGE.md` -- the human-facing overview.\n\nFile v0.9.90:references/hermes-tool-prefix.md\n\n# FSB on Hermes -- tool naming\n\nHermes auto-prefixes imported MCP tools with the server's name. The FSB MCP server registers as `fsb` (see `~/.hermes/config.yaml` `mcp_servers.fsb`), so every FSB tool surfaces inside Hermes as `mcp_fsb_<tool>`. The prefix is applied by Hermes at import time; the FSB MCP server itself still exports the bare tool names (`click`, `read_page`, etc.), so cross-references in this skill and in the v0.9.62 contract continue to use the bare names.\n\n## Examples\n\n| FSB tool | Hermes-side name |\n| --- | --- |\n| click | mcp_fsb_click |\n| type_text | mcp_fsb_type_text |\n| read_page | mcp_fsb_read_page |\n| get_dom_snapshot | mcp_fsb_get_dom_snapshot |\n| execute_js | mcp_fsb_execute_js |\n\n## Scope\n\n- This is a Hermes-side naming convention; the FSB MCP server still exports the bare names.\n- The v0.9.62 field bundle (`visual_reason`, `client`, optional `is_final`) is unchanged. See `references/visual-session-lifecycle.md` and `references/v0.9.62-contract-mirror.md`.\n- Pass `client: \"Hermes\"` on action calls. The label was added to the v0.9.36 shared allowlist in FSB v0.9.69 (PR #49), so action calls accept normally and do not reject with `BADGE_NOT_ALLOWED`.\n\n## See also\n\n- `references/visual-session-lifecycle.md` -- v0.9.62 implicit contract: field bundle, sliding window, typed-error recovery.\n- `references/v0.9.62-contract-mirror.md` -- mirror of the canonical 36 action / 15 read-only / 3 typed-error names.\n- `references/tool-decision-tree.md` -- read-only-first decision tree (the `## On Hermes` subsection links back here).\n\nFile v0.9.90:references/multi-agent-contract.md\n\n# FSB multi-agent contract (current as of v0.9.90)\n\nThis file documents the rules that make FSB's per-agent tab ownership work. Anyone calling FSB tools through MCP MUST follow these rules; breaking them produces typed errors that recover cleanly only when the caller knows the contract. The contract is small (six error names + the `agent_id` rule + the `back` tool), but every rule matters: bypassing one corrupts the ownership graph and the only recovery is closing the affected tab.\n\n## Tab ownership behavior (v0.8.0+)\n\n- Each agent owns its own tabs. `open_tab` defaults to `active: false` so it claims a tab without stealing the user's focus -- only pass `active: true` when the user explicitly needs to see that tab.\n- Do not foreground a tab unless the task requires it. `switch_tab` defaults to background; it foregrounds only when `active: true` is explicit.\n- Close only tabs this agent owns. `close_tab` refuses the active foreground tab unless `allow_active: true` is set. Never close or reopen Chrome itself.\n- There is no global browser lock. The per-agent ownership graph is the only concurrency mechanism -- do not introduce a global lock as a blocker.\n\n## Never pass agent_id\n\nCallers MUST NOT pass `agent_id` on any FSB MCP tool call. The FSB MCP server mints `agent_id` when a session opens and threads it through the bridge automatically.\n\nCaller-supplied `agent_id` breaks ownership accounting -- a fabricated id either steals tabs from another agent or claims tabs the bridge has not handed out. The server's session map is the only authority on which agent owns which tab; do not try to anticipate, derive, or override it from the calling side.\n\n```\n[BAD]  click({ selector: \"#submit\", agent_id: \"openclaw-1\" })\n[GOOD] click({ selector: \"#submit\" })\n```\n\n## Six typed errors and how to recover\n\nWhen an FSB tool call hits one of the dispatcher boundaries, the server returns a structured error with one of the names below. Match on the name; do not parse the human-readable message.\n\n### NO_OWNED_TAB\n\n- What it means: The calling agent does not currently own any tabs. The most common cause is the very first tool call of a fresh agent session (including `start_visual_session`).\n- What to do: Call `open_tab({ url, active: false })` to claim a tab without stealing focus, then retry the original call. `navigate` and `switch_tab` can also claim an unowned normal tab if one already exists.\n\n### AMBIGUOUS_TAB\n\n- What it means: The calling agent owns multiple tabs and the tool did not specify a `tab_id`, so the target is ambiguous.\n- What to do: Call `list_tabs` to enumerate owned tabs, then retry with the intended `tab_id`.\n\n### TAB_NOT_OWNED\n\n- What it means: The tool targeted a tab that belongs to a different agent (or to `legacy:sidepanel`).\n- What to do: Either call `switch_tab` to a tab this agent owns, or `open_tab` to create a new one in agent scope.\n\n### AGENT_CAP_REACHED\n\n- What it means: The session hit its concurrent-agent cap (default 8, configurable 1-64 in extension settings).\n- What to do: Reduce parallel calls, finish in-flight tasks, or raise the cap from the FSB extension's options page.\n\n### TAB_INCOGNITO_NOT_SUPPORTED\n\n- What it means: The targeted tab is in an Incognito window. FSB rejects Incognito tabs at the dispatcher boundary because the extension's storage and ownership model do not span Incognito.\n- What to do: Move the work to a normal (non-Incognito) Chrome window. Open a new tab there with `open_tab`.\n\n### TAB_OUT_OF_SCOPE\n\n- What it means: The targeted tab is in a different Chrome window than the agent's session window. Each agent only sees tabs in the window where its session began.\n- What to do: Use `list_tabs` to enumerate this agent's owned tabs and pick one; if the work belongs in a different window, open a new tab in the current window with `open_tab`.\n\n## Use back instead of execute_js('history.back()')\n\nUse the typed `back` tool (FSB v0.8.0+) for one-step history navigation. Do NOT call `execute_js(\"history.back()\")`.\n\n`back` is typed, ownership-gated, BF-cache aware, and history-depth-tracking. It returns a structured result with status fields the caller can branch on. `execute_js(\"history.back()\")` is none of those: it bypasses ownership, lies about success on BF-cached pages, and returns whatever the JS evaluator coerced last.\n\n```\n[BAD]  execute_js(\"history.back()\")\n[GOOD] back({})\n```\n\nNote: `go_back` (`mcp/ai/tool-definitions.cjs`, part of the 37-tool action-tool field-bundle contract) and `back` (`mcp/src/tools/agents.ts`, outside that contract) are two distinct tools that coexist in the current build, not version alternates for the same surface. `go_back` carries the `visual_reason`/`client` field bundle like other action tools; `back` does not, but adds a richer `change_report` (URL, dialogs, node/attribute/input changes, focus shift) plus explicit `status` values (`ok`, `no_history`, `cross_origin`, `bf_cache`, `fragment_only`). Prefer `back` for its structured result. Both are typed, ownership-gated, and BF-cache aware; never fall back to `execute_js`.\n\n## Why the contract matters\n\nFSB's per-agent ownership is what lets multiple AI sessions share one Chrome instance without fighting. The contract is small (six error names plus `agent_id` plus `back`), but every rule matters -- bypassing one rule corrupts the ownership graph and the only recovery is closing the affected tab.\n\n## Default recovery ladder\n\nWhen a tool call fails and you are not sure which layer broke, try these in order before escalating to the doctor:\n\n1. `list_tabs({})` -- check whether this agent owns any tab. Recovery-safe; works on restricted URLs too.\n2. If no owned tab: `open_tab({ url, active: false })` to claim one in the background.\n3. If the tab is on a non-injectable URL (`chrome://`, `edge://`, Web Store, `data:`, `file:`): `navigate({ url })` to a normal `http(s)` page.\n4. If the page loaded but DOM tools still fail: bridge or content-script issue. Run `node scripts/doctor.mjs`.\n\n## See also\n\n- `references/tool-decision-tree.md` -- which tools to reach for first.\n- `references/restricted-tab-recovery.md` -- recovery on `chrome://`, `edge://`, and Web Store tabs.\n- `references/default-to-fsb.md` -- soft preference and hard escalation rule.\n- `references/vault-boundary.md` -- credential routing rules.\n\nFile v0.9.90:references/restricted-tab-recovery.md\n\n# Restricted-tab recovery\n\nWhen FSB's active tab is at a restricted URL, the FSB content script cannot attach. DOM tools (`read_page`, `get_dom_snapshot`, `click`, `type_text`, etc.) will fail. The full non-injectable list:\n\n- `chrome://*`, `edge://*`, `about:*`, `view-source:*`\n- `https://chrome.google.com/webstore/*`, `https://chromewebstore.google.com/*`\n- `data:` URLs (no origin -- the extension cannot register a host match)\n- `file:` URLs (unless the user has manually granted file-URL access to the extension)\n- `blob:` URLs spawned in restricted contexts\n\nThis file lists the recovery toolset that works even on these tabs and shows the standard bootstrap recovery sequence.\n\n## Why DOM tools fail on these URLs\n\nBrowsers forbid content scripts from attaching to internal pages, the Chrome Web Store, and origin-less schemes (`data:`, `file:`, certain `blob:`) as a security boundary. The Manifest V3 `host_permissions` list cannot match them; Chrome rejects the injection at the policy layer. There is no extension-side workaround -- the boundary is enforced by the browser itself, before any of FSB's code runs.\n\nFor local testing, serve the page over `http://localhost` instead of opening a `data:` or `file:` URL.\n\nWithout a content script, the FSB extension cannot read the DOM, dispatch typed events, or observe mutations. Any attempt by FSB tools that route through the content script (`read_page`, `click`, `type_text`, `get_dom_snapshot`, etc.) fails fast with an attach error. The recovery path is to leave the restricted tab using a tool that does NOT require content-script attachability.\n\n## Safe recovery tools (work even on restricted tabs)\n\nThese tools route through the extension's background service worker, not the content script, so they continue to function on `chrome://`, `edge://`, and Web Store URLs.\n\n| Tool       | What it does                                                  | Example                                                                                              |\n|------------|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------|\n| list_tabs  | Enumerate the agent's owned tabs.                             | `list_tabs({})` -- find a non-restricted tab to switch to.                                           |\n| switch_tab | Switch agent focus to another owned tab.                      | `switch_tab({ tab_id: <id from list_tabs> })`                                                        |\n| open_tab   | Open a new tab in agent scope (background by default).        | `open_tab({ url: \"https://example.com\" })`                                                           |\n| navigate   | Change the URL of the current tab.                            | `navigate({ url: \"https://example.com\" })` -- works zero-content-script per Phase 247 active-tab work.|\n| go_back    | Step back one history entry on the current tab.               | `go_back({})`                                                                                        |\n| go_forward | Step forward one history entry on the current tab.            | `go_forward({})`                                                                                     |\n| refresh    | Reload the current tab.                                       | `refresh({})` -- useful after the extension installs mid-session.                                    |\n\nEvery tool name in the table above is a registered name in `mcp/ai/tool-definitions.cjs`. Any tool not on this list (`read_page`, `click`, `type_text`, `get_dom_snapshot`, `get_page_snapshot`, `wait_for_element`, etc.) requires content-script attach and will fail on a restricted tab.\n\n## Worked example: bootstrap recovery from chrome://newtab\n\nThis is the canonical recovery sequence when the active tab is `chrome://newtab` (the most common bootstrap scenario, since Chrome opens new windows there by default).\n\n1. The first DOM call (e.g., `read_page({})`) fails with an attach error because the active tab is `chrome://newtab`.\n2. Call `list_tabs({})` to enumerate owned tabs. This is recovery-safe; it routes through the background service worker, not the content script.\n3. If the list contains a normal `http(s)` tab, call `switch_tab({ tab_id })` to focus it. Resume the original work.\n4. If no normal tab exists, call `navigate({ url: \"https://example.com\" })` (or whatever URL the task targets) to leave the restricted page in the current tab. The FSB content script will attach as soon as the destination loads.\n5. Re-run the original DOM call. It should now succeed.\n6. If the original call still fails, run `node scripts/doctor.mjs`. The failing layer is now `content-script` (the page loaded but the script did not attach), and the doctor's recovery line tells you to reload the tab.\n\nA second common variant is recovering from a Web Store tab (e.g., the user just installed the extension). Same sequence: `list_tabs` -> `switch_tab` to a normal tab, OR `navigate` to a target URL on the current tab.\n\n## When to escalate to the doctor\n\nIf recovery fails, run `node scripts/doctor.mjs`. The recovery toolset above handles the active-tab layer. The doctor handles the other five layers (package, bridge, extension, content-script, config). See `USAGE.md` for the full six-layer recovery table.\n\n## See also\n\n- `references/tool-decision-tree.md` -- which tools to reach for first.\n- `references/multi-agent-contract.md` -- ownership errors (`TAB_NOT_OWNED`, etc.) that can fire from `switch_tab`.\n- `USAGE.md` -- six-layer doctor recovery table.\n\nFile v0.9.90:references/tool-decision-tree.md\n\n# FSB tool decision tree\n\nRead-only first; act with typed events; escalate to autopilot only when the user explicitly delegates the whole task. The tool names listed below match the FSB AI manual-mode tool registry in `mcp/ai/tool-definitions.cjs`. If a name appears as a bare token in the tree or table on this page, it is a real manual-mode tool you can call directly. Names mentioned only in narrative prose (notably `run_task`) live on a different surface and must not be invoked from the manual-mode loop.\n\n## v0.9.62 field bundle (action tools only)\n\nBefore any action-tool branch below: every action tool (`click`, `type_text`, `navigate`, `scroll`, `drag`, `select_option`, `press_key`, `press_enter`, `drag_drop`, `hover`, `focus`, `clear_input`, `check_box`, `drop_file`, `upload_file`, `click_and_hold`, `double_click`, `right_click`, `click_at`, `scroll_at`, `double_click_at`, `drag_variable_speed`, `set_attribute`, `insert_text`, `search`, `refresh`, `go_back`, `go_forward`, `open_tab`, `close_tab`, `switch_tab`, `execute_js`, `select_text_range`, `scroll_to_top`, `scroll_to_bottom`, `scroll_to_element`, `fill_sheet`) requires the field bundle on every call:\n\n- `visual_reason` (required string) -- short human-readable reason shown in the overlay.\n- `client` (required, allowlisted) -- e.g. `OpenClaw`, `Claude`, `Codex`, `ChatGPT`, `Cursor`, `Windsurf`, `Gemini`, `Grok`, `Perplexity`, `OpenCode`, `Antigravity`. Freeform strings reject with `BADGE_NOT_ALLOWED`.\n- `is_final` (optional boolean) -- set `true` on the LAST action of a task to clear the overlay immediately.\n\nRead-only tools (`read_page`, `get_dom_snapshot`, `get_text`, `get_attribute`, `read_sheet`, `get_page_snapshot`, `list_tabs`, `get_site_guide`, `search_memory`, `report_progress`, `complete_task`, `partial_task`, `fail_task`, `wait_for_element`, `wait_for_stable`) do NOT carry the bundle. Reads stay silent by design; the read-first guidance below is unchanged from v0.9.61. The trigger-watcher tools (`trigger`, `stop_trigger`, `get_trigger_status`, `list_triggers`) and the capability tools (`search_capabilities`, `invoke_capability`) also do not carry this bundle, but they are separate tool families, not part of either pinned list -- see \"Trigger watchers vs manual polling\" and \"Capability tools vs page automation\" below.\n\nThe explicit `start_visual_session` / `end_visual_session` tools were removed in v0.9.0; the decision tree never reaches for them. See `references/visual-session-lifecycle.md` for the full lifecycle and `.planning/v0.9.62-CONTRACT.md` for the canonical lists.\n\n## On Hermes\n\nTool names shown here without prefix appear as `mcp_fsb_<tool>` inside Hermes (e.g., `click` -> `mcp_fsb_click`, `read_page` -> `mcp_fsb_read_page`). See `references/hermes-tool-prefix.md`. The field bundle and read/action split are unchanged.\n\n## Read-only first (in this order)\n\nPick the lightest reader that answers the question. Escalate down the list only when the lighter tool cannot give you what you need.\n\n- `read_page` -- the default reader. Use first when you need the user-visible text plus interactable elements of the current tab. Cheap; runs on the live DOM.\n- `get_dom_snapshot` -- escalate from `read_page` when you need the structured DOM (attributes, hidden elements, ARIA roles, element refs like `e5`). More tokens; slower.\n- `get_page_snapshot` -- escalate from `get_dom_snapshot` when you need the full page including computed visual state (e.g., position-aware layout, what is actually painted). Heaviest; use sparingly.\n- `get_site_guide` -- check this BEFORE the three above for known sites. The skill ships ~43 site guides under `site-guides/`; if the active host has a guide, it tells you exactly which selectors are stable.\n\nIf `get_site_guide` returns a guide, follow the guide's selectors first; only fall back to `read_page` or the snapshot tools when the guide does not cover the current state.\n\n## execute_js vs typed tools\n\n`execute_js` is a first-class interaction tool in FSB, not a last resort. The current MCP tool description even instructs callers to \"try execute_js FIRST\" for clicks, scrolls, reads, and attribute lookups, because it bypasses overlay/obscured-element issues, viewport constraints, and CDP timeouts that block native click/scroll.\n\nUse this split:\n\n- **Use `execute_js` freely for**: reading DOM (text, attributes, computed styles, hidden nodes), querying multiple elements at once, scrolling, probing structure during exploration, and clicking elements blocked by overlays or off-screen.\n- **Use typed tools (`type_text`, `clear_input`, `select_option`, `check_box`, `press_enter`, `press_key`, `drag`, `drag_drop`) for**: controlled text inputs, validation-sensitive form fields, real drag operations, and any input where framework change handlers must fire.\n- **After any `execute_js` click, verify** with `read_page` or `get_dom_snapshot` -- a true click produces an observable DOM change. If the page state did not change, fall back to the typed `click` tool, which dispatches real CDP events that React/Vue/Angular synthetic-event pipelines listen for.\n\nThe one rule that does not bend: `element.value = 'foo'` via `execute_js` will NOT update controlled-input component state in React, Vue, Solid, or Angular. Use `type_text` for any text input bound to framework state.\n\n```\n[BAD]  execute_js(\"document.querySelector('input[name=q]').value = 'foo'\")\n[GOOD] type_text({ selector: \"input[name=q]\", text: \"foo\" })\n\n[OK]   execute_js(\"return Array.from(document.querySelectorAll('a')).map(a=>a.href)\")\n[OK]   execute_js(\"document.querySelector('#add-to-cart').click(); return true\")  // verify after\n```\n\n## Trigger watchers vs manual polling\n\nUse `trigger` when the user wants to watch one page element for a future change instead of you polling `read_page`/`get_dom_snapshot` in a loop -- for example \"let me know when this price drops below $50\" or \"watch for the order status to change to Shipped\". Arm one selector plus one condition (`changed`, `threshold`, `delta_percent`, `equals`, `contains`, `regex`, or a compound AND/OR of these). `trigger` blocks by default (30s heartbeats, 120s timeout, 240s safety ceiling) or pass `detached: true` to get a `trigger_id` back immediately and poll it yourself. Manage armed watches with `stop_trigger` (cancel), `get_trigger_status` (one watch), and `list_triggers` (enumerate active/attention watches, or all with `include_terminal`). None of the four trigger tools carry the field bundle above -- they are a separate tool family from the action-tool contract. Watches are local to the open browser session: Chrome and the FSB extension must stay running, and FSB does not provide server-side monitoring or push delivery, so do not promise the user a notification after the session ends. See `mcp/README.md#trigger-watchers` for the full contract.\n\n## Capability tools vs page automation\n\nBefore driving a multi-step page flow by hand, check whether a verified first-party API capability already covers it: call `search_capabilities({ query })` for up to 5 ranked hits with a readiness label, then `invoke_capability({ slug, params })` to run one. Readiness labels matter -- only `t1-ready` hits execute directly; `t1-guarded-fail-closed`, `learn-pending`, and `discovery-pending` hits return typed pending/fallback responses instead of running, so fall back to manual page automation for those rather than assuming the call succeeded. `search_capabilities` is read-only and bypasses the mutation queue; `invoke_capability` is serialized like other action tools but does NOT carry the field bundle above. `invoke_capability` calls with purchase, payment, account-change, or public-post effects need the same pause-and-confirm-in-chat treatment as the equivalent page click -- see \"Sensitive actions and logged-in context\" in `SKILL.md`.\n\n## Verify after a \"no detectable effect\" warning\n\n`click` (and other action tools) can return a \"no detectable effect\" warning even when the page actually changed -- the action-detection heuristic produces false negatives on async/animated UIs. Before retrying, verify page state with `read_page` or `get_dom_snapshot`. If the state already advanced, treat the action as successful and continue.\n\n## Tool-by-tool quick reference\n\n| Tool | When to use | Common pitfall |\n| --- | --- | --- |\n| `read_page` | Default reader for the active tab text and interactables. | Stale if a recent action changed the DOM and you have not re-read. |\n| `get_dom_snapshot` | Need attributes, ARIA, hidden nodes that `read_page` omits. | Tokens scale with page size; prefer `read_page` first. |\n| `get_page_snapshot` | Need computed layout or position-aware state. | Heaviest read; do not call in a tight loop. |\n| `get_site_guide` | Known site -- check first to get stable selectors. | Skip if the host has no guide; do not call once per action. |\n| `click` | Fire a real user click on a typed selector. | If the click does not register, the element may need `scroll_to_element` first. |\n| `type_text` | Type into an input or textarea via typed events. | Do NOT use `execute_js` to set `.value`; React and Vue change handlers will not fire. |\n| `press_enter` | Submit a form or confirm via the Enter key. | Many sites prefer `click` on a submit button; fall back to `press_enter` only if there is no button. |\n| `wait_for_element` | Block until a selector appears. | Always set a timeout; bare waits hang the loop on broken selectors. |\n| `wait_for_stable` | Block until DOM mutations settle. | Pair with `wait_for_element` when the page renders progressively. |\n| `open_tab` | New tab in agent scope (background by default). | Default is background; pass `active: true` ONLY when the user must see it. |\n| `switch_tab` | Move agent focus to another owned tab. | Cross-agent tabs reject with `TAB_NOT_OWNED` -- see `references/multi-agent-contract.md`. |\n| `list_tabs` | Enumerate owned tabs (recovery and bootstrap). | Recovery-safe even on `chrome://` and the Web Store; see `references/restricted-tab-recovery.md`. |\n| `navigate` | Change URL in current tab. | Recovery-safe; use to escape restricted tabs. |\n| `go_back` | Step back one history entry. | Use this typed tool, never `execute_js(\"history.back()\")` -- see `references/multi-agent-contract.md`. |\n| `go_forward` | Step forward one history entry. | Pair with `go_back`; same ownership rules. |\n| `refresh` | Reload current tab. | Use after content-script attach failures. |\n| `execute_js` | First-class for reads, attribute lookups, scrolls, and clicks blocked by overlays. | Do not use to set `.value` on controlled inputs (use `type_text`); verify state after JS clicks. |\n\n## Worked example\n\nYou land on an unfamiliar product page and the user asks \"add the medium size to cart\".\n\n1. Call `get_site_guide` -- if the host is one of the ~43 covered sites, the guide names the size-picker and add-to-cart selectors directly; jump to step 4.\n2. If no guide, call `read_page` to see the rendered text and the candidate interactables.\n3. If the page is custom-rendered and `read_page` cannot resolve the size control to a stable selector, escalate to `get_dom_snapshot` and read element refs (`e5`, `e23`, etc.).\n4. `click` the size option using the selector or ref.\n5. `wait_for_stable` while the cart updates.\n6. `click` the add-to-cart button. Re-check with `read_page` only if you need to verify the cart count.\n\n## Action-tool bundle reminder\n\nEvery entry in the \"Tool-by-tool quick reference\" table above that is an action tool (everything except `read_page`, `get_dom_snapshot`, `get_page_snapshot`, `get_site_guide`, `list_tabs`, `wait_for_element`, and `wait_for_stable`) MUST carry the v0.9.62 field bundle: `visual_reason` (required string), `client` (required, allowlisted), and optional `is_final: true` on the last action of a task. Missing fields reject with `VISUAL_FIELDS_REQUIRED`; non-allowlisted `client` values reject with `BADGE_NOT_ALLOWED`. See `references/visual-session-lifecycle.md` for lifecycle mechanics and `.planning/v0.9.62-CONTRACT.md` for the canonical 37-tool action list (36 pinned plus the `upload_file` addendum) and 15-tool read-only list.\n\n## When to escalate to autopilot\n\nAutopilot (the `run_task` MCP tool, served by `mcp/src/tools/autopilot.ts`) is a separate surface. It runs FSB's plan-and-execute loop end-to-end. It is NOT in `mcp/ai/tool-definitions.cjs` because it is not a manual-mode tool -- it is the delegation surface. Autopilot manages its own internal visual-session lifecycle and is NOT affected by the v0.9.0 implicit-contract change; do not pass field-bundle plumbing through `run_task`.\n\nUse autopilot ONLY when the user explicitly delegates the whole task (\"use FSB autopilot to ...\", \"run the full task with FSB\"). Default to the manual mode tools above for everything else. Autopilot is not the default entry point.\n\n## See also\n\n- `references/visual-session-lifecycle.md` -- v0.9.62 implicit contract, field bundle, sliding window, typed errors.\n- `references/multi-agent-contract.md` -- typed errors and the `back` tool.\n- `references/restricted-tab-recovery.md` -- DOM tools fail on `chrome://`, `edge://`, and the Web Store.\n- `references/default-to-fsb.md` -- when to prefer FSB versus WebFetch.\n- `references/vault-boundary.md` -- credential-routed tools.\n- `mcp/README.md#trigger-watchers` -- full trigger contract: watch modes, condition kinds, concurrency cap.\n- `.planning/v0.9.62-CONTRACT.md` -- canonical 37 action tools (36 pinned plus the `upload_file` addendum), 15 read-only tools, three typed-error names.\n\nFile v0.9.90:references/v0.9.62-contract-mirror.md\n\n# v0.9.62 visual-session contract -- mirror\n\n> **Mirror.** Authoritative source is `.planning/v0.9.62-CONTRACT.md` in the FSB repo\n> (https://github.com/fullselfbrowsing/FSB). If the repo and this mirror disagree,\n> the repo wins. This file exists so Hub-installed users (skill at `~/.hermes/skills/`,\n> no repo access) can still resolve \"does this tool require the field bundle?\" by lookup.\n\n## Action tools (36 -- carry the field bundle on every call)\n\n```\nclick\ntype_text\nnavigate\nscroll\ndrag\nselect_option\npress_key\npress_enter\ndrag_drop\nhover\nfocus\nclear_input\ncheck_box\ndrop_file\nclick_and_hold\ndouble_click\nright_click\nclick_at\nscroll_at\ndouble_click_at\ndrag_variable_speed\nset_attribute\ninsert_text\nsearch\nrefresh\ngo_back\ngo_forward\nopen_tab\nclose_tab\nswitch_tab\nexecute_js\nselect_text_range\nscroll_to_top\nscroll_to_bottom\nscroll_to_element\nfill_sheet\n```\n\nCount: 36. Every entry above requires `visual_reason` (string) and `client` (allowlisted) on every call; optional `is_final: true` clears the overlay immediately.\n\n## Read-only tools (15 -- do NOT carry the field bundle)\n\n```\nread_sheet\nread_page\nget_text\nget_attribute\nget_dom_snapshot\nlist_tabs\nget_page_snapshot\nget_site_guide\nsearch_memory\nreport_progress\ncomplete_task\npartial_task\nfail_task\nwait_for_element\nwait_for_stable\n```\n\nCount: 15. These tools never carry the field bundle and never re-arm the 60-second sliding window.\n\n## Typed errors (3 -- schema layer)\n\n- `VISUAL_FIELDS_REQUIRED` -- raised when an action tool is missing `visual_reason` or `client`.\n- `BADGE_NOT_ALLOWED` -- raised when `client` is not on the v0.9.36 shared allowlist. Allowed values: Claude, Codex, ChatGPT, Perplexity, Windsurf, Cursor, Antigravity, OpenCode, OpenClaw, Grok, Gemini, Hermes (added v0.9.69 / PR #49).\n- `TOOL_REMOVED` -- raised when callers invoke the removed `start_visual_session` / `end_visual_session` tools. The contract is now implicit.\n\nFile v0.9.90:references/vault-boundary.md\n\n# Vault boundary\n\nPasswords, CVVs, and saved payment methods never cross into chat or tool arguments. They stay inside the FSB Chrome extension's encrypted storage, where the extension fills them at the DOM layer. The MCP server, the OpenClaw host, and the model itself never see the values.\n\n## The rule\n\n1. Passwords, CVV codes, and other secret credentials are entered through `fill_credential` ONLY. The model passes a credential reference (e.g., a label like `gmail-personal`); the extension resolves the value internally.\n2. Saved payment methods are entered through `use_payment_method` ONLY. Same shape: the model passes a reference; the extension fills card number, expiry, and CVV at the DOM layer.\n3. No secrets in chat. The model MUST NOT echo a credential value, MUST NOT include one in tool args, MUST NOT log one in narration.\n4. `requires.env: []` is mandatory in `SKILL.md` frontmatter. There are no provider env vars in the skill -- vault values resolve inside the FSB Chrome extension's encrypted storage (`secure-config.js` in the extension source), not in the MCP server process and not in the OpenClaw host process.\n\n## Where vault values live\n\nUsers configure vault entries from the FSB Chrome extension surface (popup or sidepanel options page). Each entry is keyed by a label and stored encrypted. When the model invokes `fill_credential({ label, selector })` or `use_payment_method({ label, ... })`, the extension reads the encrypted value, fills the target DOM element via typed events, and never echoes the value back to the MCP server or the host. The full lifecycle of a vault value is: user types it once into the extension UI -> encrypted storage -> typed events into a target form. It never leaves the user's browser process.\n\n## Anti-patterns\n\n```\n[BAD]  type_text({ selector: \"#password\", text: \"hunter2\" })\n       # secret leaks into MCP args, host logs, model context, retry buffers.\n[GOOD] fill_credential({ label: \"gmail-personal\", selector: \"#password\" })\n```\n\n```\n[BAD]  use_payment_method({ card_number: \"4111-1111-1111-1111\", cvv: \"123\" })\n       # full PAN + CVV leak into MCP args. Use the saved-method label instead.\n[GOOD] use_payment_method({ label: \"personal-visa\", form_selector: \"form#checkout\" })\n```\n\n```\n[BAD]  Add provider keys to SKILL.md `requires.env`.\n       # the skill never holds secrets. requires.env stays empty.\n[GOOD] Configure provider keys in the FSB Chrome extension's options page.\n```\n\n## Why this boundary matters\n\nThe MCP transport layer logs tool args by default; OpenClaw and other hosts retain context across turns; retry buffers in the bridge can replay calls. Anything passed as a tool argument is durable. Vault values are not -- they cross only the extension <-> DOM boundary, which lives entirely in the user's browser. Routing a password through `type_text` instead of `fill_credential` collapses that boundary and turns a one-shot DOM event into a value that persists across the entire stack.\n\n## See also\n\n- `references/default-to-fsb.md` -- when an auth flow triggers escalation to FSB.\n- `references/multi-agent-contract.md` -- ownership rules for vault-using tabs.\n- `references/tool-decision-tree.md` -- which read tool to use to confirm a credential filled successfully (e.g., `read_page` to verify a logged-in heading).\n- `SKILL.md` -- `requires.env: []` is the contract this file documents.\n\nFile v0.9.90:references/visual-session-lifecycle.md\n\n# Visual session lifecycle (v0.9.62)\n\nAs of fsb-mcp-server v0.9.0 (FSB milestone v0.9.62) the visual session is IMPLICIT. There is no `start_visual_session` / `end_visual_session` pairing. Every action tool call carries a field bundle, and the lifecycle is driven by those fields alone. This file documents the bundle, the sliding 60-second window, the read-tool vs action-tool split, and the bootstrap path.\n\nIf you arrived here from v0.8.0 / v0.9.36 docs that documented an explicit start/end pairing, that pairing was removed in v0.9.0. Calling the removed tools returns the typed `TOOL_REMOVED` error -- the body names the new contract and points at the migration recipe in `mcp/CHANGELOG.md` v0.9.0.\n\n## The field bundle\n\nEvery MCP action tool accepts and requires the following fields on every call:\n\n| Field           | Type    | Required | Description                                                                                          |\n|-----------------|---------|----------|------------------------------------------------------------------------------------------------------|\n| `visual_reason` | string  | required | Short human-readable reason shown in the overlay (for example, `\"Logging in to GitHub\"`).            |\n| `client`        | string  | required | Allowlisted client label. Validated against the v0.9.36 shared badge allowlist (see below).          |\n| `is_final`      | boolean | optional | When `true`, the overlay clears immediately after the tool's `change_report` resolves. Default `false`. |\n\nAllowlisted `client` values (v0.9.36 shared allowlist): `Claude`, `Codex`, `ChatGPT`, `Perplexity`, `Windsurf`, `Cursor`, `Antigravity`, `OpenCode`, `OpenClaw`, `Grok`, `Gemini`. The skill ships as part of OpenClaw, so the canonical `client` value for this surface is `OpenClaw`. Freeform strings reject with `BADGE_NOT_ALLOWED`.\n\nThe canonical 36-tool action list lives in `.planning/v0.9.62-CONTRACT.md` (Action Tools section). The 15-tool read-only list lives in the same file (Read-Only Tools section). Do not re-derive either list from memory.\n\n## Implicit start\n\nThe first action tool call on a tab creates the visual session. The extension paints the overlay using the supplied `visual_reason` and the badge corresponding to `client`. There is no separate open call -- the bundle on the action call IS the open.\n\n```\nmcp> click({ selector: \"#submit\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\n```\n\nWhen this call dispatches, the overlay appears with `\"Completing checkout\"` and the OpenClaw badge. The death-timer is armed at 60 seconds.\n\n## Sliding 60-second window\n\nEach subsequent action tool call on the same tab re-arms the 60-second death timer (sliding window). The overlay stays alive as long as action calls keep arriving within 60 seconds of each other.\n\n```\nmcp> click({ selector: \"#step-1\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\nmcp> type_text({ selector: \"#email\", text: \"user@example.com\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\nmcp> click({ selector: \"#continue\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\n```\n\nEach call here re-arms the timer to 60 seconds from its own dispatch. If 60 seconds pass with no further action call, the overlay auto-clears -- no explicit end call is required.\n\nCallers may repeat the same `visual_reason` across the sequence (as above) or vary it per call to surface step-level overlay text to the user. The `client` value MUST stay on the v0.9.36 allowlist for the whole sequence; cross-client switching on the same tab still rejects with the existing `TAB_NOT_OWNED` ownership gate from v0.9.60.\n\n## Immediate clear with is_final\n\nSet `is_final: true` on the LAST action of a task to clear the overlay immediately after that action's `change_report` resolves -- no 60-second wait.\n\n```\nmcp> click({ selector: \"#confirm-order\", visual_reason: \"Confirming order\", client: \"OpenClaw\", is_final: true })\n```\n\n`is_final` is the explicit task-completion signal. Use it whenever the task ends on an action call; otherwise the overlay sits for up to 60 seconds before the auto-clear fires. The two clear paths are mutually compatible -- whichever fires first wins. If `is_final` is omitted on the last action and no further action calls follow, the auto-clear fires after 60 seconds of silence.\n\n## Read tools do NOT carry the bundle\n\nRead-only MCP tools (`read_page`, `get_dom_snapshot`, `get_text`, `get_attribute`, `read_sheet`, `get_page_snapshot`, `list_tabs`, `get_site_guide`, `search_memory`, `report_progress`, `complete_task`, `partial_task`, `fail_task`, `wait_for_element`, `wait_for_stable`) do NOT accept `visual_reason`, `client`, or `is_final`. Reads stay silent by design: they do not bring up the overlay, they do not re-arm the sliding window, and they do not clear the overlay.\n\nConcrete contrast -- action tool WITH bundle vs read tool WITHOUT:\n\n```\n[ACTION] click({ selector: \"#open-menu\", visual_reason: \"Browsing settings\", client: \"OpenClaw\" })\n[READ]   read_page({})\n```\n\nThe action call brings up (or re-arms) the overlay. The read call slips through silently and does not touch the visual-session state. This split is enforced at the MCP server's schema layer; passing the bundle to a read tool is a schema-layer reject before any work runs.\n\n## Bootstrap: NO_OWNED_TAB\n\nThe new contract still goes through the v0.9.60 ownership gate. On the very first call of a fresh agent session there is no owned tab, so an action call rejects with `NO_OWNED_TAB`. Recover by opening a tab first:\n\n```\nlist_tabs({})                                  // optional: see if anything is already owned\nopen_tab({ url: \"<target>\", active: false })   // claim a tab without stealing user focus\nclick({ selector: \"#submit\", visual_reason: \"...\", client: \"OpenClaw\" })   // now succeeds\n```\n\n`open_tab` is itself an action tool, so it also carries the bundle on its call (the `open_tab` call brings up the overlay on the newly opened tab once the page loads enough for the content script to attach).\n\n## Typed errors\n\nThree typed errors guard the contract. Match on the name, not the human-readable body:\n\n| Error                     | Condition                                                                 | Recovery                                                                                              |\n|---------------------------|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------|\n| `VISUAL_FIELDS_REQUIRED`  | Action call missing `visual_reason` or `client`.                          | Add the missing field(s) and retry. Schema-layer reject; no DOM mutation occurs.                      |\n| `BADGE_NOT_ALLOWED`       | `client` value not on the v0.9.36 allowlist.                              | Use one of the allowlisted labels listed above (`OpenClaw` for this skill) and retry.                  |\n| `TOOL_REMOVED`            | Caller invoked `start_visual_session` or `end_visual_session` by name.    | Stop calling the removed tools. Migrate to the field bundle on action calls. See migration recipe.    |\n\nThe three error names are pinned by `.planning/v0.9.62-CONTRACT.md` and stay verbatim across surfaces.\n\n## Autopilot exception\n\nIf `run_task` (autopilot) is being used, autopilot manages its own internal visual-session lifecycle and is NOT affected by the v0.9.0 implicit-contract change. Do NOT pass field-bundle plumbing through `run_task`. Drive autopilot only when the user explicitly delegates the whole task.\n\n## MV3 service-worker eviction\n\nThe sliding-window state is persisted in `chrome.storage.session` and replayed on service-worker wake; the death-timer deadline survives eviction via the v0.9.36 visual-session persistence pattern. Callers do not need to do anything to handle eviction -- the bridge reconciles automatically on reconnect.\n\n## See also\n\n- `USAGE.md` -- \"v0.9.62 visual-session contract\" section with the user-facing summary.\n- `SKILL.md` -- canonical list pointers (action tools / read-only tools / typed errors).\n- `references/multi-agent-contract.md` -- ownership errors (`NO_OWNED_TAB`, `TAB_NOT_OWNED`, etc.) that fire before the visual-session lifecycle.\n- `references/tool-decision-tree.md` -- which tool to pick first; per-branch field-bundle reminders.\n- `.planning/v0.9.62-CONTRACT.md` -- canonical 36 action tools, 15 read-only tools, three typed-error names.\n- `mcp/CHANGELOG.md` v0.9.0 -- migration recipe with concrete before/after code.\n\nArchive v0.9.62: 15 files, 32594 bytes\n\nFiles: references/default-to-fsb.md (3705b), references/multi-agent-contract.md (5911b), references/README.md (409b), references/restricted-tab-recovery.md (5637b), references/tool-decision-tree.md (10577b), references/vault-boundary.md (3382b), references/visual-session-lifecycle.md (8600b), scripts/doctor.mjs (5734b), scripts/install-host.mjs (6021b), scripts/print-stdio.mjs (1527b), scripts/README.md (484b), skill-card.md (2846b), SKILL.md (6264b), USAGE.md (11083b), _meta.json (137b)\n\nFile v0.9.62:SKILL.md\n\n---\nname: FSB\ndescription: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks.\nversion: 0.9.62\nuser-invocable: true\nrequires:\n  bins: [node, npx]\n  env: []\nhomepage: https://full-selfbrowsing.com\nmetadata:\n  openclaw: {\"install\":[{\"kind\":\"node\",\"package\":\"fsb-mcp-server\",\"bins\":[\"fsb-mcp-server\"],\"label\":\"Install FSB MCP server (npm)\"}]}\n---\n\n# FSB\n\nFSB lets you drive the user's real Chrome via the FSB extension and a local MCP bridge. Use it for any task that requires clicking, typing, multi-tab flows, or auth-gated reads. Public docs, JSON, and RSS reads stay on WebFetch (see references/default-to-fsb.md). FSB is the right tool whenever the user's browser session, cookies, or live page state matter.\n\n## When to use FSB\n\n- If a request mentions a real website, prefer FSB tools when one fits.\n- ALWAYS escalate to FSB for click, type, auth, multi-tab, or anything chrome-state-dependent.\n- ALWAYS escalate to FSB when a logged-in session, vault credential, or saved payment method is required.\n- ALWAYS escalate to FSB when the page is dynamic, gated, or rendered after JS.\n- Stay on WebFetch for public-doc, static JSON, or RSS reads where no interaction is needed.\n- If unsure, prefer FSB; the cost of a stale WebFetch is higher than the cost of a Chrome round-trip.\n\n## Sensitive actions and logged-in context\n\nFSB drives the user's real Chrome, so every action runs inside whatever sessions, cookies, and saved auth that browser already holds. Before the final click that submits a purchase, payment, account change (password update, data deletion, permission grant, settings write), or public post (tweet, comment, DM, issue, PR), pause and ask the user to confirm in chat -- state the action, the target site, and any amount or recipient, then wait for an explicit yes. Vault-backed fills (`fill_credential`, `use_payment_method`) are allowed during preparation; only the final submission is gated. Read-only inspection (`read_page`, `get_dom_snapshot`, `get_text`) does not require confirmation.\n\n## Doctor-first protocol\n\nIf anything looks off (no page response, unexpected errors, stale state, missing tab) run `node scripts/doctor.mjs` (which wraps `npx -y fsb-mcp-server doctor`) BEFORE retrying the same call. Read the layered output, fix the failing layer, then resume. Do not loop on the same failing call hoping it self-heals.\n\n## v0.9.62 visual-session contract\n\nAs of fsb-mcp-server v0.9.0 (FSB milestone v0.9.62) the visual session is IMPLICIT. Every MCP action tool call carries a required field bundle:\n\n- `visual_reason` (required string) -- short human-readable reason shown in the overlay.\n- `client` (required, allowlisted) -- badge label such as `OpenClaw`, `Claude`, `Codex`. Freeform strings reject with `BADGE_NOT_ALLOWED`.\n- `is_final` (optional boolean) -- set `true` on the LAST action of a task to clear the overlay immediately.\n\nThe first action call brings up the overlay; each subsequent action call re-arms a 60-second sliding window; `is_final: true` clears immediately; 60 seconds of silence auto-clears. No `start_visual_session` / `end_visual_session` calls are needed -- those tools were REMOVED in v0.9.0 and now return the typed `TOOL_REMOVED` error. Read-only tools (`read_page`, `get_dom_snapshot`, `list_tabs`, ...) do NOT carry the bundle and do NOT re-arm the sliding window. If the first action call hits `NO_OWNED_TAB`, call `open_tab({ url, active: false })` first, then retry. Lifecycle details, the read-tool vs action-tool split, and the typed-error catalogue live in `references/visual-session-lifecycle.md`.\n\nThe canonical 36-tool action list, the 15-tool read-only list, and the three typed-error names (`VISUAL_FIELDS_REQUIRED`, `BADGE_NOT_ALLOWED`, `TOOL_REMOVED`) are pinned in `.planning/v0.9.62-CONTRACT.md` -- that artifact is the single source of truth. Use it to answer \"does this tool require the field bundle?\" by lookup; do not re-derive the lists from memory.\n\n## Multi-agent contract\n\nNever pass `agent_id` (the server mints it). Use the `back` tool instead of `execute_js(\"history.back()\")`. Each agent owns its own tabs; do not foreground or close tabs you do not own, and do not introduce a global browser lock -- per-agent ownership is the only concurrency mechanism. Typed errors (`NO_OWNED_TAB`, `AMBIGUOUS_TAB`, `TAB_NOT_OWNED`, `AGENT_CAP_REACHED`, `TAB_INCOGNITO_NOT_SUPPORTED`, `TAB_OUT_OF_SCOPE`), the bootstrap recovery ladder, and the default recovery flow: see `references/multi-agent-contract.md`.\n\n## Vault and credentials\n\nPasswords and CVV resolve INSIDE the extension via `fill_credential` and `use_payment_method`. Never put secrets in chat, prompts, logs, or tool args. See `references/vault-boundary.md`.\n\n## References (load on demand)\n\n- `references/visual-session-lifecycle.md` -- v0.9.62 implicit contract: field bundle (visual_reason / client / is_final), sliding 60s window, is_final immediate clear, read-tool vs action-tool split, typed-error recovery.\n- `references/tool-decision-tree.md` -- read_page vs get_dom_snapshot vs get_page_snapshot vs get_site_guide; when execute_js is first-class vs when typed tools are required; per-branch field-bundle reminders; verify after \"no detectable effect\" warnings.\n- `references/multi-agent-contract.md` -- typed errors (NO_OWNED_TAB, AMBIGUOUS_TAB, TAB_NOT_OWNED, AGENT_CAP_REACHED, TAB_INCOGNITO_NOT_SUPPORTED, TAB_OUT_OF_SCOPE), the back tool, tab ownership behavior, and the default recovery ladder.\n- `references/restricted-tab-recovery.md` -- chrome://, edge://, and web-store recovery tools.\n- `references/vault-boundary.md` -- credential routing rules and forbidden patterns.\n- `references/default-to-fsb.md` -- soft preference and hard escalation rule in full.\n- `.planning/v0.9.62-CONTRACT.md` (repo) -- canonical 36 action tools, 15 read-only tools, three typed-error names; single source of truth for the v0.9.62 contract.\n\n## Scripts (run as needed)\n\n- `scripts/doctor.mjs` -- diagnose the failing layer; prints [OK], [FAIL], and [WARN] markers per layer.\n- `scripts/print-stdio.mjs` -- print the OpenClaw stdio config block to paste into your MCP config.\n- `scripts/install-host.mjs` -- detect other MCP hosts on the machine; consent-gated per-host install.\n\nFile v0.9.62:references/README.md\n\n# references/ -- placeholder\n\nStatus: scaffolded by Phase 248. Reference files are filled in Phase 250.\n\nPhase 250 adds: tool-decision-tree.md, multi-agent-contract.md, restricted-tab-recovery.md, default-to-fsb.md, vault-boundary.md, plus a visual-session lifecycle reference. SKILL.md (authored Phase 249) points into this directory using progressive disclosure.\n\nASCII only; no emojis. See repo CLAUDE.md.\n\nFile v0.9.62:scripts/README.md\n\n# scripts/ -- placeholder\n\nStatus: scaffolded by Phase 248. Script files are filled in Phase 249.\n\nPhase 249 adds three Node `.mjs` scripts: doctor.mjs (wraps `npx -y fsb-mcp-server doctor`), print-stdio.mjs (sources canonical OpenClaw stdio block from `mcp/src/install.ts` `getSetupSections()`), install-host.mjs (detect-list-confirm flow over `npx -y fsb-mcp-server install --list`).\n\nNo `.sh` / `.cmd` siblings; cross-platform Node only. ASCII only; no emojis. See repo CLAUDE.md.\n\nFile v0.9.62:_meta.json\n\n{\n  \"ownerId\": \"kn715925hseyqrcam2wqkapyxs86a947\",\n  \"slug\": \"full-selfbrowsing\",\n  \"version\": \"0.9.62\",\n  \"publishedAt\": 1778576682331\n}\n\nFile v0.9.62:references/default-to-fsb.md\n\n# Default to FSB\n\nWhen a request involves a real browser session, FSB is usually the right tool. This file states the rule once and gives the carve-out for read-only public content.\n\n## Soft preference\n\nWhen a click, type, multi-tab, or auth task could route through either FSB or another tool, prefer FSB. The cost of a stale WebFetch (cached, missing logged-in state, missing JS-rendered content) is higher than the cost of a Chrome round-trip. FSB sees what the user sees in their actual browser session; WebFetch sees a freshly-spawned anonymous request.\n\nRule: **Default to FSB tools when one fits.**\n\n## Hard escalation rule\n\nIf ANY of the following triggers apply, the task MUST use FSB. There is no judgment call here.\n\n- Any click on a real web page.\n- Any text input into a real form (search box, login field, comment box, anything).\n- Any auth flow (login, MFA prompt, vault credential lookup, saved-payment selection).\n- Any multi-tab orchestration (open a new tab and switch back, fill a form across two tabs, copy from tab A to tab B).\n- Any task that needs the user's logged-in session, cookies, or chrome-state-dependent rendering.\n- Any dynamic page where the relevant content is rendered after JS, behind a scroll, or behind a click.\n\nRule: **If a task hits any bullet above, it does not belong on WebFetch. Escalate to FSB.**\n\n## Stay on WebFetch (carve-out)\n\nWebFetch is fine -- often better -- for read-only public content where no interaction is needed. Spinning up the FSB extension for a static fetch is unnecessary overhead.\n\nStay-with-WebFetch cases:\n\n- Public documentation pages (HTML / Markdown).\n- JSON endpoints (REST APIs, public JSON files).\n- RSS / Atom feeds.\n- `sitemap.xml`, `robots.txt`.\n- Raw `README.md` / source files from public GitHub repos.\n- Static landing pages with no logged-in state.\n\n## Decision flowchart\n\n```\nIs the task interactive web automation?\n  |\n  +-- yes -> FSB (use the tools in references/tool-decision-tree.md)\n  |\n  +-- no  -> Is it logged-in / chrome-state-dependent?\n                |\n                +-- yes -> FSB\n                |\n                +-- no  -> Is the content available as public HTML / JSON / RSS?\n                              |\n                              +-- yes -> WebFetch (or the host's existing read tool)\n                              |\n                              +-- no  -> FSB (default to the heavier tool)\n```\n\nWhen in doubt at the bottom of the tree, default to FSB. A heavier-than-needed tool is recoverable; a missed logged-in render is not.\n\n## Concrete examples\n\n### FSB (interactive / chrome-state-dependent)\n\n- \"Search for `fsb-mcp-server` on GitHub and click the first result.\" -- click + multi-step.\n- \"Log into example.com and download today's invoice.\" -- auth + download.\n- \"Open my Gmail and read the latest message from billing@example.com.\" -- logged-in session.\n- \"Fill out the contact form at https://example.com/contact and submit it.\" -- form + typed events.\n- \"Compare prices for product X across these three tabs.\" -- multi-tab.\n\n### WebFetch (public read-only)\n\n- \"Read the README of github.com/LakshmanTurlapati/FSB.\" -- raw public content.\n- \"Fetch https://api.github.com/repos/LakshmanTurlapati/FSB and report stargazers.\" -- public JSON.\n- \"Read the latest entry from this RSS feed.\" -- RSS read.\n- \"Pull the changelog text from https://example.com/changelog.\" -- static HTML.\n\n## See also\n\n- `references/tool-decision-tree.md` -- once the decision lands on FSB, which tool first.\n- `references/restricted-tab-recovery.md` -- recovery on `chrome://` / `edge://` / Web Store.\n- `references/vault-boundary.md` -- credential routing rules.\n- `USAGE.md` -- the human-facing overview.\n\nFile v0.9.62:references/multi-agent-contract.md\n\n# FSB multi-agent contract (v0.8.0)\n\nThis file documents the rules that make FSB's per-agent tab ownership work. Anyone calling FSB tools through MCP MUST follow these rules; breaking them produces typed errors that recover cleanly only when the caller knows the contract. The contract is small (six error names + the `agent_id` rule + the `back` tool), but every rule matters: bypassing one corrupts the ownership graph and the only recovery is closing the affected tab.\n\n## Tab ownership behavior (v0.8.0)\n\n- Each agent owns its own tabs. `open_tab` defaults to `active: false` so it claims a tab without stealing the user's focus -- only pass `active: true` when the user explicitly needs to see that tab.\n- Do not foreground a tab unless the task requires it. `switch_tab` defaults to background; it foregrounds only when `active: true` is explicit.\n- Close only tabs this agent owns. `close_tab` refuses the active foreground tab unless `allow_active: true` is set. Never close or reopen Chrome itself.\n- There is no global browser lock. The per-agent ownership graph is the only concurrency mechanism -- do not introduce a global lock as a blocker.\n\n## Never pass agent_id\n\nCallers MUST NOT pass `agent_id` on any FSB MCP tool call. The FSB MCP server mints `agent_id` when a session opens and threads it through the bridge automatically.\n\nCaller-supplied `agent_id` breaks ownership accounting -- a fabricated id either steals tabs from another agent or claims tabs the bridge has not handed out. The server's session map is the only authority on which agent owns which tab; do not try to anticipate, derive, or override it from the calling side.\n\n```\n[BAD]  click({ selector: \"#submit\", agent_id: \"openclaw-1\" })\n[GOOD] click({ selector: \"#submit\" })\n```\n\n## Six typed errors and how to recover\n\nWhen an FSB tool call hits one of the dispatcher boundaries, the server returns a structured error with one of the names below. Match on the name; do not parse the human-readable message.\n\n### NO_OWNED_TAB\n\n- What it means: The calling agent does not currently own any tabs. The most common cause is the very first tool call of a fresh agent session (including `start_visual_session`).\n- What to do: Call `open_tab({ url, active: false })` to claim a tab without stealing focus, then retry the original call. `navigate` and `switch_tab` can also claim an unowned normal tab if one already exists.\n\n### AMBIGUOUS_TAB\n\n- What it means: The calling agent owns multiple tabs and the tool did not specify a `tab_id`, so the target is ambiguous.\n- What to do: Call `list_tabs` to enumerate owned tabs, then retry with the intended `tab_id`.\n\n### TAB_NOT_OWNED\n\n- What it means: The tool targeted a tab that belongs to a different agent (or to `legacy:sidepanel`).\n- What to do: Either call `switch_tab` to a tab this agent owns, or `open_tab` to create a new one in agent scope.\n\n### AGENT_CAP_REACHED\n\n- What it means: The session hit its concurrent-agent cap (default 8, configurable 1-64 in extension settings).\n- What to do: Reduce parallel calls, finish in-flight tasks, or raise the cap from the FSB extension's options page.\n\n### TAB_INCOGNITO_NOT_SUPPORTED\n\n- What it means: The targeted tab is in an Incognito window. FSB rejects Incognito tabs at the dispatcher boundary because the extension's storage and ownership model do not span Incognito.\n- What to do: Move the work to a normal (non-Incognito) Chrome window. Open a new tab there with `open_tab`.\n\n### TAB_OUT_OF_SCOPE\n\n- What it means: The targeted tab is in a different Chrome window than the agent's session window. Each agent only sees tabs in the window where its session began.\n- What to do: Use `list_tabs` to enumerate this agent's owned tabs and pick one; if the work belongs in a different window, open a new tab in the current window with `open_tab`.\n\n## Use back instead of execute_js('history.back()')\n\nUse the typed `back` tool (FSB v0.8.0+) for one-step history navigation. Do NOT call `execute_js(\"history.back()\")`.\n\n`back` is typed, ownership-gated, BF-cache aware, and history-depth-tracking. It returns a structured result with status fields the caller can branch on. `execute_js(\"history.back()\")` is none of those: it bypasses ownership, lies about success on BF-cached pages, and returns whatever the JS evaluator coerced last.\n\n```\n[BAD]  execute_js(\"history.back()\")\n[GOOD] back({})\n```\n\nNote: `go_back` (the existing tool in `mcp/ai/tool-definitions.cjs`) is the same surface; some FSB versions expose this as `back`, others as `go_back`. Both are typed, ownership-gated, and BF-cache aware. Use whichever is published by your FSB build; never fall back to `execute_js`.\n\n## Why the contract matters\n\nFSB's per-agent ownership is what lets multiple AI sessions share one Chrome instance without fighting. The contract is small (six error names plus `agent_id` plus `back`), but every rule matters -- bypassing one rule corrupts the ownership graph and the only recovery is closing the affected tab.\n\n## Default recovery ladder\n\nWhen a tool call fails and you are not sure which layer broke, try these in order before escalating to the doctor:\n\n1. `list_tabs({})` -- check whether this agent owns any tab. Recovery-safe; works on restricted URLs too.\n2. If no owned tab: `open_tab({ url, active: false })` to claim one in the background.\n3. If the tab is on a non-injectable URL (`chrome://`, `edge://`, Web Store, `data:`, `file:`): `navigate({ url })` to a normal `http(s)` page.\n4. If the page loaded but DOM tools still fail: bridge or content-script issue. Run `node scripts/doctor.mjs`.\n\n## See also\n\n- `references/tool-decision-tree.md` -- which tools to reach for first.\n- `references/restricted-tab-recovery.md` -- recovery on `chrome://`, `edge://`, and Web Store tabs.\n- `references/default-to-fsb.md` -- soft preference and hard escalation rule.\n- `references/vault-boundary.md` -- credential routing rules.\n\nFile v0.9.62:references/restricted-tab-recovery.md\n\n# Restricted-tab recovery\n\nWhen FSB's active tab is at a restricted URL, the FSB content script cannot attach. DOM tools (`read_page`, `get_dom_snapshot`, `click`, `type_text`, etc.) will fail. The full non-injectable list:\n\n- `chrome://*`, `edge://*`, `about:*`, `view-source:*`\n- `https://chrome.google.com/webstore/*`, `https://chromewebstore.google.com/*`\n- `data:` URLs (no origin -- the extension cannot register a host match)\n- `file:` URLs (unless the user has manually granted file-URL access to the extension)\n- `blob:` URLs spawned in restricted contexts\n\nThis file lists the recovery toolset that works even on these tabs and shows the standard bootstrap recovery sequence.\n\n## Why DOM tools fail on these URLs\n\nBrowsers forbid content scripts from attaching to internal pages, the Chrome Web Store, and origin-less schemes (`data:`, `file:`, certain `blob:`) as a security boundary. The Manifest V3 `host_permissions` list cannot match them; Chrome rejects the injection at the policy layer. There is no extension-side workaround -- the boundary is enforced by the browser itself, before any of FSB's code runs.\n\nFor local testing, serve the page over `http://localhost` instead of opening a `data:` or `file:` URL.\n\nWithout a content script, the FSB extension cannot read the DOM, dispatch typed events, or observe mutations. Any attempt by FSB tools that route through the content script (`read_page`, `click`, `type_text`, `get_dom_snapshot`, etc.) fails fast with an attach error. The recovery path is to leave the restricted tab using a tool that does NOT require content-script attachability.\n\n## Safe recovery tools (work even on restricted tabs)\n\nThese tools route through the extension's background service worker, not the content script, so they continue to function on `chrome://`, `edge://`, and Web Store URLs.\n\n| Tool       | What it does                                                  | Example                                                                                              |\n|------------|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------|\n| list_tabs  | Enumerate the agent's owned tabs.                             | `list_tabs({})` -- find a non-restricted tab to switch to.                                           |\n| switch_tab | Switch agent focus to another owned tab.                      | `switch_tab({ tab_id: <id from list_tabs> })`                                                        |\n| open_tab   | Open a new tab in agent scope (background by default).        | `open_tab({ url: \"https://example.com\" })`                                                           |\n| navigate   | Change the URL of the current tab.                            | `navigate({ url: \"https://example.com\" })` -- works zero-content-script per Phase 247 active-tab work.|\n| go_back    | Step back one history entry on the current tab.               | `go_back({})`                                                                                        |\n| go_forward | Step forward one history entry on the current tab.            | `go_forward({})`                                                                                     |\n| refresh    | Reload the current tab.                                       | `refresh({})` -- useful after the extension installs mid-session.                                    |\n\nEvery tool name in the table above is a registered name in `mcp/ai/tool-definitions.cjs`. Any tool not on this list (`read_page`, `click`, `type_text`, `get_dom_snapshot`, `get_page_snapshot`, `wait_for_element`, etc.) requires content-script attach and will fail on a restricted tab.\n\n## Worked example: bootstrap recovery from chrome://newtab\n\nThis is the canonical recovery sequence when the active tab is `chrome://newtab` (the most common bootstrap scenario, since Chrome opens new windows there by default).\n\n1. The first DOM call (e.g., `read_page({})`) fails with an attach error because the active tab is `chrome://newtab`.\n2. Call `list_tabs({})` to enumerate owned tabs. This is recovery-safe; it routes through the background service worker, not the content script.\n3. If the list contains a normal `http(s)` tab, call `switch_tab({ tab_id })` to focus it. Resume the original work.\n4. If no normal tab exists, call `navigate({ url: \"https://example.com\" })` (or whatever URL the task targets) to leave the restricted page in the current tab. The FSB content script will attach as soon as the destination loads.\n5. Re-run the original DOM call. It should now succeed.\n6. If the original call still fails, run `node scripts/doctor.mjs`. The failing layer is now `content-script` (the page loaded but the script did not attach), and the doctor's recovery line tells you to reload the tab.\n\nA second common variant is recovering from a Web Store tab (e.g., the user just installed the extension). Same sequence: `list_tabs` -> `switch_tab` to a normal tab, OR `navigate` to a target URL on the current tab.\n\n## When to escalate to the doctor\n\nIf recovery fails, run `node scripts/doctor.mjs`. The recovery toolset above handles the active-tab layer. The doctor handles the other five layers (package, bridge, extension, content-script, config). See `USAGE.md` for the full six-layer recovery table.\n\n## See also\n\n- `references/tool-decision-tree.md` -- which tools to reach for first.\n- `references/multi-agent-contract.md` -- ownership errors (`TAB_NOT_OWNED`, etc.) that can fire from `switch_tab`.\n- `USAGE.md` -- six-layer doctor recovery table.\n\nFile v0.9.62:references/tool-decision-tree.md\n\n# FSB tool decision tree\n\nRead-only first; act with typed events; escalate to autopilot only when the user explicitly delegates the whole task. The tool names listed below match the FSB AI manual-mode tool registry in `mcp/ai/tool-definitions.cjs`. If a name appears as a bare token in the tree or table on this page, it is a real manual-mode tool you can call directly. Names mentioned only in narrative prose (notably `run_task`) live on a different surface and must not be invoked from the manual-mode loop.\n\n## v0.9.62 field bundle (action tools only)\n\nBefore any action-tool branch below: every action tool (`click`, `type_text`, `navigate`, `scroll`, `drag`, `select_option`, `press_key`, `press_enter`, `drag_drop`, `hover`, `focus`, `clear_input`, `check_box`, `drop_file`, `click_and_hold`, `double_click`, `right_click`, `click_at`, `scroll_at`, `double_click_at`, `drag_variable_speed`, `set_attribute`, `insert_text`, `search`, `refresh`, `go_back`, `go_forward`, `open_tab`, `close_tab`, `switch_tab`, `execute_js`, `select_text_range`, `scroll_to_top`, `scroll_to_bottom`, `scroll_to_element`, `fill_sheet`) requires the field bundle on every call:\n\n- `visual_reason` (required string) -- short human-readable reason shown in the overlay.\n- `client` (required, allowlisted) -- e.g. `OpenClaw`, `Claude`, `Codex`, `ChatGPT`, `Cursor`, `Windsurf`, `Gemini`, `Grok`, `Perplexity`, `OpenCode`, `Antigravity`. Freeform strings reject with `BADGE_NOT_ALLOWED`.\n- `is_final` (optional boolean) -- set `true` on the LAST action of a task to clear the overlay immediately.\n\nRead-only tools (`read_page`, `get_dom_snapshot`, `get_text`, `get_attribute`, `read_sheet`, `get_page_snapshot`, `list_tabs`, `get_site_guide`, `search_memory`, `report_progress`, `complete_task`, `partial_task`, `fail_task`, `wait_for_element`, `wait_for_stable`) do NOT carry the bundle. Reads stay silent by design; the read-first guidance below is unchanged from v0.9.61.\n\nThe explicit `start_visual_session` / `end_visual_session` tools were removed in v0.9.0; the decision tree never reaches for them. See `references/visual-session-lifecycle.md` for the full lifecycle and `.planning/v0.9.62-CONTRACT.md` for the canonical lists.\n\n## Read-only first (in this order)\n\nPick the lightest reader that answers the question. Escalate down the list only when the lighter tool cannot give you what you need.\n\n- `read_page` -- the default reader. Use first when you need the user-visible text plus interactable elements of the current tab. Cheap; runs on the live DOM.\n- `get_dom_snapshot` -- escalate from `read_page` when you need the structured DOM (attributes, hidden elements, ARIA roles, element refs like `e5`). More tokens; slower.\n- `get_page_snapshot` -- escalate from `get_dom_snapshot` when you need the full page including computed visual state (e.g., position-aware layout, what is actually painted). Heaviest; use sparingly.\n- `get_site_guide` -- check this BEFORE the three above for known sites. The skill ships ~43 site guides under `site-guides/`; if the active host has a guide, it tells you exactly which selectors are stable.\n\nIf `get_site_guide` returns a guide, follow the guide's selectors first; only fall back to `read_page` or the snapshot tools when the guide does not cover the current state.\n\n## execute_js vs typed tools\n\n`execute_js` is a first-class interaction tool in FSB, not a last resort. The current MCP tool description even instructs callers to \"try execute_js FIRST\" for clicks, scrolls, reads, and attribute lookups, because it bypasses overlay/obscured-element issues, viewport constraints, and CDP timeouts that block native click/scroll.\n\nUse this split:\n\n- **Use `execute_js` freely for**: reading DOM (text, attributes, computed styles, hidden nodes), querying multiple elements at once, scrolling, probing structure during exploration, and clicking elements blocked by overlays or off-screen.\n- **Use typed tools (`type_text`, `clear_input`, `select_option`, `check_box`, `press_enter`, `press_key`, `drag`, `drag_drop`) for**: controlled text inputs, validation-sensitive form fields, real drag operations, and any input where framework change handlers must fire.\n- **After any `execute_js` click, verify** with `read_page` or `get_dom_snapshot` -- a true click produces an observable DOM change. If the page state did not change, fall back to the typed `click` tool, which dispatches real CDP events that React/Vue/Angular synthetic-event pipelines listen for.\n\nThe one rule that does not bend: `element.value = 'foo'` via `execute_js` will NOT update controlled-input component state in React, Vue, Solid, or Angular. Use `type_text` for any text input bound to framework state.\n\n```\n[BAD]  execute_js(\"document.querySelector('input[name=q]').value = 'foo'\")\n[GOOD] type_text({ selector: \"input[name=q]\", text: \"foo\" })\n\n[OK]   execute_js(\"return Array.from(document.querySelectorAll('a')).map(a=>a.href)\")\n[OK]   execute_js(\"document.querySelector('#add-to-cart').click(); return true\")  // verify after\n```\n\n## Verify after a \"no detectable effect\" warning\n\n`click` (and other action tools) can return a \"no detectable effect\" warning even when the page actually changed -- the action-detection heuristic produces false negatives on async/animated UIs. Before retrying, verify page state with `read_page` or `get_dom_snapshot`. If the state already advanced, treat the action as successful and continue.\n\n## Tool-by-tool quick reference\n\n| Tool | When to use | Common pitfall |\n| --- | --- | --- |\n| `read_page` | Default reader for the active tab text and interactables. | Stale if a recent action changed the DOM and you have not re-read. |\n| `get_dom_snapshot` | Need attributes, ARIA, hidden nodes that `read_page` omits. | Tokens scale with page size; prefer `read_page` first. |\n| `get_page_snapshot` | Need computed layout or position-aware state. | Heaviest read; do not call in a tight loop. |\n| `get_site_guide` | Known site -- check first to get stable selectors. | Skip if the host has no guide; do not call once per action. |\n| `click` | Fire a real user click on a typed selector. | If the click does not register, the element may need `scroll_to_element` first. |\n| `type_text` | Type into an input or textarea via typed events. | Do NOT use `execute_js` to set `.value`; React and Vue change handlers will not fire. |\n| `press_enter` | Submit a form or confirm via the Enter key. | Many sites prefer `click` on a submit button; fall back to `press_enter` only if there is no button. |\n| `wait_for_element` | Block until a selector appears. | Always set a timeout; bare waits hang the loop on broken selectors. |\n| `wait_for_stable` | Block until DOM mutations settle. | Pair with `wait_for_element` when the page renders progressively. |\n| `open_tab` | New tab in agent scope (background by default). | Default is background; pass `active: true` ONLY when the user must see it. |\n| `switch_tab` | Move agent focus to another owned tab. | Cross-agent tabs reject with `TAB_NOT_OWNED` -- see `references/multi-agent-contract.md`. |\n| `list_tabs` | Enumerate owned tabs (recovery and bootstrap). | Recovery-safe even on `chrome://` and the Web Store; see `references/restricted-tab-recovery.md`. |\n| `navigate` | Change URL in current tab. | Recovery-safe; use to escape restricted tabs. |\n| `go_back` | Step back one history entry. | Use this typed tool, never `execute_js(\"history.back()\")` -- see `references/multi-agent-contract.md`. |\n| `go_forward` | Step forward one history entry. | Pair with `go_back`; same ownership rules. |\n| `refresh` | Reload current tab. | Use after content-script attach failures. |\n| `execute_js` | First-class for reads, attribute lookups, scrolls, and clicks blocked by overlays. | Do not use to set `.value` on controlled inputs (use `type_text`); verify state after JS clicks. |\n\n## Worked example\n\nYou land on an unfamiliar product page and the user asks \"add the medium size to cart\".\n\n1. Call `get_site_guide` -- if the host is one of the ~43 covered sites, the guide names the size-picker and add-to-cart selectors directly; jump to step 4.\n2. If no guide, call `read_page` to see the rendered text and the candidate interactables.\n3. If the page is custom-rendered and `read_page` cannot resolve the size control to a stable selector, escalate to `get_dom_snapshot` and read element refs (`e5`, `e23`, etc.).\n4. `click` the size option using the selector or ref.\n5. `wait_for_stable` while the cart updates.\n6. `click` the add-to-cart button. Re-check with `read_page` only if you need to verify the cart count.\n\n## Action-tool bundle reminder\n\nEvery entry in the \"Tool-by-tool quick reference\" table above that is an action tool (everything except `read_page`, `get_dom_snapshot`, `get_page_snapshot`, `get_site_guide`, `list_tabs`, `wait_for_element`, and `wait_for_stable`) MUST carry the v0.9.62 field bundle: `visual_reason` (required string), `client` (required, allowlisted), and optional `is_final: true` on the last action of a task. Missing fields reject with `VISUAL_FIELDS_REQUIRED`; non-allowlisted `client` values reject with `BADGE_NOT_ALLOWED`. See `references/visual-session-lifecycle.md` for lifecycle mechanics and `.planning/v0.9.62-CONTRACT.md` for the canonical 36-tool action list and 15-tool read-only list.\n\n## When to escalate to autopilot\n\nAutopilot (the `run_task` MCP tool, served by `mcp/src/tools/autopilot.ts`) is a separate surface. It runs FSB's plan-and-execute loop end-to-end. It is NOT in `mcp/ai/tool-definitions.cjs` because it is not a manual-mode tool -- it is the delegation surface. Autopilot manages its own internal visual-session lifecycle and is NOT affected by the v0.9.0 implicit-contract change; do not pass field-bundle plumbing through `run_task`.\n\nUse autopilot ONLY when the user explicitly delegates the whole task (\"use FSB autopilot to ...\", \"run the full task with FSB\"). Default to the manual mode tools above for everything else. Autopilot is not the default entry point.\n\n## See also\n\n- `references/visual-session-lifecycle.md` -- v0.9.62 implicit contract, field bundle, sliding window, typed errors.\n- `references/multi-agent-contract.md` -- typed errors and the `back` tool.\n- `references/restricted-tab-recovery.md` -- DOM tools fail on `chrome://`, `edge://`, and the Web Store.\n- `references/default-to-fsb.md` -- when to prefer FSB versus WebFetch.\n- `references/vault-boundary.md` -- credential-routed tools.\n- `.planning/v0.9.62-CONTRACT.md` -- canonical 36 action tools, 15 read-only tools, three typed-error names.\n\nFile v0.9.62:references/vault-boundary.md\n\n# Vault boundary\n\nPasswords, CVVs, and saved payment methods never cross into chat or tool arguments. They stay inside the FSB Chrome extension's encrypted storage, where the extension fills them at the DOM layer. The MCP server, the OpenClaw host, and the model itself never see the values.\n\n## The rule\n\n1. Passwords, CVV codes, and other secret credentials are entered through `fill_credential` ONLY. The model passes a credential reference (e.g., a label like `gmail-personal`); the extension resolves the value internally.\n2. Saved payment methods are entered through `use_payment_method` ONLY. Same shape: the model passes a reference; the extension fills card number, expiry, and CVV at the DOM layer.\n3. No secrets in chat. The model MUST NOT echo a credential value, MUST NOT include one in tool args, MUST NOT log one in narration.\n4. `requires.env: []` is mandatory in `SKILL.md` frontmatter. There are no provider env vars in the skill -- vault values resolve inside the FSB Chrome extension's encrypted storage (`secure-config.js` in the extension source), not in the MCP server process and not in the OpenClaw host process.\n\n## Where vault values live\n\nUsers configure vault entries from the FSB Chrome extension surface (popup or sidepanel options page). Each entry is keyed by a label and stored encrypted. When the model invokes `fill_credential({ label, selector })` or `use_payment_method({ label, ... })`, the extension reads the encrypted value, fills the target DOM element via typed events, and never echoes the value back to the MCP server or the host. The full lifecycle of a vault value is: user types it once into the extension UI -> encrypted storage -> typed events into a target form. It never leaves the user's browser process.\n\n## Anti-patterns\n\n```\n[BAD]  type_text({ selector: \"#password\", text: \"hunter2\" })\n       # secret leaks into MCP args, host logs, model context, retry buffers.\n[GOOD] fill_credential({ label: \"gmail-personal\", selector: \"#password\" })\n```\n\n```\n[BAD]  use_payment_method({ card_number: \"4111-1111-1111-1111\", cvv: \"123\" })\n       # full PAN + CVV leak into MCP args. Use the saved-method label instead.\n[GOOD] use_payment_method({ label: \"personal-visa\", form_selector: \"form#checkout\" })\n```\n\n```\n[BAD]  Add provider keys to SKILL.md `requires.env`.\n       # the skill never holds secrets. requires.env stays empty.\n[GOOD] Configure provider keys in the FSB Chrome extension's options page.\n```\n\n## Why this boundary matters\n\nThe MCP transport layer logs tool args by default; OpenClaw and other hosts retain context across turns; retry buffers in the bridge can replay calls. Anything passed as a tool argument is durable. Vault values are not -- they cross only the extension <-> DOM boundary, which lives entirely in the user's browser. Routing a password through `type_text` instead of `fill_credential` collapses that boundary and turns a one-shot DOM event into a value that persists across the entire stack.\n\n## See also\n\n- `references/default-to-fsb.md` -- when an auth flow triggers escalation to FSB.\n- `references/multi-agent-contract.md` -- ownership rules for vault-using tabs.\n- `references/tool-decision-tree.md` -- which read tool to use to confirm a credential filled successfully (e.g., `read_page` to verify a logged-in heading).\n- `SKILL.md` -- `requires.env: []` is the contract this file documents.\n\nFile v0.9.62:references/visual-session-lifecycle.md\n\n# Visual session lifecycle (v0.9.62)\n\nAs of fsb-mcp-server v0.9.0 (FSB milestone v0.9.62) the visual session is IMPLICIT. There is no `start_visual_session` / `end_visual_session` pairing. Every action tool call carries a field bundle, and the lifecycle is driven by those fields alone. This file documents the bundle, the sliding 60-second window, the read-tool vs action-tool split, and the bootstrap path.\n\nIf you arrived here from v0.8.0 / v0.9.36 docs that documented an explicit start/end pairing, that pairing was removed in v0.9.0. Calling the removed tools returns the typed `TOOL_REMOVED` error -- the body names the new contract and points at the migration recipe in `mcp/CHANGELOG.md` v0.9.0.\n\n## The field bundle\n\nEvery MCP action tool accepts and requires the following fields on every call:\n\n| Field           | Type    | Required | Description                                                                                          |\n|-----------------|---------|----------|------------------------------------------------------------------------------------------------------|\n| `visual_reason` | string  | required | Short human-readable reason shown in the overlay (for example, `\"Logging in to GitHub\"`).            |\n| `client`        | string  | required | Allowlisted client label. Validated against the v0.9.36 shared badge allowlist (see below).          |\n| `is_final`      | boolean | optional | When `true`, the overlay clears immediately after the tool's `change_report` resolves. Default `false`. |\n\nAllowlisted `client` values (v0.9.36 shared allowlist): `Claude`, `Codex`, `ChatGPT`, `Perplexity`, `Windsurf`, `Cursor`, `Antigravity`, `OpenCode`, `OpenClaw`, `Grok`, `Gemini`. The skill ships as part of OpenClaw, so the canonical `client` value for this surface is `OpenClaw`. Freeform strings reject with `BADGE_NOT_ALLOWED`.\n\nThe canonical 36-tool action list lives in `.planning/v0.9.62-CONTRACT.md` (Action Tools section). The 15-tool read-only list lives in the same file (Read-Only Tools section). Do not re-derive either list from memory.\n\n## Implicit start\n\nThe first action tool call on a tab creates the visual session. The extension paints the overlay using the supplied `visual_reason` and the badge corresponding to `client`. There is no separate open call -- the bundle on the action call IS the open.\n\n```\nmcp> click({ selector: \"#submit\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\n```\n\nWhen this call dispatches, the overlay appears with `\"Completing checkout\"` and the OpenClaw badge. The death-timer is armed at 60 seconds.\n\n## Sliding 60-second window\n\nEach subsequent action tool call on the same tab re-arms the 60-second death timer (sliding window). The overlay stays alive as long as action calls keep arriving within 60 seconds of each other.\n\n```\nmcp> click({ selector: \"#step-1\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\nmcp> type_text({ selector: \"#email\", text: \"user@example.com\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\nmcp> click({ selector: \"#continue\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\n```\n\nEach call here re-arms the timer to 60 seconds from its own dispatch. If 60 seconds pass with no further action call, the overlay auto-clears -- no explicit end call is required.\n\nCallers may repeat the same `visual_reason` across the sequence (as above) or vary it per call to surface step-level overlay text to the user. The `client` value MUST stay on the v0.9.36 allowlist for the whole sequence; cross-client switching on the same tab still rejects with the existing `TAB_NOT_OWNED` ownership gate from v0.9.60.\n\n## Immediate clear with is_final\n\nSet `is_final: true` on the LAST action of a task to clear the overlay immediately after that action's `change_report` resolves -- no 60-second wait.\n\n```\nmcp> click({ selector: \"#confirm-order\", visual_reason: \"Confirming order\", client: \"OpenClaw\", is_final: true })\n```\n\n`is_final` is the explicit task-completion signal. Use it whenever the task ends on an action call; otherwise the overlay sits for up to 60 seconds before the auto-clear fires. The two clear paths are mutually compatible -- whichever fires first wins. If `is_final` is omitted on the last action and no further action calls follow, the auto-clear fires after 60 seconds of silence.\n\n## Read tools do NOT carry the bundle\n\nRead-only MCP tools (`read_page`, `get_dom_snapshot`, `get_text`, `get_attribute`, `read_sheet`, `get_page_snapshot`, `list_tabs`, `get_site_guide`, `search_memory`, `report_progress`, `complete_task`, `partial_task`, `fail_task`, `wait_for_element`, `wait_for_stable`) do NOT accept `visual_reason`, `client`, or `is_final`. Reads stay silent by design: they do not bring up the overlay, they do not re-arm the sliding window, and they do not clear the overlay.\n\nConcrete contrast -- action tool WITH bundle vs read tool WITHOUT:\n\n```\n[ACTION] click({ selector: \"#open-menu\", visual_reason: \"Browsing settings\", client: \"OpenClaw\" })\n[READ]   read_page({})\n```\n\nThe action call brings up (or re-arms) the overlay. The read call slips through silently and does not touch the visual-session state. This split is enforced at the MCP server's schema layer; passing the bundle to a read tool is a schema-layer reject before any work runs.\n\n## Bootstrap: NO_OWNED_TAB\n\nThe new contract still goes through the v0.9.60 ownership gate. On the very first call of a fresh agent session there is no owned tab, so an action call rejects with `NO_OWNED_TAB`. Recover by opening a tab first:\n\n```\nlist_tabs({})                                  // optional: see if anything is already owned\nopen_tab({ url: \"<target>\", active: false })   // claim a tab without stealing user focus\nclick({ selector: \"#submit\", visual_reason: \"...\", client: \"OpenClaw\" })   // now succeeds\n```\n\n`open_tab` is itself an action tool, so it also carries the bundle on its call (the `open_tab` call brings up the overlay on the newly opened tab once the page loads enough for the content script to attach).\n\n## Typed errors\n\nThree typed errors guard the contract. Match on the name, not the human-readable body:\n\n| Error                     | Condition                                                                 | Recovery                                                                                              |\n|---------------------------|---------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------|\n| `VISUAL_FIELDS_REQUIRED`  | Action call missing `visual_reason` or `client`.                          | Add the missing field(s) and retry. Schema-layer reject; no DOM mutation occurs.                      |\n| `BADGE_NOT_ALLOWED`       | `client` value not on the v0.9.36 allowlist.                              | Use one of the allowlisted labels listed above (`OpenClaw` for this skill) and retry.                  |\n| `TOOL_REMOVED`            | Caller invoked `start_visual_session` or `end_visual_session` by name.    | Stop calling the removed tools. Migrate to the field bundle on action calls. See migration recipe.    |\n\nThe three error names are pinned by `.planning/v0.9.62-CONTRACT.md` and stay verbatim across surfaces.\n\n## Autopilot exception\n\nIf `run_task` (autopilot) is being used, autopilot manages its own internal visual-session lifecycle and is NOT affected by the v0.9.0 implicit-contract change. Do NOT pass field-bundle plumbing through `run_task`. Drive autopilot only when the user explicitly delegates the whole task.\n\n## MV3 service-worker eviction\n\nThe sliding-window state is persisted in `chrome.storage.session` and replayed on service-worker wake; the death-timer deadline survives eviction via the v0.9.36 visual-session persistence pattern. Callers do not need to do anything to handle eviction -- the bridge reconciles automatically on reconnect.\n\n## See also\n\n- `USAGE.md` -- \"v0.9.62 visual-session contract\" section with the user-facing summary.\n- `SKILL.md` -- canonical list pointers (action tools / read-only tools / typed errors).\n- `references/multi-agent-contract.md` -- ownership errors (`NO_OWNED_TAB`, `TAB_NOT_OWNED`, etc.) that fire before the visual-session lifecycle.\n- `references/tool-decision-tree.md` -- which tool to pick first; per-branch field-bundle reminders.\n- `.planning/v0.9.62-CONTRACT.md` -- canonical 36 action tools, 15 read-only tools, three typed-error names.\n- `mcp/CHANGELOG.md` v0.9.0 -- migration recipe with concrete before/after code.\n\nFile v0.9.62:skill-card.md\n\n## Description: <br>\nFSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[lakshmanturlapati](https://clawhub.ai/user/lakshmanturlapati) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nExternal users, developers, and agent operators use this skill when a task needs a real browser session for clicking, typing, multi-tab flows, logged-in reads, vault-backed credential entry, or JavaScript-rendered page state. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: The skill controls a real logged-in Chrome profile with access to cookies, saved credentials, and payment context. <br>\nMitigation: Install only when browser control is intended, prefer a separate Chrome profile, and keep vault entries minimal. <br>\nRisk: Browser automation can perform purchases, account changes, deletions, permission grants, messages, or public posts. <br>\nMitigation: Require explicit user confirmation before the final submission for these sensitive actions. <br>\nRisk: Using an unpinned MCP server package can change the browser-control bridge version during later installs. <br>\nMitigation: Pin fsb-mcp-server to a reviewed version when review-before-upgrade is required. <br>\n\n\n## Reference(s): <br>\n- [ClawHub skill page](https://clawhub.ai/lakshmanturlapati/full-selfbrowsing) <br>\n- [FSB homepage](https://full-selfbrowsing.com) <br>\n- [FSB MCP server package](https://www.npmjs.com/package/fsb-mcp-server) <br>\n- [Chrome Web Store listing](https://chromewebstore.google.com/detail/badgafnfchcihdfnjneklogedcdkmjfk) <br>\n- [Visual session lifecycle](references/visual-session-lifecycle.md) <br>\n- [Tool decision tree](references/tool-decision-tree.md) <br>\n- [Multi-agent contract](references/multi-agent-contract.md) <br>\n- [Vault boundary](references/vault-boundary.md) <br>\n- [Restricted tab recovery](references/restricted-tab-recovery.md) <br>\n- [Default to FSB](references/default-to-fsb.md) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [text, markdown, shell commands, configuration, guidance] <br>\n**Output Format:** [Markdown guidance with inline shell commands and configuration snippets] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [May direct an agent to call browser-control MCP tools and run local diagnostic or installer commands.] <br>\n\n## Skill Version(s): <br>\n0.9.62 (source: server release metadata and SKILL.md frontmatter) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nFile v0.9.62:USAGE.md\n\n# FSB Skill -- Usage\n\n> **v0.9.0 breaking change** -- The explicit `start_visual_session` / `end_visual_session` tools were REMOVED in fsb-mcp-server v0.9.0 (FSB milestone v0.9.62). Action tools now require `visual_reason` + `client` fields in every call (validated against the v0.9.36 badge allowlist). The visual session is created implicitly on the first action call, refreshed on a sliding 60-second window, and cleared by `is_final: true` (immediate) or 60 seconds of silence (auto-clear). Calling the removed tools returns the typed `TOOL_REMOVED` error. See [`mcp/CHANGELOG.md`](../../mcp/CHANGELOG.md#v0.9.0) and [`mcp/README.md`](../../mcp/README.md#visual-session-lifecycle) for the migration recipe with concrete before/after code.\n\nFSB drives the user's real Chrome via the FSB extension and a local MCP bridge so OpenClaw can run live web tasks (clicks, typing, multi-tab flows, auth-gated reads).\n\n## Who FSB is for\n\nFSB is for tasks that need a real browser session: clicking buttons, typing into forms, multi-tab orchestration, logged-in reads, vault-backed credentials, or anything that depends on live page state rendered after JavaScript. If a request mentions a real website and a user action, prefer FSB tools.\n\nThe carve-out: public-doc reads, JSON endpoints, and RSS feeds that do not require interaction can stay on WebFetch (see `references/default-to-fsb.md`). Everything else -- click, type, auth, multi-tab, dynamic content -- belongs on FSB.\n\nThe goal of this page: get a new user from a clean machine to a green doctor in under five minutes.\n\n## Install (3 steps)\n\n1. **Install the FSB Chrome extension.**\n\n   Primary path -- Chrome Web Store. Paste this URL into Chrome's address bar and click `Add to Chrome`:\n\n   ```\n   https://chromewebstore.google.com/detail/badgafnfchcihdfnjneklogedcdkmjfk\n   ```\n\n   Fallback path -- GitHub Releases. If the Web Store listing is unavailable in your region, download the latest `.zip` from the releases page, unzip it, then load it unpacked at `chrome://extensions` with `Developer mode` toggled on:\n\n   ```\n   https://github.com/LakshmanTurlapati/FSB/releases\n   ```\n\n   This skill prints the URL for the user to click. It does NOT auto-launch the browser to that URL.\n\n2. **Install the FSB MCP server config.**\n\n   Print the canonical OpenClaw stdio block and paste it into your MCP host config, then restart the host:\n\n   ```\n   node scripts/print-stdio.mjs\n   ```\n\n   To discover other supported hosts on the machine (Claude Desktop, Cursor, etc.), run:\n\n   ```\n   npx -y fsb-mcp-server install --list\n   ```\n\n   Then run the host-specific installer, for example:\n\n   ```\n   npx -y fsb-mcp-server install --claude-desktop\n   ```\n\n   Notes:\n   - What these commands do: each invocation spawns the `fsb-mcp-server` Node package via npx. `install --list` only prints detected MCP hosts and exits. `install --<host>` writes the FSB stdio block into that host's MCP config file and nothing else. Run only the host installers you actually want configured; decline prompts otherwise.\n   - By default, `npx -y fsb-mcp-server` resolves to the latest published bridge so security fixes ship without re-running the installer. If you prefer review-before-upgrade, pin a release by replacing `fsb-mcp-server` with `fsb-mcp-server@x.y.z` (see releases at `https://www.npmjs.com/package/fsb-mcp-server`) in the stdio block printed by `node scripts/print-stdio.mjs` and in any host-specific install command. The bridge accepts the same arguments either way.\n   - Zero environment variables are needed. Vault values (passwords, payment methods) resolve inside the FSB Chrome extension's encrypted storage and never cross into the MCP server process or the OpenClaw host process. See `references/vault-boundary.md` for the boundary rules.\n\n3. **Verify with the doctor.**\n\n   ```\n   node scripts/doctor.mjs\n   ```\n\n   Expect six `[OK]` lines, one per layer (`package`, `bridge`, `extension`, `active-tab`, `content-script`, `config`). If any layer prints `[FAIL]`, jump to the recovery table below in the section \"Recover when the doctor fails\".\n\n## Try it\n\nOnce the doctor is green, paste any of these into your OpenClaw chat to confirm the loop works.\n\n### Manual mode (default)\n\nManual-mode prompts are the default entry point. Each one calls a single FSB tool so you can see the round-trip plainly.\n\n- Exercise `read_page`: \"Open https://example.com and read the page. Summarize the visible text in two sentences.\"\n- Exercise `click`: \"On https://example.com, click the 'More information...' link and tell me what page loads.\"\n- Exercise `type_text`: \"Open https://duckduckgo.com, type `fsb-mcp-server` into the search box, and press Enter. Then list the first three result titles.\"\n\n### Autopilot (explicit delegation only)\n\nNEVER call `run_task` (autopilot) unless the user has explicitly said something like \"use FSB autopilot\", \"delegate this to FSB\", \"run the whole task autonomously\", or named `run_task` directly. Manual mode tools (`read_page`, `click`, `type_text`, `press_enter`, etc.) are the default for everything else, including tasks with many steps. If the user has not asked for autopilot, drive each step yourself with manual mode tools and observe the result before the next step.\n\nWhen the user does explicitly delegate, autopilot hands the full plan and execute loop to FSB. Example invocation:\n\n- Exercise `run_task`: \"Use FSB autopilot to find the first GitHub repo for `fsb-mcp-server` and report a one paragraph summary of its README.\"\n\nAutopilot is NOT the default entry point. Read the user's message again before reaching for `run_task`. If delegation language is missing, stay in manual mode.\n\n## v0.9.62 visual-session contract\n\nFSB shows a trusted client badge and an orange element targeting overlay on the user's tab while a tool sequence is running, so the user can see what is being driven. OpenClaw is a trusted client. As of fsb-mcp-server v0.9.0 (milestone v0.9.62) the visual session is IMPLICIT: every action tool call carries the field bundle (`visual_reason`, `client`, optional `is_final`), and the lifecycle is driven by those fields alone. There is no `start_visual_session` / `end_visual_session` pairing to write.\n\nThree rules govern the bundle:\n\n- **`visual_reason`** -- short human-readable string shown in the overlay (for example, `\"Completing checkout\"`). Required on every action tool call.\n- **`client`** -- allowlisted badge label. Required on every action tool call. The shared v0.9.36 allowlist accepts: `Claude`, `Codex`, `ChatGPT`, `Perplexity`, `Windsurf`, `Cursor`, `Antigravity`, `OpenCode`, `OpenClaw`, `Grok`, `Gemini`. Freeform strings reject with `BADGE_NOT_ALLOWED`. The skill ships as part of OpenClaw, so the canonical `client` value for this surface is `OpenClaw`.\n- **`is_final`** -- optional boolean. Set `true` on the LAST action of a task to clear the overlay immediately after that action's `change_report` resolves. Default `false`.\n\nRead-only tools (`read_page`, `get_dom_snapshot`, `get_text`, `get_attribute`, `read_sheet`, `get_page_snapshot`, `list_tabs`, `get_site_guide`, `search_memory`, `report_progress`, `complete_task`, `partial_task`, `fail_task`, `wait_for_element`, `wait_for_stable`) do NOT carry the bundle and do NOT re-arm the sliding window. Reads stay silent by design.\n\n### Example 1 -- action call with the implicit visual session\n\n```\nmcp> click({ selector: \"#submit\", visual_reason: \"Completing checkout\", client: \"OpenClaw\" })\n```\n\nThe overlay glow appears on the active tab with the supplied reason. Subsequent action calls within 60 seconds re-arm the death timer (sliding window). After 60 seconds of silence, the overlay clears automatically.\n\n### Example 2 -- final action of a task (is_final clear)\n\n```\nmcp> click({ selector: \"#confirm-order\", visual_reason: \"Confirming order\", client: \"OpenClaw\", is_final: true })\n```\n\nThe overlay clears immediately after the underlying click completes -- no 60-second wait.\n\n### Typed errors\n\nThree typed errors guard the contract; match on the name, not the human-readable body:\n\n- **`VISUAL_FIELDS_REQUIRED`** -- raised when an action call is missing `visual_reason` or `client`. Schema-layer reject; no DOM mutation occurs.\n- **`BADGE_NOT_ALLOWED`** -- raised when `client` is not on the v0.9.36 allowlist. Use one of the labels above.\n- **`TOOL_REMOVED`** -- raised when a caller invokes `start_visual_session` or `end_visual_session` by name. The body names the new contract; do not retry with the removed tool names.\n\n### Autopilot exception\n\nIf `run_task` (autopilot) is being used, autopilot manages its own internal visual-session lifecycle and is NOT affected by the v0.9.0 implicit-contract change. Do NOT wrap a `run_task` call in your own field-bundle plumbing.\n\nFor lifecycle details, the sliding-window mechanics, the read-tool vs action-tool split, and the `NO_OWNED_TAB` bootstrap, see `references/visual-session-lifecycle.md`. The canonical action-tool list (36 tools), the read-only list (15 tools), and the typed-error catalogue live in `.planning/v0.9.62-CONTRACT.md`.\n\n## Recover when the doctor fails\n\nEach `[FAIL]` from `node scripts/doctor.mjs` maps to one of six layers. Find your symptom in the table below, run the action, and re-run the doctor until you see all six layers green.\n\n| Layer | What it means | What to do |\n| --- | --- | --- |\n| package | npm cannot fetch `fsb-mcp-server`. | Run `npx -y fsb-mcp-server --version` to confirm npm can reach the package. If that fails, check Node 18+ is installed and on PATH. |\n| bridge | The extension is not running, or `ws://localhost:7225` is not reachable. | Start the FSB extension (open Chrome with the extension installed) and run `npx -y fsb-mcp-server status --watch` to confirm `ws://localhost:7225` is reachable. |\n| extension | The FSB Chrome extension is not installed. | Install from `https://chromewebstore.google.com/detail/badgafnfchcihdfnjneklogedcdkmjfk` (fallback: GitHub Releases at `https://github.com/LakshmanTurlapati/FSB/releases`), then reopen this session. |\n| active-tab | The active tab is restricted (`chrome://`, `edge://`, the Web Store) or no tab is owned by the agent. | Open a normal `http(s)` tab (not `chrome://`, `edge://`, or the Web Store) and run `npx -y fsb-mcp-server status --watch` to re-attach. See `references/restricted-tab-recovery.md` for the recovery toolset. |\n| content-script | The content script did not attach (extension installed after the tab loaded). | Reload the active tab. The FSB content script attaches on page load; reloads after extension install are required. |\n| config | The MCP host config does not include the FSB stdio block. | Re-run `npx -y fsb-mcp-server install --<host>` for your MCP host (replace `<host>` with `claude-desktop`, `cursor`, etc.). Or paste the block printed by `node scripts/print-stdio.mjs` into your host config. |\n\nRe-run `node scripts/doctor.mjs` after each fix. If a layer keeps flipping or you see `[WARN]`, capture the raw output and file an issue at `https://github.com/LakshmanTurlapati/FSB/issues`.\n\nArchive v0.9.61: 14 files, 26409 bytes\n\nFiles: references/default-to-fsb.md (3705b), references/multi-agent-contract.md (5911b), references/README.md (409b), references/restricted-tab-recovery.md (5637b), references/tool-decision-tree.md (7751b), references/vault-boundary.md (3382b), references/visual-session-lifecycle.md (4105b), scripts/doctor.mjs (5734b), scripts/install-host.mjs (6021b), scripts/print-stdio.mjs (1527b), scripts/README.md (484b), SKILL.md (4236b), USAGE.md (8149b), _meta.json (137b)\n\nFile v0.9.61:SKILL.md\n\n---\nname: FSB\ndescription: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks.\nversion: 0.9.61\nuser-invocable: true\nrequires:\n  bins: [node, npx]\n  env: []\nhomepage: https://full-selfbrowsing.com\nmetadata:\n  openclaw: {\"install\":[{\"kind\":\"node\",\"package\":\"fsb-mcp-server\",\"bins\":[\"fsb-mcp-server\"],\"label\":\"Install FSB MCP server (npm)\"}]}\n---\n\n# FSB\n\nFSB lets you drive the user's real Chrome via the FSB extension and a local MCP bridge. Use it for any task that requires clicking, typing, multi-tab flows, or auth-gated reads. Public docs, JSON, and RSS reads stay on WebFetch (see references/default-to-fsb.md). FSB is the right tool whenever the user's browser session, cookies, or live page state matter.\n\n## When to use FSB\n\n- If a request mentions a real website, prefer FSB tools when one fits.\n- ALWAYS escalate to FSB for click, type, auth, multi-tab, or anything chrome-state-dependent.\n- ALWAYS escalate to FSB when a logged-in session, vault credential, or saved payment method is required.\n- ALWAYS escalate to FSB when the page is dynamic, gated, or rendered after JS.\n- Stay on WebFetch for public-doc, static JSON, or RSS reads where no interaction is needed.\n- If unsure, prefer FSB; the cost of a stale WebFetch is higher than the cost of a Chrome round-trip.\n\n## Doctor-first protocol\n\nIf anything looks off (no page response, unexpected errors, stale state, missing tab) run `node scripts/doctor.mjs` (which wraps `npx -y fsb-mcp-server doctor`) BEFORE retrying the same call. Read the layered output, fix the failing layer, then resume. Do not loop on the same failing call hoping it self-heals.\n\n## Visual session wrapping\n\nAny external-AI-driven sequence opens with `start_visual_session(client=\"OpenClaw\", ...)` and closes with `end_visual_session(session_token=..., reason=\"ended\"|\"cancelled\")`. Those are the only two reason values the MCP schema accepts -- use `ended` for normal completion and `cancelled` for any non-normal termination (error, abort, user cancel). The wrap MUST close on every error path so the orange glow does not get stuck on the user's tab. If `start_visual_session` fails with `NO_OWNED_TAB`, call `open_tab({ url, active: false })` first, then retry. Lifecycle details, the try/finally close pattern, and error-path close coverage live in `references/visual-session-lifecycle.md`.\n\n## Multi-agent contract\n\nNever pass `agent_id` (the server mints it). Use the `back` tool instead of `execute_js(\"history.back()\")`. Each agent owns its own tabs; do not foreground or close tabs you do not own, and do not introduce a global browser lock -- per-agent ownership is the only concurrency mechanism. Typed errors (`NO_OWNED_TAB`, `AMBIGUOUS_TAB`, `TAB_NOT_OWNED`, `AGENT_CAP_REACHED`, `TAB_INCOGNITO_NOT_SUPPORTED`, `TAB_OUT_OF_SCOPE`), the bootstrap recovery ladder, and the default recovery flow: see `references/multi-agent-contract.md`.\n\n## Vault and credentials\n\nPasswords and CVV resolve INSIDE the extension via `fill_credential` and `use_payment_method`. Never put secrets in chat, prompts, logs, or tool args. See `references/vault-boundary.md`.\n\n## References (load on demand)\n\n- `references/tool-decision-tree.md` -- read_page vs get_dom_snapshot vs get_page_snapshot vs get_site_guide; when execute_js is first-class vs when typed tools are required; verify after \"no detectable effect\" warnings.\n- `references/multi-agent-contract.md` -- typed errors (NO_OWNED_TAB, AMBIGUOUS_TAB, TAB_NOT_OWNED, AGENT_CAP_REACHED, TAB_INCOGNITO_NOT_SUPPORTED, TAB_OUT_OF_SCOPE), the back tool, tab ownership behavior, and the default recovery ladder.\n- `references/restricted-tab-recovery.md` -- chrome://, edge://, and web-store recovery tools.\n- `references/vault-boundary.md` -- credential routing rules and forbidden patterns.\n- `references/default-to-fsb.md` -- soft preference and hard escalation rule in full.\n\n## Scripts (run as needed)\n\n- `scripts/doctor.mjs` -- diagnose the failing layer; prints [OK], [FAIL], and [WARN] markers per layer.\n- `scripts/print-stdio.mjs` -- print the OpenClaw stdio config block to paste into your MCP config.\n- `scripts/install-host.mjs` -- detect other MCP hosts on the machine; consent-gated per-host install.\n\nFile v0.9.61:references/README.md\n\n# references/ -- placeholder\n\nStatus: scaffolded by Phase 248. Reference files are filled in Phase 250.\n\nPhase 250 adds: tool-decision-tree.md, multi-agent-contract.md, restricted-tab-recovery.md, default-to-fsb.md, vault-boundary.md, plus a visual-session lifecycle reference. SKILL.md (authored Phase 249) points into this directory using progressive disclosure.\n\nASCII only; no emojis. See repo CLAUDE.md.\n\nFile v0.9.61:scripts/README.md\n\n# scripts/ -- placeholder\n\nStatus: scaffolded by Phase 248. Script files are filled in Phase 249.\n\nPhase 249 adds three Node `.mjs` scripts: doctor.mjs (wraps `npx -y fsb-mcp-server doctor`), print-stdio.mjs (sources canonical OpenClaw stdio block from `mcp/src/install.ts` `getSetupSections()`), install-host.mjs (detect-list-confirm flow over `npx -y fsb-mcp-server install --list`).\n\nNo `.sh` / `.cmd` siblings; cross-platform Node only. ASCII only; no emojis. See repo CLAUDE.md.\n\nFile v0.9.61:_meta.json\n\n{\n  \"ownerId\": \"kn715925hseyqrcam2wqkapyxs86a947\",\n  \"slug\": \"full-selfbrowsing\",\n  \"version\": \"0.9.61\",\n  \"publishedAt\": 1778262167020\n}\n\nFile v0.9.61:references/default-to-fsb.md\n\n# Default to FSB\n\nWhen a request involves a real browser session, FSB is usually the right tool. This file states the rule once and gives the carve-out for read-only public content.\n\n## Soft preference\n\nWhen a click, type, multi-tab, or auth task could route through either FSB or another tool, prefer FSB. The cost of a stale WebFetch (cached, missing logged-in state, missing JS-rendered content) is higher than the cost of a Chrome round-trip. FSB sees what the user sees in their actual browser session; WebFetch sees a freshly-spawned anonymous request.\n\nRule: **Default to FSB tools when one fits.**\n\n## Hard escalation rule\n\nIf ANY of the following triggers apply, the task MUST use FSB. There is no judgment call here.\n\n- Any click on a real web page.\n- Any text input into a real form (search box, login field, comment box, anything).\n- Any auth flow (login, MFA prompt, vault credential lookup, saved-payment selection).\n- Any multi-tab orchestration (open a new tab and switch back, fill a form across two tabs, copy from tab A to tab B).\n- Any task that needs the user's logged-in session, cookies, or chrome-state-dependent rendering.\n- Any dynamic page where the relevant content is rendered after JS, behind a scroll, or behind a click.\n\nRule: **If a task hits any bullet above, it does not belong on WebFetch. Escalate to FSB.**\n\n## Stay on WebFetch (carve-out)\n\nWebFetch is fine -- often better -- for read-only public content where no interaction is needed. Spinning up the FSB extension for a static fetch is unnecessary overhead.\n\nStay-with-WebFetch cases:\n\n- Public documentation pages (HTML / Markdown).\n- JSON endpoints (REST APIs, public JSON files).\n- RSS / Atom feeds.\n- `sitemap.xml`, `robots.txt`.\n- Raw `README.md` / source files from public GitHub repos.\n- Static landing pages with no logged-in state.\n\n## Decision flowchart\n\n```\nIs the task interactive web automation?\n  |\n  +-- yes -> FSB (use the tools in references/tool-decision-tree.md)\n  |\n  +-- no  -> Is it logged-in / chrome-state-dependent?\n                |\n                +-- yes -> FSB\n                |\n                +-- no  -> Is the content available as public HTML / JSON / RSS?\n                              |\n                              +-- yes -> WebFetch (or the host's existing read tool)\n                              |\n                              +-- no  -> FSB (default to the heavier tool)\n```\n\nWhen in doubt at the bottom of the tree, default to FSB. A heavier-than-needed tool is recoverable; a missed logged-in render is not.\n\n## Concrete examples\n\n### FSB (interactive / chrome-state-dependent)\n\n- \"Search for `fsb-mcp-server` on GitHub and click the first result.\" -- click + multi-step.\n- \"Log into example.com and download today's invoice.\" -- auth + download.\n- \"Open my Gmail and read the latest message from billing@example.com.\" -- logged-in session.\n- \"Fill out the contact form at https://example.com/contact and submit it.\" -- form + typed events.\n- \"Compare prices for product X across these three tabs.\" -- multi-tab.\n\n### WebFetch (public read-only)\n\n- \"Read the README of github.com/LakshmanTurlapati/FSB.\" -- raw public content.\n- \"Fetch https://api.github.com/repos/LakshmanTurlapati/FSB and report stargazers.\" -- public JSON.\n- \"Read the latest entry from this RSS feed.\" -- RSS read.\n- \"Pull the changelog text from https://example.com/changelog.\" -- static HTML.\n\n## See also\n\n- `references/tool-decision-tree.md` -- once the decision lands on FSB, which tool first.\n- `references/restricted-tab-recovery.md` -- recovery on `chrome://` / `edge://` / Web Store.\n- `references/vault-boundary.md` -- credential routing rules.\n- `USAGE.md` -- the human-facing overview.\n\nFile v0.9.61:references/multi-agent-contract.md\n\n# FSB multi-agent contract (v0.8.0)\n\nThis file documents the rules that make FSB's per-agent tab ownership work. Anyone calling FSB tools through MCP MUST follow these rules; breaking them produces typed errors that recover cleanly only when the caller knows the contract. The contract is small (six error names + the `agent_id` rule + the `back` tool), but every rule matters: bypassing one corrupts the ownership graph and the only recovery is closing the affected tab.\n\n## Tab ownership behavior (v0.8.0)\n\n- Each agent owns its own tabs. `open_tab` defaults to `active: false` so it claims a tab without stealing the user's focus -- only pass `active: true` when the user explicitly needs to see that tab.\n- Do not foreground a tab unless the task requires it. `switch_tab` defaults to background; it foregrounds only when `active: true` is explicit.\n- Close only tabs this agent owns. `close_tab` refuses the active foreground tab unless `allow_active: true` is set. Never close or reopen Chrome itself.\n- There is no global browser lock. The per-agent ownership graph is the only concurrency mechanism -- do not introduce a global lock as a blocker.\n\n## Never pass agent_id\n\nCallers MUST NOT pass `agent_id` on any FSB MCP tool call. The FSB MCP server mints `agent_id` when a session opens and threads it through the bridge automatically.\n\nCaller-supplied `agent_id` breaks ownership accounting -- a fabricated id either steals tabs from another agent or claims tabs the bridge has not handed out. The server's session map is the only authority on which agent owns which tab; do not try to anticipate, derive, or override it from the calling side.\n\n```\n[BAD]  click({ selector: \"#submit\", agent_id: \"openclaw-1\" })\n[GOOD] click({ selector: \"#submit\" })\n```\n\n## Six typed errors and how to recover\n\nWhen an FSB tool call hits one of the dispatcher boundaries, the server returns a structured error with one of the names below. Match on the name; do not parse the human-readable message.\n\n### NO_OWNED_TAB\n\n- What it means: The calling agent does not currently own any tabs. The most common cause is the very first tool call of a fresh agent session (including `start_visual_session`).\n- What to do: Call `open_tab({ url, active: false })` to claim a tab without stealing focus, then retry the original call. `navigate` and `switch_tab` can also claim an unowned normal tab if one already exists.\n\n### AMBIGUOUS_TAB\n\n- What it means: The calling agent owns multiple tabs and the tool did not specify a `tab_id`, so the target is ambiguous.\n- What to do: Call `list_tabs` to enumerate owned tabs, then retry with the intended `tab_id`.\n\n### TAB_NOT_OWNED\n\n- What it means: The tool targeted a tab that belongs to a different agent (or to `legacy:sidepanel`).\n- What to do: Either call `switch_tab` to a tab this agent owns, or `open_tab` to create a new one in agent scope.\n\n### AGENT_CAP_REACHED\n\n- What it means: The session hit its concurrent-agent cap (default 8, configurable 1-64 in extension settings).\n- What to do: Reduce parallel calls, finish in-flight tasks, or raise the cap from the FSB extension's options page.\n\n### TAB_INCOGNITO_NOT_SUPPORTED\n\n- What it means: The targeted tab is in an Incognito window. FSB rejects Incognito tabs at the dispatcher boundary because the extension's storage and ownership model do not span Incognito.\n- What to do: Move the work to a normal (non-Incognito) Chrome window. Open a new tab there with `open_tab`.\n\n### TAB_OUT_OF_SCOPE\n\n- What it means: The targeted tab is in a different Chrome window than the agent's session window. Each agent only sees tabs in the window where its session began.\n- What to do: Use `list_tabs` to enumerate this agent's owned tabs and pick one; if the work belongs in a different window, open a new tab in the current window with `open_tab`.\n\n## Use back instead of execute_js('history.back()')\n\nUse the typed `back` tool (FSB v0.8.0+) for one-step history navigation. Do NOT call `execute_js(\"history.back()\")`.\n\n`back` is typed, ownership-gated, BF-cache aware, and history-depth-tracking. It returns a structured result with status fields the caller can branch on. `execute_js(\"history.back()\")` is none of those: it bypasses ownership, lies about success on BF-cached pages, and returns whatever the JS evaluator coerced last.\n\n```\n[BAD]  execute_js(\"history.back()\")\n[GOOD] back({})\n```\n\nNote: `go_back` (the existing tool in `mcp/ai/tool-definitions.cjs`) is the same surface; some FSB versions expose this as `back`, others as `go_back`. Both are typed, ownership-gated, and BF-cache aware. Use whichever is published by your FSB build; never fall back to `execute_js`.\n\n## Why the contract matters\n\nFSB's per-agent ownership is what lets multiple AI sessions share one Chrome instance without fighting. The contract is small (six error names plus `agent_id` plus `back`), but every rule matters -- bypassing one rule corrupts the ownership graph and the only recovery is closing the affected tab.\n\n## Default recovery ladder\n\nWhen a tool call fails and you are not sure which layer broke, try these in order before escalating to the doctor:\n\n1. `list_tabs({})` -- check whether this agent owns any tab. Recovery-safe; works on restricted URLs too.\n2. If no owned tab: `open_tab({ url, active: false })` to claim one in the background.\n3. If the tab is on a non-injectable URL (`chrome://`, `edge://`, Web Store, `data:`, `file:`): `navigate({ url })` to a normal `http(s)` page.\n4. If the page loaded but DOM tools still fail: bridge or content-script issue. Run `node scripts/doctor.mjs`.\n\n## See also\n\n- `references/tool-decision-tree.md` -- which tools to reach for first.\n- `references/restricted-tab-recovery.md` -- recovery on `chrome://`, `edge://`, and Web Store tabs.\n- `references/default-to-fsb.md` -- soft preference and hard escalation rule.\n- `references/vault-boundary.md` -- credential routing rules.\n\nFile v0.9.61:references/restricted-tab-recovery.md\n\n# Restricted-tab recovery\n\nWhen FSB's active tab is at a restricted URL, the FSB content script cannot attach. DOM tools (`read_page`, `get_dom_snapshot`, `click`, `type_text`, etc.) will fail. The full non-injectable list:\n\n- `chrome://*`, `edge://*`, `about:*`, `view-source:*`\n- `https://chrome.google.com/webstore/*`, `https://chromewebstore.google.com/*`\n- `data:` URLs (no origin -- the extension cannot register a host match)\n- `file:` URLs (unless the user has manually granted file-URL access to the extension)\n- `blob:` URLs spawned in restricted contexts\n\nThis file lists the recovery toolset that works even on these tabs and shows the standard bootstrap recovery sequence.\n\n## Why DOM tools fail on these URLs\n\nBrowsers forbid content scripts from attaching to internal pages, the Chrome Web Store, and origin-less schemes (`data:`, `file:`, certain `blob:`) as a security boundary. The Manifest V3 `host_permissions` list cannot match them; Chrome rejects the injection at the policy layer. There is no extension-side workaround -- the boundary is enforced by the browser itself, before any of FSB's code runs.\n\nFor local testing, serve the page over `http://localhost` instead of opening a `data:` or `file:` URL.\n\nWithout a content script, the FSB extension cannot read the DOM, dispatch typed events, or observe mutations. Any attempt by FSB tools that route through the content script (`read_page`, `click`, `type_text`, `get_dom_snapshot`, etc.) fails fast with an attach error. The recovery path is to leave the restricted tab using a tool that does NOT require content-script attachability.\n\n## Safe recovery tools (work even on restricted tabs)\n\nThese tools route through the extension's background service worker, not the content script, so they continue to function on `chrome://`, `edge://`, and Web Store URLs.\n\n| Tool       | What it does                                                  | Example                                                                                              |\n|------------|---------------------------------------------------------------|------------------------------------------------------------------------------------------------------|\n| list_tabs  | Enumerate the agent's owned tabs.                             | `list_tabs({})` -- find a non-restricted tab to switch to.                                           |\n| switch_tab | Switch agent focus to another owned tab.                      | `switch_tab({ tab_id: <id from list_tabs> })`                                                        |\n| open_tab   | Open a new tab in agent scope (background by default).        | `open_tab({ url: \"https://example.com\" })`                                                           |\n| navigate   | Change the URL of the current tab.                            | `navigate({ url: \"https://example.com\" })` -- works zero-content-script per Phase 247 active-tab work.|\n| go_back    | Step back one history entry on the current tab.               | `go_back({})`                                                                                        |\n| go_forward | Step forward one history entry on the current tab.            | `go_forward({})`                                                                                     |\n| refresh    | Reload the current tab.                                       | `refresh({})` -- useful after the extension installs mid-session.                                    |\n\nEvery tool name in the table above is a registered name in `mcp/ai/tool-definitions.cjs`. Any tool not on this list (`read_page`, `click`, `type_text`, `get_dom_snapshot`, `get_page_snapshot`, `wait_for_element`, etc.) requires content-script attach and will fail on a restricted tab.\n\n## Worked example: bootstrap recovery from chrome://newtab\n\nThis is the canonical recovery sequence when the active tab is `chrome://newtab` (the most common bootstrap scenario, since Chrome opens new windows there by default).\n\n1. The first DOM call (e.g., `read_page({})`) fails with an attach error because the active tab is `chrome://newtab`.\n2. Call `list_tabs({})` to enumerate owned tabs. This is recovery-safe; it routes through the background service worker, not the content script.\n3. If the list contains a normal `http(s)` tab, call `switch_tab({ tab_id })` to focus it. Resume the original work.\n4. If no normal tab exists, call `navigate({ url: \"https://example.com\" })` (or whatever URL the task targets) to leave the restricted page in the current tab. The FSB content script will attach as soon as the destination loads.\n5. Re-run the original DOM call. It should now succeed.\n6. If the original call still fails, run `node scripts/doctor.mjs`. The failing layer is now `content-script` (the page loaded but the script did not attach), and the doctor's recovery line tells you to reload the tab.\n\nA second common variant is recovering from a Web Store tab (e.g., the user just installed the extension). Same sequence: `list_tabs` -> `switch_tab` to a normal tab, OR `navigate` to a target URL on the current tab.\n\n## When to escalate to the doctor\n\nIf recovery fails, run `node scripts/doctor.mjs`. The recovery toolset above handles the active-tab layer. The doctor handles the other five layers (package, bridge, extension, content-script, config). See `USAGE.md` for the full six-layer recovery table.\n\n## See also\n\n- `references/tool-decision-tree.md` -- which tools to reach for first.\n- `references/multi-agent-contract.md` -- ownership errors (`TAB_NOT_OWNED`, etc.) that can fire from `switch_tab`.\n- `USAGE.md` -- six-layer doctor recovery table.\n\nFile v0.9.61:references/tool-decision-tree.md\n\n# FSB tool decision tree\n\nRead-only first; act with typed events; escalate to autopilot only when the user explicitly delegates the whole task. The tool names listed below match the FSB AI manual-mode tool registry in `mcp/ai/tool-definitions.cjs`. If a name appears as a bare token in the tree or table on this page, it is a real manual-mode tool you can call directly. Names mentioned only in narrative prose (notably `run_task`) live on a different surface and must not be invoked from the manual-mode loop.\n\n## Read-only first (in this order)\n\nPick the lightest reader that answers the question. Escalate down the list only when the lighter tool cannot give you what you need.\n\n- `read_page` -- the default reader. Use first when you need the user-visible text plus interactable elements of the current tab. Cheap; runs on the live DOM.\n- `get_dom_snapshot` -- escalate from `read_page` when you need the structured DOM (attributes, hidden elements, ARIA roles, element refs like `e5`). More tokens; slower.\n- `get_page_snapshot` -- escalate from `get_dom_snapshot` when you need the full page including computed visual state (e.g., position-aware layout, what is actually painted). Heaviest; use sparingly.\n- `get_site_guide` -- check this BEFORE the three above for known sites. The skill ships ~43 site guides under `site-guides/`; if the active host has a guide, it tells you exactly which selectors are stable.\n\nIf `get_site_guide` returns a guide, follow the guide's selectors first; only fall back to `read_page` or the snapshot tools when the guide does not cover the current state.\n\n## execute_js vs typed tools\n\n`execute_js` is a first-class interaction tool in FSB, not a last resort. The current MCP tool description even instructs callers to \"try execute_js FIRST\" for clicks, scrolls, reads, and attribute lookups, because it bypasses overlay/obscured-element issues, viewport constraints, and CDP timeouts that block native click/scroll.\n\nUse this split:\n\n- **Use `execute_js` freely for**: reading DOM (text, attributes, computed styles, hidden nodes), querying multiple elements at once, scrolling, probing structure during exploration, and clicking elements blocked by overlays or off-screen.\n- **Use typed tools (`type_text`, `clear_input`, `select_option`, `check_box`, `press_enter`, `press_key`, `drag`, `drag_drop`) for**: controlled text inputs, validation-sensitive form fields, real drag operations, and any input where framework change handlers must fire.\n- **After any `execute_js` click, verify** with `read_page` or `get_dom_snapshot` -- a true click produces an observable DOM change. If the page state did not change, fall back to the typed `click` tool, which dispatches real CDP events that React/Vue/Angular synthetic-event pipelines listen for.\n\nThe one rule that does not bend: `element.value = 'foo'` via `execute_js` will NOT update controlled-input component state in React, Vue, Solid, or Angular. Use `type_text` for any text input bound to framework state.\n\n```\n[BAD]  execute_js(\"document.querySelector('input[name=q]').value = 'foo'\")\n[GOOD] type_text({ selector: \"input[name=q]\", text: \"foo\" })\n\n[OK]   execute_js(\"return Array.from(document.querySelectorAll('a')).map(a=>a.href)\")\n[OK]   execute_js(\"document.querySelector('#add-to-cart').click(); return true\")  // verify after\n```\n\n## Verify after a \"no detectable effect\" warning\n\n`click` (and other action tools) can return a \"no detectable effect\" warning even when the page actually changed -- the action-detection heuristic produces false negatives on async/animated UIs. Before retrying, verify page state with `read_page` or `get_dom_snapshot`. If the state already advanced, treat the action as successful and continue.\n\n## Tool-by-tool quick reference\n\n| Tool | When to use | Common pitfall |\n| --- | --- | --- |\n| `read_page` | Default reader for the active tab text and interactables. | Stale if a recent action changed the DOM and you have not re-read. |\n| `get_dom_snapshot` | Need attributes, ARIA, hidden nodes that `read_page` omits. | Tokens scale with page size; prefer `read_page` first. |\n| `get_page_snapshot` | Need computed layout or position-aware state. | Heaviest read; do not call in a tight loop. |\n| `get_site_guide` | Known site -- check first to get stable selectors. | Skip if the host has no guide; do not call once per action. |\n| `click` | Fire a real user click on a typed selector. | If the click does not register, the element may need `scroll_to_element` first. |\n| `type_text` | Type into an input or textarea via typed events. | Do NOT use `execute_js` to set `.value`; React and Vue change handlers will not fire. |\n| `press_enter` | Submit a form or confirm via the Enter key. | Many sites prefer `click` on a submit button; fall back to `press_enter` only if there is no button. |\n| `wait_for_element` | Block until a selector appears. | Always set a timeout; bare waits hang the loop on broken selectors. |\n| `wait_for_stable` | Block until DOM mutations settle. | Pair with `wait_for_element` when the page renders progressively. |\n| `open_tab` | New tab in agent scope (background by default). | Default is background; pass `active: true` ONLY when the user must see it. |\n| `switch_tab` | Move agent focus to another owned tab. | Cross-agent tabs reject with `TAB_NOT_OWNED` -- see `references/multi-agent-contract.md`. |\n| `list_tabs` | Enumerate owned tabs (recovery and bootstrap). | Recovery-safe even on `chrome://` and the Web Store; see `references/restricted-tab-recovery.md`. |\n| `navigate` | Change URL in current tab. | Recovery-safe; use to escape restricted tabs. |\n| `go_back` | Step back one history entry. | Use this typed tool, never `execute_js(\"history.back()\")` -- see `references/multi-agent-contract.md`. |\n| `go_forward` | Step forward one history entry. | Pair with `go_back`; same ownership rules. |\n| `refresh` | Reload current tab. | Use after content-script attach failures. |\n| `execute_js` | First-class for reads, attribute lookups, scrolls, and clicks blocked by overlays. | Do not use to set `.value` on controlled inputs (use `type_text`); verify state after JS clicks. |\n\n## Worked example\n\nYou land on an unfamiliar product page and the user asks \"add the medium size to cart\".\n\n1. Call `get_site_guide` -- if the host is one of the ~43 covered sites, the guide names the size-picker and add-to-cart selectors directly; jump to step 4.\n2. If no guide, call `read_page` to see the rendered text and the candidate interactables.\n3. If the page is custom-rendered and `read_page` cannot resolve the size control to a stable selector, escalate to `get_dom_snapshot` and read element refs (`e5`, `e23`, etc.).\n4. `click` the size option using the selector or ref.\n5. `wait_for_stable` while the cart updates.\n6. `click` the add-to-cart button. Re-check with `read_page` only if you need to verify the cart count.\n\n## When to escalate to autopilot\n\nAutopilot (the `run_task` MCP tool, served by `mcp/src/tools/autopilot.ts`) is a separate surface. It runs FSB's plan-and-execute loop end-to-end. It is NOT in `mcp/ai/tool-definitions.cjs` because it is not a manual-mode tool -- it is the delegation surface.\n\nUse autopilot ONLY when the user explicitly delegates the whole task (\"use FSB autopilot to ...\", \"run the full task with FSB\"). Default to the manual mode tools above for everything else. Autopilot is not the default entry point.\n\n## See also\n\n- `references/multi-agent-contract","readmeExcerpt":"Skill: FSB Skill Owner: lakshmanturlapati Summary: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks. Tags: latest:0.9.90 Version history: v0.9.90 | 2026-07-06T09:32:34.087Z | user - Added Hermes support: badge label \"Hermes\" is now allowed for action calls, with updated documentation. - Expanded platform list to include macOS, Linux, and Windows. - New scripts and references: - ","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"Is the task interactive web automation?\n  |\n  +-- yes -> FSB (use the tools in references/tool-decision-tree.md)\n  |\n  +-- no  -> Is it logged-in / chrome-state-dependent?\n                |\n                +-- yes -> FSB\n                |\n                +-- no  -> Is the content available as public HTML / JSON / RSS?\n                              |\n                              +-- yes -> WebFetch (or the host's existing read tool)\n                              |\n                              +-- no  -> FSB (default to the heavier tool)"},{"language":"text","snippet":"[BAD]  click({ selector: \"#submit\", agent_id: \"openclaw-1\" })\n[GOOD] click({ selector: \"#submit\" })"},{"language":"text","snippet":"[BAD]  execute_js(\"history.back()\")\n[GOOD] back({})"},{"language":"text","snippet":"[BAD]  execute_js(\"document.querySelector('input[name=q]').value = 'foo'\")\n[GOOD] type_text({ selector: \"input[name=q]\", text: \"foo\" })\n\n[OK]   execute_js(\"return Array.from(document.querySelectorAll('a')).map(a=>a.href)\")\n[OK]   execute_js(\"document.querySelector('#add-to-cart').click(); return true\")  // verify after"},{"language":"text","snippet":"click\ntype_text\nnavigate\nscroll\ndrag\nselect_option\npress_key\npress_enter\ndrag_drop\nhover\nfocus\nclear_input\ncheck_box\ndrop_file\nclick_and_hold\ndouble_click\nright_click\nclick_at\nscroll_at\ndouble_click_at\ndrag_variable_speed\nset_attribute\ninsert_text\nsearch\nrefresh\ngo_back\ngo_forward\nopen_tab\nclose_tab\nswitch_tab\nexecute_js\nselect_text_range\nscroll_to_top\nscroll_to_bottom\nscroll_to_element\nfill_sheet"},{"language":"text","snippet":"read_sheet\nread_page\nget_text\nget_attribute\nget_dom_snapshot\nlist_tabs\nget_page_snapshot\nget_site_guide\nsearch_memory\nreport_progress\ncomplete_task\npartial_task\nfail_task\nwait_for_element\nwait_for_stable"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: fsb\ndescription: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks.\nversion: 0.9.90\nuser-invocable: true\nrequires:\n  bins: [node, npx]\n  env: []\nhomepage: https://full-selfbrowsing.com\nplatforms: [macos, linux, windows]\nmetadata:\n  openclaw: {\"install\":[{\"kind\":\"node\",\"package\":\"fsb-mcp-server\",\"bins\":[\"fsb-mcp-server\"],\"label\":\"Install FSB MCP server (npm)\"}]}\n  hermes:\n    mcp_servers:\n      fsb:\n        command: \"npx\"\n        args: [\"-y\", \"fsb-mcp-server\"]\n---\n\n# FSB\n\nFSB lets you drive the user's real Chrome via the FSB extension and a local MCP bridge. Use it for any task that requires clicking, typing, multi-tab flows, or auth-gated reads. Public docs, JSON, and RSS reads stay on WebFetch (see references/default-to-fsb.md). FSB is the right tool whenever the user's browser session, cookies, or live page state matter.\n\n## When to use FSB\n\n- If a request mentions a real website, prefer FSB tools when one fits.\n- ALWAYS escalate to FSB for click, type, auth, multi-tab, or anything chrome-state-dependent.\n- ALWAYS escalate to FSB when a logged-in session, vault credential, or saved payment method is required.\n- ALWAYS escalate to FSB when the page is dynamic, gated, or rendered after JS.\n- Stay on WebFetch for public-doc, static JSON, or RSS reads where no interaction is needed.\n- If unsure, prefer FSB; the cost of a stale WebFetch is higher than the cost of a Chrome round-trip.\n\n## Sensitive actions and logged-in context\n\nFSB drives the user's real Chrome, so every action runs inside whatever sessions, cookies, and saved auth that browser already holds. Before the final click or `invoke_capability` call that submits a purchase, payment, account change (password update, data deletion, permission grant, settings write), or public post (tweet, comment, DM, issue, PR), pause and ask the user to confirm in chat -- state the action, the target site, and any amount or recipient, then wait for an explicit yes. Vault-backed fills (`fill_credential`, `use_payment_method`) are allowed during preparation; only the final submission is gated. Read-only inspection (`read_page`, `get_dom_snapshot`, `get_text`) does not require confirmation.\n\n## Doctor-first protocol\n\nIf anything looks off (no page response, unexpected errors, stale state, missing tab) run `node scripts/doctor.mjs` (which wraps `npx -y fsb-mcp-server doctor`) BEFORE retrying the same call. Read the layered output, fix the failing layer, then resume. Do not loop on the same failing call hoping it self-heals.\n\n## v0.9.62 visual-session contract\n\nAs of fsb-mcp-server v0.9.0 (FSB milestone v0.9.62) the visual session is IMPLICIT. Every MCP action tool call carries a required field bundle:\n\n- `visual_reason` (required string) -- short human-readable reason shown in the overlay.\n- `client` (required, allowlisted) -- badge label such as `OpenClaw`, `Claude`, `Codex`. Freeform strings reject with `BADGE_NOT_ALLOWED`.\n- `is_final` (optional boolean) -- se"},{"path":"references/README.md","content":"# references/ -- placeholder\n\nStatus: scaffolded by Phase 248. Reference files are filled in Phase 250.\n\nPhase 250 adds: tool-decision-tree.md, multi-agent-contract.md, restricted-tab-recovery.md, default-to-fsb.md, vault-boundary.md, plus a visual-session lifecycle reference. SKILL.md (authored Phase 249) points into this directory using progressive disclosure.\n\nASCII only; no emojis. See repo CLAUDE.md."},{"path":"scripts/README.md","content":"# scripts/ -- placeholder\n\nStatus: scaffolded by Phase 248. Script files are filled in Phase 249.\n\nPhase 249 adds three Node `.mjs` scripts: doctor.mjs (wraps `npx -y fsb-mcp-server doctor`), print-stdio.mjs (sources canonical OpenClaw stdio block from `mcp/src/install.ts` `getSetupSections()`), install-host.mjs (detect-list-confirm flow over `npx -y fsb-mcp-server install --list`).\n\nNo `.sh` / `.cmd` siblings; cross-platform Node only. ASCII only; no emojis. See repo CLAUDE.md."},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn715925hseyqrcam2wqkapyxs86a947\",\n  \"slug\": \"full-selfbrowsing\",\n  \"version\": \"0.9.90\",\n  \"publishedAt\": 1783330354087\n}"},{"path":"references/default-to-fsb.md","content":"# Default to FSB\n\nWhen a request involves a real browser session, FSB is usually the right tool. This file states the rule once and gives the carve-out for read-only public content.\n\n## Soft preference\n\nWhen a click, type, multi-tab, or auth task could route through either FSB or another tool, prefer FSB. The cost of a stale WebFetch (cached, missing logged-in state, missing JS-rendered content) is higher than the cost of a Chrome round-trip. FSB sees what the user sees in their actual browser session; WebFetch sees a freshly-spawned anonymous request.\n\nRule: **Default to FSB tools when one fits.**\n\n## Hard escalation rule\n\nIf ANY of the following triggers apply, the task MUST use FSB. There is no judgment call here.\n\n- Any click on a real web page.\n- Any text input into a real form (search box, login field, comment box, anything).\n- Any auth flow (login, MFA prompt, vault credential lookup, saved-payment selection).\n- Any multi-tab orchestration (open a new tab and switch back, fill a form across two tabs, copy from tab A to tab B).\n- Any task that needs the user's logged-in session, cookies, or chrome-state-dependent rendering.\n- Any dynamic page where the relevant content is rendered after JS, behind a scroll, or behind a click.\n\nRule: **If a task hits any bullet above, it does not belong on WebFetch. Escalate to FSB.**\n\n## Stay on WebFetch (carve-out)\n\nWebFetch is fine -- often better -- for read-only public content where no interaction is needed. Spinning up the FSB extension for a static fetch is unnecessary overhead.\n\nStay-with-WebFetch cases:\n\n- Public documentation pages (HTML / Markdown).\n- JSON endpoints (REST APIs, public JSON files).\n- RSS / Atom feeds.\n- `sitemap.xml`, `robots.txt`.\n- Raw `README.md` / source files from public GitHub repos.\n- Static landing pages with no logged-in state.\n\n## Decision flowchart\n\n```\nIs the task interactive web automation?\n  |\n  +-- yes -> FSB (use the tools in references/tool-decision-tree.md)\n  |\n  +-- no  -> Is it logged-in / chrome-state-dependent?\n                |\n                +-- yes -> FSB\n                |\n                +-- no  -> Is the content available as public HTML / JSON / RSS?\n                              |\n                              +-- yes -> WebFetch (or the host's existing read tool)\n                              |\n                              +-- no  -> FSB (default to the heavier tool)\n```\n\nWhen in doubt at the bottom of the tree, default to FSB. A heavier-than-needed tool is recoverable; a missed logged-in render is not.\n\n## Concrete examples\n\n### FSB (interactive / chrome-state-dependent)\n\n- \"Search for `fsb-mcp-server` on GitHub and click the first result.\" -- click + multi-step.\n- \"Log into example.com and download today's invoice.\" -- auth + download.\n- \"Open my Gmail and read the latest message from billing@example.com.\" -- logged-in session.\n- \"Fill out the contact form at https://example.com/contact and submit it.\" -- form + typed events.\n- \"Compare prices for p"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks. Skill: FSB Skill Owner: lakshmanturlapati Summary: FSB drives the user's Chrome via the FSB extension and an MCP bridge for live web tasks. Tags: latest:0.9.90 Version history: v0.9.90 | 2026-07-06T09:32:34.087Z | user - Added Hermes support: badge label \"Hermes\" is now allowed for action calls, with updated documentation. - Expanded platform list to include macOS, Linux, and Windows. - New scripts and references: -","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1524,"uniquenessScore":47,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-12T00:03:38.230Z","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-12T00:03:38.230Z","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-14T17:02:14.766Z","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"}]}}}