v0.9.62 — Implicit Visual Session Contract#33
Conversation
…ed at milestone level)
…p back / get_logs / get_task_status; add 6 actions; wait_for_* -> read-only)
…ly, field bundle, badge allowlist citation, typed errors) Phase 254 hard gate. Single-source-of-truth pinned at .planning/v0.9.62-CONTRACT.md: - Canonical action-tool list: 36 tools (live TOOL_REGISTRY filtered to _readOnly:false, minus wait_for_* reclassified). - Field-bundle keys: visual_reason (required string), client (required, allowlisted), is_final (optional boolean). - Badge-allowlist citation: extension/utils/mcp-visual-session.js -- MCP_VISUAL_CLIENT_LABELS / normalizeMcpVisualClientLabel / isAllowedMcpVisualClientLabel / getAllowedMcpVisualClientLabels. No per-tool duplication. - Typed-error names: VISUAL_FIELDS_REQUIRED, BADGE_NOT_ALLOWED, TOOL_REMOVED. - Read-only schema-lock list: 15 tools (13 from registry + wait_for_element + wait_for_stable reclassified). - Resolutions applied: dropped back / get_logs / get_task_status (transcription artifacts); added 6 actions (execute_js, select_text_range, scroll_to_*, fill_sheet); reclassified wait_for_* to read-only. - Sign-off line: Contract pinned: 2026-05-11 -- v0.9.62 Implicit Visual Session Contract -- LakshmanTurlapati. No code modified. No tests added. No MCP server runtime behaviour changes. Phases 255-260 reference this artifact by path + sign-off line. Requirements: CONTRACT-01. Contract pinned: 2026-05-11 -- v0.9.62 Implicit Visual Session Contract -- LakshmanTurlapati.
…-fragment + dispatch-chokepoint validator + 6-commit shape)
Phase 255 (Schema Enforcement on Action Tools) -- decomposes CONTRACT-02 / 03 / 04 / 05
into 4 parallel-optimised plans:
Wave 1 (independent file scopes):
- 255-01-PLAN.md: declare VISUAL_SESSION_FIELDS fragment + withVisualSessionFields
helper in mcp/ai/tool-definitions.cjs, apply to canonical 36 action tools, flip
_readOnly: true on wait_for_element / wait_for_stable, mirror byte-identical to
extension/ai/tool-definitions.js (CONTRACT-02, CONTRACT-05).
- 255-02-PLAN.md: register VISUAL_FIELDS_REQUIRED + BADGE_NOT_ALLOWED in
CODE_ONLY_ERROR_KEYS and buildLayeredDetail switch arms in mcp/src/errors.ts
(CONTRACT-03, CONTRACT-04 error-name registration; TOOL_REMOVED reserved for
Phase 258).
Wave 2 (depends on 01 + 02):
- 255-03-PLAN.md: wire validateVisualSessionFields + stripVisualSessionFields into
the dispatch chokepoint at registerManualTools (mcp/src/tools/manual.ts); widen
exports of isAllowedMcpVisualClientLabel / getAllowedMcpVisualClientLabels in
mcp/src/tools/visual-session.ts so the dispatcher imports the shared v0.9.36
allowlist (CONTRACT-03, CONTRACT-04 runtime enforcement).
- 255-04-PLAN.md: author tests/visual-session-schema-lock.test.js asserting
schema-shape invariants for the 36 action tools + 15 read-only tools, plus
dispatcher-rejection invariants for both typed errors via a recording mock
bridge; wire into root npm test chain (CONTRACT-02 / 03 / 04 / 05 CI lock).
Every plan cites .planning/v0.9.62-CONTRACT.md verbatim for canonical name lists,
field-bundle keys, typed-error names, and read-only schema-lock set. Every task has
grep-verifiable acceptance criteria and a <read_first> block. <threat_model> blocks
cover the four threats called out in the planning context (allowlist bypass,
visual_reason as overlay injection vector [downstream], denial-of-action by
rejection, validator skip via _readOnly misclassification).
ROADMAP.md Phase 255 placeholders finalised: Plans count set to 4 with the four
plan entries enumerated; progress table 0/TBD -> 0/4.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d reclassify wait_for_* - Declare VISUAL_SESSION_FIELDS fragment + withVisualSessionFields helper at the top of mcp/ai/tool-definitions.cjs. - Apply withVisualSessionFields() to every action tool in the canonical 36-name list pinned in .planning/v0.9.62-CONTRACT.md (Action Tools section). - Flip _readOnly: false -> true on wait_for_element and wait_for_stable to match the milestone-level reclassification recorded in .planning/v0.9.62-CONTRACT.md (Read-Only Tools section + Resolutions applied entry 2). - Mirror the edits byte-for-byte to extension/ai/tool-definitions.js so tests/tool-definitions-parity.test.js stays green. Schema-only change. No validator runs yet (Plan 03), no error names are defined yet (Plan 02). Phase 255 / Plan 01. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… + helper + 36 wraps + wait_for_* reclassification Records the artifacts wired by Plan 01 (mcp/ai/tool-definitions.cjs + extension/ai/tool-definitions.js byte-identical mirror), the verification commands future plans can rerun, and the handoff to Plan 02 (typed errors) and Plan 03 (dispatch validator). Includes the verbatim 36-name action-tool list and 15-name read-only schema-lock list copied from .planning/v0.9.62-CONTRACT.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Register VISUAL_FIELDS_REQUIRED and BADGE_NOT_ALLOWED in CODE_ONLY_ERROR_KEYS so mapFSBError() routes them through the layered-detail path. - Add two switch arms in buildLayeredDetail() producing the Detected / Why / Next-action body specified in .planning/v0.9.62-CONTRACT.md Typed Errors section. - LAYER_LABELS.visualSession is reused (already exists for invalid_client_label / visual_session_not_found / visual_surface_busy). - TOOL_REMOVED is NOT added; it lands in Phase 258. Error-name declaration only. The dispatch validator that actually raises these codes lands in Plan 03 (mcp/src/tools/manual.ts). Phase 255 / Plan 02. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…_REQUIRED + BADGE_NOT_ALLOWED summary Summary of Plan 02 -- adds the two SCREAMING_SNAKE_CASE typed-error codes to mcp/src/errors.ts CODE_ONLY_ERROR_KEYS and wires matching buildLayeredDetail() switch arms under LAYER_LABELS.visualSession. Documents the exact edits, verbatim Detected / Why / Next-action text for downstream Plan 03 / 04 assertions, body-intent provenance against .planning/v0.9.62-CONTRACT.md, and the explicit exclusion of TOOL_REMOVED (reserved for Phase 258). Self-check PASSED: errors.ts grep audit, regenerated mcp/build/errors.js end-to-end check via mapFSBError, and zero regression on the pre-existing tests/mcp-recovery-messaging.test.js (63/63). Phase 255 / Plan 02. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…badge allowlist - Add validateVisualSessionFields() to mcp/src/tools/manual.ts. Runs in every action-tool handler before execAction(). Rejects missing/empty visual_reason or client with VISUAL_FIELDS_REQUIRED. Rejects non-allowlisted client values with BADGE_NOT_ALLOWED. - Add stripVisualSessionFields() so the field bundle is removed from the params object before being forwarded to the FSB extension over the WebSocket bridge. - Export isAllowedMcpVisualClientLabel + normalizeMcpVisualClientLabel + getAllowedMcpVisualClientLabels from mcp/src/tools/visual-session.ts so the dispatcher imports the shared v0.9.36 allowlist instead of declaring a duplicate. - Read-only tools are not touched (they register through mcp/src/tools/read-only.ts, which does not import the validator). - is_final is accepted by the schema (Plan 01) and stripped here; runtime semantics land in Phase 257. Phase 255 / Plan 03. Depends on Plan 01 (schema declared) + Plan 02 (error codes registered). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…+ badge allowlist summary - Records the validateVisualSessionFields + stripVisualSessionFields function shapes wired into registerManualTools at the action-tool chokepoint. - Documents the 3-step validation order (visual_reason -> client -> allowlist) and the typed-error routing (VISUAL_FIELDS_REQUIRED, BADGE_NOT_ALLOWED). - Confirms via in-process integration check that bridge.sendAndWait / queue.enqueue / sendAgentScopedBridgeMessage do NOT fire on any rejection path. - Points Plan 04 at the schema-lock test surface that exercises these invariants. Phase 255 / Plan 03. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add tests/visual-session-schema-lock.test.js asserting (1) every action tool in the canonical 36-name list carries visual_reason + client + is_final in inputSchema.properties and visual_reason + client in inputSchema.required, (2) every read-only tool in the 15-name list has none of those fields in inputSchema.properties, (3) wait_for_element and wait_for_stable have _readOnly: true, (4) the dispatcher rejects missing-field calls with VISUAL_FIELDS_REQUIRED routing and blocks bridge / queue invocation, (5) the dispatcher rejects non-allowlisted client values with BADGE_NOT_ALLOWED routing. - Wire the test into the root npm test chain alongside tests/tool-definitions-parity.test.js so the ci / all-green gate enforces the contract. - Update tests/agent-id-threading.test.js navigate invocation to pass the v0.9.62 field bundle (visual_reason + client). Plan 03's dispatcher validator rejects calls missing the bundle; the existing parallel-navigate test was authored before Plan 03 and needed the bundle to reach the post-validator code path it actually tests (Rule 3 auto-fix to keep the npm test chain green). - Source-of-truth for tool name lists: .planning/v0.9.62-CONTRACT.md (embedded verbatim in the test so registry regressions surface as FAIL, not as silently-shrunk derived lists). Phase 255 / Plan 04. Depends on Plans 01-03. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…session contract summary - Records the new tests/visual-session-schema-lock.test.js (314 assertions / 0 failed) that locks the v0.9.62 contract: 36 action tools carry visual_reason / client / is_final in inputSchema.properties + visual_reason / client in inputSchema.required; 15 read-only tools have NONE of the bundle keys; wait_for_element + wait_for_stable are _readOnly:true; dispatcher rejects missing-field calls with VISUAL_FIELDS_REQUIRED and non-allowlisted client values with BADGE_NOT_ALLOWED; rejections never invoke bridge.sendAndWait or queue.enqueue. - Documents the Rule 3 auto-fix to tests/agent-id-threading.test.js (pre-existing failure introduced by Plan 03's validator; navigate calls now thread the v0.9.62 field bundle so they reach the post-validator code path the test was designed to exercise). - Confirms `npm test` exits 0 with 2582 PASS, 0 FAIL after this plan lands. - Points downstream phases (256 lifecycle, 257 is_final semantics, 258 explicit-tool removal, 259 broader CI gate) at the contract surface this test locks. Phase 255 / Plan 04. Final docs commit completing the milestone-level schema enforcement work for v0.9.62. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
….storage.session + chrome.alarms; reuses v0.9.36 renderer)
…h recordVisualSessionTick + clearVisualSession + tab-removed cleanup - Pure-helper module owning the v0.9.62 sliding-window lifecycle state machine. - Storage key prefix mcpVisualSession:<tabId> (no collision with v0.9.36 fsbMcpVisualSessions). - Alarm name prefix mcpVisualDeath:<tabId> (no collision with fsb-mcp-bridge-reconnect or fsb-domstream-watchdog). - 60s sliding-window TTL exported as MCP_VISUAL_LIFECYCLE_DEATH_MS named constant. - Defense-in-depth: re-normalizes client via normalizeMcpVisualClientLabel even though Phase 255 already rejects at the MCP schema layer (T-256-01-01). - Belt-and-suspenders: rejects same-tab cross-agent ticks with agent_mismatch even though v0.9.60 ownership gate is the primary defense (T-256-01-04). - Reuses v0.9.36 buildMcpVisualSessionStatus + buildMcpVisualSessionClearStatus so overlay payload is byte-compatible with explicit start_visual_session. - Pure helpers only; no SW listeners wired (Plan 03 owns chrome.alarms.onAlarm + chrome.tabs.onRemoved + SW-startup restore call).
…isualSessionLifecycleAlarm to lifecycle module - handleVisualSessionLifecycleAlarm (TIMEOUT-03): inspects the alarm payload, looks up the storage entry, clears the session if Date.now() >= deadlineAt, or reschedules with the original deadline if the alarm fired early (clock-skew defense, T-256-01-06). Returns noop_no_entry when the storage entry has already been cleared by another path (T-256-01-05). - restoreVisualSessionLifecyclesFromStorage (TIMEOUT-04): walks every mcpVisualSession:* key in chrome.storage.session at SW startup, drops malformed entries, clears stale ones, and re-arms alarms with the ORIGINAL deadlineAt so the sliding window survives MV3 SW eviction transparently (T-256-01-03 unbounded-growth mitigation also enforced via the dropped-entry path). - Both functions exported on the module's global namespace MCPVisualSessionLifecycleUtils and on module.exports for Node test access; helpers remain pure (Plan 03 wires the SW listeners).
…ortScripts chain - Single-line insertion at extension/background.js:11, immediately after the existing utils/mcp-visual-session.js import on line 10. - Places the lifecycle module BEFORE utils/agent-registry.js (line 12) so the v0.9.36 MCPVisualSessionUtils global is already on globalThis when the lifecycle module's IIFE evaluates. - No listener wiring, no calls to restoreVisualSessionLifecyclesFromStorage, no chrome.alarms.onAlarm or chrome.tabs.onRemoved handlers in this plan -- Plan 03 owns the integration. This task only loads the helpers into the SW namespace. - npm test continues to exit 0.
…ired; helpers pure (Plan 03 wires) 3/3 must_have artifacts produced. All 5 named helpers + 3 namespace constants verified via Node smoke tests. npm test exits 0. Commits: 42de569 (Task 1 record/clear/tab-removed) + f77a478 (Task 2 alarm-fired + SW-startup restore) + fb88f58 (Task 3 importScripts wiring). STATE.md and ROADMAP.md intentionally NOT touched per user instructions.
…visualSession) on mcp:execute-action payloads
- Add buildVisualSessionSidecar(params) helper next to stripVisualSessionFields. Reads visual_reason / client / is_final from the ORIGINAL caller params (before the strip), trims the reason, re-normalises client to canonical casing via normalizeMcpVisualClientLabel, defaults isFinal to false, returns { visualReason, client, isFinal }.
- Extend the existing visual-session.js import to also pull in normalizeMcpVisualClientLabel.
- Grow execAction with a 7th parameter visualSession: VisualSessionSidecar | null. Inside queue.enqueue, build a basePayload object { tool, params } and conditionally attach the sidecar at the top level so it lands as a sibling of agentId / ownershipToken / connectionId in agent-bridge.ts buildAgentPayload.
- Wire the chokepoint arrow: after validateVisualSessionFields passes, capture the sidecar from the ORIGINAL params, then run the Phase 255 strip helper on params, then call execAction with the cleaned params plus the sidecar.
The Phase 255 invariants are preserved verbatim:
- validateVisualSessionFields runs first.
- Rejections short-circuit before any side effect (no sidecar built, no bridge invoked).
- stripVisualSessionFields still scrubs the action params (no visual_reason / client / is_final leaks through to the FSB action handler).
The bridge wire shape for every action-tool call is now { tool, params, visualSession: { visualReason, client, isFinal } } at the message-payload top level, alongside agentId. This is what Plan 03 (extension-side dispatcher) will read after the v0.9.60 ownership gate fires to record a lifecycle tick.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…bridge payload now carries visualSession alongside agentId Records the single-task completion of the Phase 256 Plan 02 chokepoint edit. The Phase 255 validate-then-strip pattern is preserved verbatim; the validated field bundle is captured as a sidecar BEFORE the strip and travels as a top-level `visualSession` object on the mcp:execute-action bridge payload, sibling to agentId / ownershipToken / connectionId. This is what Plan 03 reads extension-side after the v0.9.60 ownership gate fires. Per executor instructions: STATE.md and ROADMAP.md are NOT touched in this plan. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ction after v0.9.60 ownership gate - Add _recordVisualSessionTickIfPresent helper on MCPBridgeClient that reads payload.visualSession (Plan 02 sidecar) and fires recordVisualSessionTick from the lifecycle module; no-op when sidecar absent. - Resolved-tab branch: invoke the hook AFTER resolveAgentTabOrError approves (resolved.success === true) and BEFORE executeFn -- ordering keeps TIMEOUT-05 (ownership-gating wins) verifiable by grep. - Bootstrap branch (open_tab / switch_tab): invoke the hook POST-dispatch on success using the tabId returned by sanitizeSingleTab. - Non-blocking try/catch; lifecycle failures never break the underlying action.
…d SW-startup restore for visual-session lifecycle - chrome.alarms.onAlarm: new prefix-matched branch routes mcpVisualDeath:* alarms to MCPVisualSessionLifecycleUtils.handleVisualSessionLifecycleAlarm before the existing MCP_RECONNECT_ALARM and fsb-domstream-watchdog arms. - chrome.tabs.onRemoved: new listener invokes handleVisualSessionLifecycleTabRemoved(tabId), mirroring the existing multi-listener tab-close cleanup pattern (lines 2526, 2584, 12826). - SW boot: invoke restoreVisualSessionLifecyclesFromStorage once inside restoreSessionsFromStorage, adjacent to restorePersistedMcpVisualSessions, to replay live deadlines after MV3 SW eviction (TIMEOUT-04). - All three hooks are guarded with typeof checks and non-blocking catches so the lifecycle module's absence never poisons SW boot or existing listeners.
… live (hook in _handleExecuteAction; SW alarm + tabs.onRemoved + boot-time restore)
… all 5 TIMEOUT REQ-IDs at the helper level
Cases A-I (62 assertions, all green):
A. recordVisualSessionTick on a fresh tab writes the documented shape
and creates the death alarm (TIMEOUT-01 implicit start).
B. recordVisualSessionTick on same tab+agent re-arms the sliding window
and preserves startedAt (TIMEOUT-02).
C. recordVisualSessionTick from a different agentId returns
reason=agent_mismatch and does not mutate storage / alarms
(TIMEOUT-05 defense-in-depth).
D. handleVisualSessionLifecycleAlarm at-or-after deadline deletes the
entry, broadcasts a phase=ended clear, and clears the alarm
(TIMEOUT-03 auto-clear).
E. handleVisualSessionLifecycleAlarm before deadline reschedules with
the ORIGINAL deadlineAt and does not broadcast a clear.
F. restoreVisualSessionLifecyclesFromStorage replays live entries with
the original when, auto-clears elapsed entries, and drops malformed
entries (TIMEOUT-04 SW-eviction replay).
G. handleVisualSessionLifecycleTabRemoved drops storage + alarm with
skipBroadcast=true (the tab is gone).
H. recordVisualSessionTick with a non-allowlisted client returns
reason=client_not_allowed without mutating storage or alarms
(defense-in-depth dual of Phase 255 BADGE_NOT_ALLOWED).
I. Lifecycle module's export surface is structurally action-tool-only
-- no recordReadOnlyTick / handleReadOnlyTool / readToolNoOp.
Test harness uses an in-memory chrome.storage.session + chrome.alarms
mock (createChromeMock pattern copied verbatim from
tests/mcp-bridge-client-lifecycle.test.js lines 118-146), assigns the
v0.9.36 utils to global.MCPVisualSessionUtils, and installs a fake
sendSessionStatus to capture broadcasts. Each case clears the lifecycle
module's require.cache entry so the IIFE rebinds against a fresh global.
ASCII-only. 411 lines. Node stdlib only -- no new dependencies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…root npm test chain Slots between visual-session-schema-lock.test.js (Phase 255 Plan 04 schema-lock invariants) and agent-id-threading.test.js, keeping the visual-session test family adjacent in the chain. One-line edit; net diff is exactly one inserted substring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…udit Phase 255/256 coverage
…cases; covers TEST-01, TEST-02, TEST-05)
…t contract + TOOL_REMOVED (TEST-01, TEST-02, TEST-05) Rewrite tests/mcp-visual-session-contract.test.js end-to-end for the v0.9.62 implicit visual-session contract. Pre-v0.9.62 cases targeting the explicit start_visual_session / end_visual_session lifecycle are obsolete (Phase 258 converted both tools to TOOL_REMOVED stubs). The new file enumerates 5 cases: 1. runAllowlistAndManagerCase -- locks the v0.9.36 shared badge allowlist module surface (MCP_VISUAL_CLIENT_LABELS, normalizeMcpVisualClientLabel, isAllowedMcpVisualClientLabel, getAllowedMcpVisualClientLabels) still reused by the v0.9.62 contract. Defensive-copy invariant included. 2. runPersistenceReplayCase -- replays per-tab entries against the new mcpVisualSession:<tabId> namespace (Phase 256 lifecycle module). Live entries re-arm the death alarm at the ORIGINAL deadlineAt (TIMEOUT-04); stale entries immediate-clear (storage removed, no alarm). Replaces the v0.9.36 fsbMcpVisualSessions single-key replay. 3. runImplicitDispatcherValidationCase -- loads mcp/build/tools/manual.js registerManualTools dispatcher with a recording-mock bridge + queue + agentScope. Asserts: missing visual_reason -> VISUAL_FIELDS_REQUIRED (no bridge/queue); empty visual_reason -> VISUAL_FIELDS_REQUIRED; non-allowlisted client -> BADGE_NOT_ALLOWED (echoes offending label); valid bundle -> bridge.sendAndWait with visualSession sidecar at top level and visual_reason/client/is_final stripped from action params; is_final: true forwarded into sidecar.isFinal. 4. runOwnershipPrecedenceCase -- DOWNSCOPED per plan clause: tests the defense-in-depth dual at the lifecycle layer rather than the full _handleExecuteAction dispatcher (which would require harnessing resolveAgentTabOrError, dispatchMcpToolRoute, wrapWithChangeReport, and a live AgentRegistry). Asserts: cross-agent tick on owned tab returns ok=false reason=agent_mismatch; owning agent's storage entry untouched (agentId, client, visualReason, lastTickAt, deadlineAt all preserved); no broadcast on rejection; owner can still tick afterwards. 5. runToolRemovedCase -- loads mcp/build/tools/visual-session.js registerVisualSessionTools and invokes both removed handlers. Asserts the typed TOOL_REMOVED envelope returns with isError: true; no bridge or queue call; response text names the removed tool, cites v0.9.0, references the implicit contract fields (visual_reason / client / is_final), and references CHANGELOG + README anchors. The recording-mock pattern uses counters that explicitly assert "no bridge.sendAndWait call" and "no queue.enqueue call" on rejection paths (Phase 255 schema-lock test pattern reuse). Verification: - node tests/mcp-visual-session-contract.test.js -- 116 PASS, 0 FAIL, exit 0 - npm test -- entire chain passes, exit 0 - File contains literal strings: VISUAL_FIELDS_REQUIRED, BADGE_NOT_ALLOWED, TOOL_REMOVED, visual_reason, client, is_final - File has 5 distinct case functions - File does NOT contain the obsolete "start_visual_session.*returns session" assertion (Phase 258 converted to TOOL_REMOVED stub) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Document the test rewrite end-to-end: 5 cases (allowlist + persistence + implicit dispatcher validation + ownership precedence + TOOL_REMOVED), 116 assertions, both node and npm test exit 0, single downscope on Case 4 applied per plan's explicit constraint clause (lifecycle-layer test in place of full dispatcher harness). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…it contract (DOCS-01) - add v0.9.0 breaking-change banner at top with CHANGELOG + README anchors - replace "Visual session handling" section with "v0.9.62 visual-session contract" - document the field bundle (visual_reason / client / is_final) and read-tool exemption - two worked examples: action call with bundle + final action with is_final: true - list allowlisted client labels and typed-error names - preserve autopilot exception note; preserve doctor-recovery table unchanged
…ontract (DOCS-02) - full rewrite -- replace explicit start/end pairing with implicit field-bundle lifecycle - document visual_reason / client / is_final fields and v0.9.36 client allowlist - sliding 60-second window mechanics + is_final immediate clear - read-tool vs action-tool split with concrete contrast example - bootstrap path (NO_OWNED_TAB recovery) + typed-error catalogue - pointer to .planning/v0.9.62-CONTRACT.md for canonical lists
…d (DOCS-03) - new "v0.9.62 field bundle (action tools only)" section at top - enumerate all 36 action tools that require visual_reason + client + optional is_final - list 15 read-only tools exempted from the bundle - new "Action-tool bundle reminder" section after the quick-reference table - preserve read-first guidance unchanged (read_page / get_dom_snapshot / get_page_snapshot) - update "When to escalate to autopilot" with autopilot exception note - add visual-session-lifecycle.md + v0.9.62-CONTRACT.md to See also
…cal artifact (DOCS-04) - replace "Visual session wrapping" with "v0.9.62 visual-session contract" - summarize field bundle (visual_reason / client / is_final) and sliding window - link to .planning/v0.9.62-CONTRACT.md as the single source of truth (do NOT inline the 36 + 15 lists) - name the three typed errors (VISUAL_FIELDS_REQUIRED, BADGE_NOT_ALLOWED, TOOL_REMOVED) - add visual-session-lifecycle.md + contract artifact to References section - frontmatter unchanged (version 0.9.61 / bins / metadata.openclaw preserved per skill-fsb-spec.test.js)
…n check - visual_reason and is_final are MCP action-tool input fields, not tool names - the existing token-cross-reference heuristic flagged them as unknown tools because they are backtick-wrapped in tool-decision-tree.md (DOCS-03 update) - extend nonToolAllowlist to declare them as known non-tool tokens, matching the existing precedent for agent_id / session_token / client / reason - Rule 3 fix: blocking issue caused by phase 260 doc updates
…test fix) Phase 260 closes milestone v0.9.62 by aligning the 4 FSB Skill documentation surfaces with the implicit visual-session contract (no explicit start/end pairing, required field bundle on every action tool, sliding 60s window, is_final immediate clear). All checks pass; tests/skill-fsb-spec.test.js 48/48 PASS; npm test exit 0. STATE.md / ROADMAP.md left untouched per phase prompt instruction.
…SUMMARY for execution)
…es, 13/13 integration points)
Phases 254-260 shipped 2026-05-11 on branch `refinements`. 27/27 v1 requirements satisfied; audit `passed`; 13/13 cross-phase integration points verified; full `npm test` chain (74 files) green. Archived: - .planning/milestones/v0.9.62-ROADMAP.md - .planning/milestones/v0.9.62-REQUIREMENTS.md - .planning/milestones/v0.9.62-MILESTONE-AUDIT.md ROADMAP.md collapsed to one-line summary; REQUIREMENTS.md retired (fresh one created at next `/gsd-new-milestone`). Final `npm publish fsb-mcp-server@0.9.0` remains user-gated.
… tag created locally; push user-gated)
…lestone) Bumps all current-version markers in lockstep with the just-shipped v0.9.62 Implicit Visual Session Contract milestone: - extension/manifest.json: name + version - extension/README.md: header reference - README.md: heading + badge + production-ready note (3 occurrences) - store-assets/chrome-web-store/listing-copy.md: title - showcase/angular/src/app/core/seo/version.ts: APP_VERSION - showcase/angular/package.json + package-lock.json - package.json: root project version - mcp/README.md: cross-version note (MCP 0.9.0 + extension 0.9.62) Historical "(v0.9.60 ownership gate)" / "Phase 245 (v0.9.60)" / etc. in-code comments NOT touched -- they document phase history, not current version. CLAUDE.md local heading also bumped (file is gitignored; not in this commit).
The committed build/install.js was stale relative to mcp/src/install.ts. The source already contains the OpenClaw FSB-skill canonical install copy landed during the v0.9.62 milestone; the build output was not regenerated when those changes were committed. Rebuilding via npm --prefix mcp run build brings build/install.js into sync.
The mcp-tool-smoke test was missed during the v0.9.62 milestone sweep and
asserted pre-contract behaviour:
- navigate/click/close_tab were called without the now-required
visual_reason + client field bundle, so the new dispatcher gate
rejected them with VISUAL_FIELDS_REQUIRED before reaching the bridge.
- start_visual_session + end_visual_session asserted bridge dispatch,
but those handlers are now TOOL_REMOVED synchronous stubs (Phase 258
Plan 01) that short-circuit before queue.enqueue and before the bridge
connectivity check.
- The queue coverage loop expected start/end_visual_session to pass
through queue.enqueue; stubs do not.
Fixes:
- Add visual_reason + client to navigate/click/close_tab calls and
extend the expected bridge payload with the v0.9.62 visualSession
sidecar (camelCase wire form: visualReason / client / isFinal).
- Replace start/end_visual_session dispatch assertions with TOOL_REMOVED
no-dispatch assertions.
- Add start/end_visual_session to the DIRECT_TOOLS set so the queue
coverage loop skips them, with explicit negative assertions.
All 84 smoke assertions pass; full CI green.
Regenerated by scripts/build-crawler-files.mjs as part of the showcase prebuild step. Sitemap loc lastmod dates bumped to 2026-05-12. No content changes.
There was a problem hiding this comment.
💡 Codex Review
https://github.com/LakshmanTurlapati/FSB/blob/11c043d6f8734580704164fcfcbe887372a1061a/extension/ws/mcp-bridge-client.js#L735-L737
Record lifecycle tick in navigate no-owned-tab fallback
The navigate recovery branch for NO_OWNED_TAB returns early via dispatchWithoutResolvedTab(...) and skips _recordVisualSessionTickIfPresent, so this valid action path never starts/refreshes the implicit visual session despite requiring visual_reason and client. This creates inconsistent contract behavior specifically when an agent has no owned tabs and navigate uses the bootstrap path.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (now >= deadlineAt) { | ||
| // TIMEOUT-03 auto-clear path. | ||
| await clearVisualSession(tabId, { reason: 'timeout' }); | ||
| return { ok: true, action: 'cleared', entry: entry }; |
There was a problem hiding this comment.
Re-check timeout state before clearing a visual session
The alarm path clears the session based on a stale entry.deadlineAt read and then calls clearVisualSession(tabId) unconditionally; if an action tick refreshes the session between these async steps, the timeout handler can delete a newly refreshed session and clear the overlay while automation is still active. This race is reachable around the 60s boundary because recordVisualSessionTick and handleVisualSessionLifecycleAlarm run as separate async events, so the timeout branch should re-validate expiry (or compare a version/lastTick snapshot) immediately before deletion.
Useful? React with 👍 / 👎.
| if (now >= deadlineAt) { | ||
| // TIMEOUT-03 auto-clear path. | ||
| await clearVisualSession(tabId, { reason: 'timeout' }); | ||
| return { ok: true, action: 'cleared', entry: entry }; |
There was a problem hiding this comment.
Re-check session deadline before clearing on alarm
The timeout handler clears based on a previously read entry.deadlineAt and then calls clearVisualSession(tabId) without re-validating state; if a fresh action tick updates the same tab between these async steps, the handler can delete the newly refreshed session and clear the overlay while work is still active. This is a race around the 60s boundary because recordVisualSessionTick and alarm handling run as separate async events.
Useful? React with 👍 / 👎.
| @@ -535,7 +595,7 @@ const TOOL_REGISTRY = [ | |||
| required: ['selector'] | |||
| }, | |||
| _route: 'content', | |||
| _readOnly: false, | |||
| _readOnly: true, | |||
There was a problem hiding this comment.
Keep wait_for_ tools registered after reclassification*
Marking wait_for_element/wait_for_stable as _readOnly: true moves them out of manual registration, but registerReadOnlyTools only registers names present in MESSAGE_TYPE_MAP, which does not include either tool. As a result, both tools silently disappear from the MCP surface (the loop continues when no message builder exists), creating a functional regression for callers that rely on them.
Useful? React with 👍 / 👎.
| if (now >= deadlineAt) { | ||
| // TIMEOUT-03 auto-clear path. | ||
| await clearVisualSession(tabId, { reason: 'timeout' }); | ||
| return { ok: true, action: 'cleared', entry: entry }; |
There was a problem hiding this comment.
Re-check session deadline before timeout clear
The alarm path clears based on a previously read entry.deadlineAt and then deletes the session without re-reading current state; if a new action tick refreshes the same tab between these async steps, the timeout handler can remove that fresh session and clear the overlay while automation is still active. Re-validate expiry (or compare a version/lastTick snapshot) immediately before deletion.
Useful? React with 👍 / 👎.
| @@ -535,7 +595,7 @@ const TOOL_REGISTRY = [ | |||
| required: ['selector'] | |||
| }, | |||
| _route: 'content', | |||
| _readOnly: false, | |||
| _readOnly: true, | |||
There was a problem hiding this comment.
Keep wait_for tools registered after read-only flip
Reclassifying wait_for_element as _readOnly: true moves it out of manual registration, but the read-only registrar only wires tools present in MESSAGE_TYPE_MAP; since wait_for_element/wait_for_stable are not mapped there, both handlers are skipped and disappear from the MCP surface. This is a functional regression for existing callers of those wait tools.
Useful? React with 👍 / 👎.
| // (v0.9.60 ownership gate) and BEFORE the underlying executeFn fires. | ||
| // TIMEOUT-05 ownership-gating-wins: this line is unreachable when | ||
| // resolved.success === false above. | ||
| await this._recordVisualSessionTickIfPresent(tabId, agentId, payload); |
There was a problem hiding this comment.
Tick lifecycle in NO_OWNED_TAB navigate bootstrap path
The new lifecycle tick is wired only after successful tab resolution, but navigate has a NO_OWNED_TAB fallback that returns early through dispatchWithoutResolvedTab(...); that successful bootstrap action therefore skips implicit visual-session start/refresh entirely. Under the new contract, this path should still emit a lifecycle tick once the target tab is known.
Useful? React with 👍 / 👎.
| nextEntry = { | ||
| tabId: numericTabId, | ||
| agentId: trimmedAgentId, | ||
| client: existingEntry.client || canonicalClient, |
There was a problem hiding this comment.
Overwrite stale stored client with canonical client label
On updates, the lifecycle entry keeps existingEntry.client whenever it is truthy instead of replacing it with the newly validated canonical client label. A malformed/tampered stored value therefore persists indefinitely and can bypass the defense-in-depth intent of re-validating fields.client on every tick.
Useful? React with 👍 / 👎.
| client: z.string().describe('Trusted MCP client label, for example Codex, ChatGPT, Claude, or Gemini. Must be on the approved allowlist.'), | ||
| task: z.string().describe('Short task title shown in the visible automation surface.'), |
There was a problem hiding this comment.
Allow TOOL_REMOVED stubs to run without legacy args
The removed start_visual_session/end_visual_session handlers are intended to always return TOOL_REMOVED, but the registration schema still requires legacy fields (client, task, etc.). SDK-side input validation can reject missing-arg calls before the stub executes, so callers get a generic validation failure instead of the migration error contract.
Useful? React with 👍 / 👎.
- package.json: version, package zip name, badge URL - extension/manifest.json: name + version - showcase/angular/package.json + lockfile v0.9.63 was a showcase-only tag (Chrome extension stayed at 0.9.62). v0.9.64 ships the extension fix for the Implicit Visual Session Contract NO_OWNED_TAB bootstrap path (Codex P2 from PR #33) plus a test-side build-staleness guard.
…bootstrap - Mirror open_tab/switch_tab post-dispatch lifecycle (lines 705-728) onto the navigate NO_OWNED_TAB branch so the implicit visual session is started/refreshed when navigate bootstraps the agent's first owned tab. - Award _recordVisualSessionTickIfPresent + _clearVisualSessionIfFinal only when dispatched.success === true and tabId is finite (no overlay for failed dispatches). - Addresses Codex PR fullselfbrowsing#33 P2 (v0.9.62 Implicit Visual Session Contract).
Summary
Ships the v0.9.62 milestone — Implicit Visual Session Contract. The visual session is no longer a separately-managed lifecycle (
start_visual_session/end_visual_session); it is implicit on every action tool call via a requiredvisual_reason+clientfield bundle, with a 60-second sliding-window auto-dismiss and an explicitis_final: trueimmediate-clear hatch.fsb-mcp-server@0.9.0(BREAKING)0.9.62What changed
VISUAL_FIELDS_REQUIRED) enforced at the dispatcher chokepoint for all 36 action tools; 15 read-only tools schema-locked and exemptstart_visual_session/end_visual_sessionconverted to synchronousTOOL_REMOVEDstubs that return the migration recipeVISUAL_FIELDS_REQUIRED,BADGE_NOT_ALLOWED,TOOL_REMOVEDchrome.storage.sessionticks +chrome.alarms60s death timer (MV3 service-worker-survivable)mcp/CHANGELOG.md#v0.9.0andmcp/README.mdSKILL.md,USAGE.md,references/tool-decision-tree.md,references/visual-session-lifecycle.md)Pre-merge housekeeping (this PR)
Three small follow-ups landed on top of the milestone:
chore: sync mcp/build/install.js to match source— stale committed build output regenerated to matchmcp/src/install.tstest(mcp-smoke): update for v0.9.62 implicit visual session contract—tests/mcp-tool-smoke.test.jswas missed during the milestone sweep; addsvisual_reason/clientto action calls, assertsTOOL_REMOVEDno-dispatch for the two stubs, and updates queue coverage. Caught by re-running CI on this branch.chore(showcase): refresh sitemap + llms-full.txt lastmod dates— auto-regenerated byshowcase/angular/scripts/build-crawler-files.mjsduringnpm run ciTest plan
npm run ci(validate-extension + ~70 unit tests + mcp-smoke + showcase build + showcase crawler smoke) — all green locallynpx -y fsb-mcp-serverto localmcp/build/index.jsand exercise Wikipedia + Google + Perplexity + Bing flows; every action call carried the implicit field bundle, server accepted all, nostart_visual_sessioncalls anywherev0.9.62andnpm publish fsb-mcp-server@0.9.0🤖 Generated with Claude Code