Skip to content

Add multi-agent browser tab isolation (Phase 1 MVP)#1

Open
lark1115 wants to merge 8 commits intomainfrom
feat-browser-agent-session
Open

Add multi-agent browser tab isolation (Phase 1 MVP)#1
lark1115 wants to merge 8 commits intomainfrom
feat-browser-agent-session

Conversation

@lark1115
Copy link
Copy Markdown
Owner

@lark1115 lark1115 commented Mar 24, 2026

Summary

Multi-agent browser tab isolation (Phase 1 MVP) — enables multiple AI agents to share imported Chrome profiles while maintaining complete cookie isolation via clone-on-first-use WKWebsiteDataStore cloning.

What's included

  • BrowserAgentSession + Store: Session model with on-disk manifest for crash-recovery GC
  • Cookie clone-on-first-use: Clones cookies from source profile on first session open, with in-flight deduplication
  • Socket commands: browser.agent_session.open / .tab / .dispose / .list
  • CLI wrappers: cmux browser agent-session open|tab|dispose|list
  • Caller identity verification: Surface UUID existence check before trusting params
  • Agent panel lifecycle: Panels excluded from session restore, GC on app launch, cleanup on socket disconnect
  • Security: Cross-agent enumeration blocked, profile switch blocked on agent panels, dispose/clone race protection
  • Max 10 concurrent sessions with proper counting of in-flight clones
  • Tests: WKProcessPool cookie isolation test + BrowserAgentSessionStore unit tests

Architecture

Agent A (surface:1)                    Agent B (surface:6)
    │                                      │
    ▼                                      ▼
browser.agent_session.open             browser.agent_session.open
    │  (same profile_id)                   │  (same profile_id)
    ▼                                      ▼
┌─────────────────────┐            ┌─────────────────────┐
│ Session CC7D76E2     │            │ Session EB58F710     │
│ DataStore: clone-A   │            │ DataStore: clone-B   │
│ Cookies: agent1_token│            │ Cookies: agent2_token│
└─────────────────────┘            └─────────────────────┘
         ▲ clone-on-first-use              ▲ clone-on-first-use
         │                                 │
         └─────────┬───────────────────────┘
                   │
          ┌────────▼────────┐
          │ Source Profile   │
          │ (Chrome import)  │
          └─────────────────┘

E2E Verification

Cookie isolation verified with httpbin.org:

  • Agent 1 sets agent1_token=secret123 → only visible in Agent 1's session
  • Agent 2 sets agent2_token=xyz789 → only visible in Agent 2's session
  • Cross-agent cookie leakage: none

Agent 1 cookies (only agent1_token)

agent1-cookies

Agent 2 cookies (only agent2_token)

agent2-cookies

Overview — both agents side by side

isolation-overview

Review History

2 rounds of 3-agent (Codex + Claude + Cursor Agent) review board:

  • Round 1: 5 must-fix found and resolved (CLI disconnect, reattach isolation, dispose multi-window, disconnect orphan panels, test manifest path)
  • Round 2: 4 suggestions found and resolved (session rollback, concurrent limit race, dispose/clone race, formatter allocation)
  • Final verdict: All 3 agents APPROVE

Test plan

  • Unit tests: WKProcessPool cookie isolation, BrowserAgentSessionStore CRUD/capacity/disconnect
  • E2E: cmux browser agent-session open → set cookies → verify isolation → dispose
  • Build verification: DEBUG + unit test scheme
  • CI: unit tests via gh workflow run test-e2e.yml

🤖 Generated with Claude Code

lark1115 and others added 8 commits March 25, 2026 01:58
Introduce BrowserAgentSession system that enables multiple AI agents to
share imported Chrome profiles while maintaining cookie isolation via
clone-on-first-use WKWebsiteDataStore cloning.

New files:
- BrowserAgentSession.swift: session struct (agent UUID, source profile, data store ID)
- BrowserAgentSessionStore.swift: session lifecycle management with Task-based
  clone serialization, on-disk manifest for crash-recovery GC, and
  WKWebsiteDataStore.remove(forIdentifier:) cleanup

Modified:
- BrowserPanel: agentSessionId property, agent data store in init,
  block switchToProfile() on agent-owned panels
- SessionPersistence: agentSessionId field (agent panels discarded on restore)
- Workspace: newBrowserSurface gains agentSessionId/agentDataStoreId params
- TerminalController: 4 new socket commands:
  - browser.agent_session.open: create session with cookie clone
  - browser.agent_session.tab: add tab to existing session
  - browser.agent_session.dispose: close tabs + remove data store
  - browser.agent_session.list: enumerate sessions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address all must-fix issues from 3-agent code review:

- Fix caller identity: verify surface UUID exists in live workspace
  before trusting params['caller']['surface_id']
- Fix session snapshot: include agentSessionId in persistence
- Fix session restore: skip agent-owned panels on app restart
- Fix degenerate session: getOrCreate returns nil at capacity
  instead of untracked orphan session
- Fix session limit: remove premature guard that blocked reuse
  of existing sessions at capacity
- Fix list command: wrap in v2MainSync, restrict to caller's
  own sessions by default
- Wire GC: call garbageCollect() on app launch in AppDelegate
- Wire disconnect: track agent surfaces per connection, call
  handleAgentDisconnect on socket close
- Implement tabCount: enumerate workspace panels for real count

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Restrict browser.agent_session.list to caller's own sessions only;
  explicit agent_surface_id filter must match caller UUID
- Move disconnect handler tracking after processCommand succeeds and
  verify method == "browser.agent_session.open" via parsed JSON
  (prevents string-injection DoS)
- Update tabCount documentation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmux browser agent-session open --profile <uuid> [--url <url>]
cmux browser agent-session tab --session <uuid> [--url <url>]
cmux browser agent-session dispose --session <uuid>
cmux browser agent-session list [--json]

Caller context (CMUX_SURFACE_ID, CMUX_WORKSPACE_ID) is automatically
included from environment variables for agent identity resolution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- WKProcessPoolCookieIsolationTests: verify cookies set in one
  WKWebsiteDataStore are NOT visible in another sharing the same
  WKProcessPool (foundation of agent session isolation)
- BrowserAgentSessionStoreTests: CRUD, dedup, capacity limit,
  disconnect cleanup
- BrowserPanelAgentOwnershipTests: switchToProfile blocked on
  agent-owned panels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- rb-001: Only track agent surfaces for disconnect cleanup after
  system.identify (long-lived connections), not one-shot CLI commands
- rb-002: Preserve agent cookie isolation in reattachToWorkspace by
  keeping existing websiteDataStore when agentSessionId is set
- rb-003: Dispose iterates ALL windows to close agent panels, not
  just the resolved TabManager's window
- rb-004: handleAgentDisconnect closes orphaned BrowserPanel tabs
  across all windows before disposing sessions
- rb-006: Add injectable manifestURL to BrowserAgentSessionStore;
  tests use temp directory instead of production path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- rr-001: Dispose session on panel creation failure paths to prevent
  orphaned sessions consuming slots
- rr-002: Count cloneInFlight toward maxConcurrentSessions to prevent
  concurrent opens from exceeding the limit
- rr-003: Cancel matching cloneInFlight tasks on dispose; check
  Task.isCancelled before appending to prevent zombie sessions
- rr-004: Hoist ISO8601DateFormatter outside .map closure

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The browser subcommand parser was treating "agent-session" as a surface
handle instead of a verb, causing "Unsupported browser subcommand" errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.

1 participant