Skip to content

fix(server): t3_preview_list and t3_preview_close see the thread's Environment browser tabs - #201

Merged
immakermatty merged 1 commit into
mainfrom
agent/environment-browser-thread-tabs
Nov 14, 2026
Merged

immakermatty merged 1 commit into
mainfrom
agent/environment-browser-thread-tabs

Conversation

@immakermatty

@immakermatty immakermatty commented Nov 14, 2026 •

Copy link
Copy Markdown

Status: in progress (draft)

Problem

With the Environment browser, a thread's tabs live in its strip: ThreadTabs in apps/server/src/lazurio/threadTabs.ts, persisted in the holder file $XDG_RUNTIME_DIR/lazurio-browser/holders/t3-<thread>.json. The handlers of t3_preview_list and t3_preview_close (apps/server/src/mcp/toolkits/previewControls/handlers.ts) read only upstream's PreviewManager, which the overlay never writes:

  • t3_preview_list answered sessions: [] while the thread held tabs.
  • t3_preview_close sent close to the broker only for a server session PreviewManager knew, and otherwise fell through to manager.close, which does nothing for an unknown tab and answered {}. The overlay's close (threadTabs.release) never ran, and the tab stayed in the strip.

Fix

  • The Environment browser host (environmentBrowser.ts) hands out the thread's strip as EnvironmentBrowserTabs (lazurio/environmentBrowserTabs.ts). This is a Context.Reference whose default lists nothing, so without the Environment browser, and in every upstream test, the handlers behave exactly as upstream. overlay.ts builds the host and the strips once and server.ts provides the same layer to /mcp (LazurioOverlay.withEnvironmentBrowserTabs(McpHttpServer.layer)). The memoized layer shares one host and one set of strips with the overlay's routes.
  • Each tab's address and title come from one Target.getTargets call (connection.pages()), not from evaluating in every page, so a busy page or one with an open dialog does not stall the list.
  • t3_preview_list: where the Environment declares its browser, the strip's tabs come first as server sessions (tabId, navStatus with url and title, runtime: "server", and view = https://browser.…/t/<tabId>, as preview_open answers), followed by any PreviewManager sessions. The tool's success schema gains the optional view field.
  • t3_preview_close: a tab in the thread's strip goes to the broker as close, which the host turns into threadTabs.release. On a strip, a tab that the thread holds neither in the strip nor in PreviewManager is refused with invalid_request instead of {}. Without a strip, upstream's path is unchanged.
  • preview_status carries the same strip as tabs (tabId, url, visible).
  • Fork distribution: the allowlist in lazurio-fork-ci.yml gains the two previewControls files, the release contract test checks the new seams in overlay.ts, server.ts and the host, and docs/operations/lazurio-overlay.md gets one sentence. Tool descriptions stay as they are, because none of them is untrue after the fix.

Verification

  • New case in apps/server/src/lazurio/environmentBrowser.test.ts runs the real broker, the real host over the fake browser, the real ThreadTabs on a temporary XDG_RUNTIME_DIR, and the real PreviewControls handlers. It runs preview_open ×3, then checks that the list has 3 tabs with URL, title and view and that the holder file holds 3. It closes 1 and checks that the list and the holder file hold 2, that preview_status tabs shows the same 2 and that the window stays open. Closing the released tab again returns OrchestratorMcpFailure invalid_request, and the holder file stays at 2. With upstream's handlers.ts the case fails exactly as in the issue: expected { sessions: [] … }.
  • vp test run src/lazurio src/mcp/toolkits/previewControls src/mcp/PreviewAutomationBroker.test.ts src/mcp/McpHttpServer.test.ts: 41 files, 552 tests passed (15 skipped, as on main).
  • vp run typecheck (apps/server): passed. node --test scripts/lazurio-release-contract.test.mjs: 52/52.
  • vp run check:changed: format, lint, knip, graphql and typecheck passed. src/provider/AgentContextBudget.test.ts fails with the same numbers with or without this change (codexAdditionalContextBytes 8838 vs 7327, mcpToolListJsonBytes 48399 vs 48217 in agentContextBudget.json), because the budget measures tool descriptions and input schemas, which this PR does not change. The mismatch is already on origin/main and is not addressed here.
  • No runtime transcript against a live Environment browser yet: this PR has not been deployed anywhere.

Open

  • Runbook docs/operations/lazurio-fork-release.md, "Cílový stack": this commit belongs to row 10 (and touches overlay.ts* of row 11). By §9 the stack gets commits that landed after the swap in the post-release PR, so it is not edited here.

Fixes #200

Agent: environment a480495c-25e0-4e51-828b-1dbf1b77d62f · T3 thread thread:delegated-task:command%3Amcp%3Abd2c9a58-fde8-46a0-b4c7-a6abe8e94735%3Adelegate-task%3Aea52843b-643d-4e8a-a3d8-88b6ea81a654

🤖 Generated with Claude Code

Note

Show the thread's Environment browser tabs in t3_preview_list and t3_preview_close

  • Adds an EnvironmentBrowserTabs service that lists a thread's browser strip, built from the CDP target listing in environmentBrowserConnection.ts and the make service in environmentBrowser.ts
  • t3_preview_list now prepends strip tabs to PreviewManager sessions and paginates the combined list, with a view URL on each session (see handlers.ts)
  • t3_preview_close can close a tab held by the thread's strip via the broker; when a strip is available and the ID matches neither the strip nor a server session, it returns an invalid_request failure
  • Wires the new layers into the overlay and MCP server via LazurioOverlay.withEnvironmentBrowserTabs in overlay.ts and server.ts, and covers the flow with an integration test and updated docs
  • Risk: EnvironmentBrowserConnection implementations must now provide a pages operation; strip-list failures surface as orchestration_error MCP failures

Macroscope summarized b561a1a.

…vironment browser tabs

With the Environment browser, a thread's tabs live in its strip (the holder file of
threadTabs.ts), which upstream's PreviewManager never sees. t3_preview_list answered
an empty list, and t3_preview_close fell through to PreviewManager.close, which does
nothing for an unknown tab and answered {}, so the tab stayed in the strip.

The host now hands /mcp the thread's strip, through a reference whose default keeps
upstream's behavior and which overlay.ts provides from the same host and strips as
its routes. t3_preview_list lists the strip's tabs as server sessions with their
address, title and view (one Target.getTargets call, so a busy page still answers),
and preview_status carries the same tabs. t3_preview_close sends close to the host
for a tab the thread holds, which lets go of it through threadTabs.release, and
refuses a tab the thread does not hold instead of answering {}.

Fixes #200

Claude Opus 5.5 in Claude Code (T3 Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@immakermatty
immakermatty force-pushed the agent/environment-browser-thread-tabs branch from ee40e14 to b64c1aa Compare November 14, 2026 21:33
@immakermatty
immakermatty merged commit 439c780 into main Nov 14, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Environment browser: t3_preview_list nevidí záložky vlákna a t3_preview_close je nezavře

1 participant