Skip to content

feat(session): add local shared-window sessions - #306

Open
lymerin wants to merge 4 commits into
Tencent:mainfrom
lymerin:main
Open

lymerin wants to merge 4 commits into
Tencent:mainfrom
lymerin:main

Conversation

@lymerin

@lymerin lymerin commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #243.

Problem

bsk session start always opens a dedicated Agent Window. In single-window and Arc workflows, this creates an extra window when the user wants the session's tabs in their current window. As reported in #243, Arc displays the extra window as an orphan belonging to no Space.

Changes

  • Add opt-in bsk session start --in-window: the session tab is created in the last-focused normal, non-incognito user window instead of a new window. --no-focus creates it as an inactive tab.
  • Treat the window as a container and enforce control per tab ID. Other pages in that window, including other sessions' tabs, do not become session-owned.
  • Keep same-window borrowed tabs in place; returning them releases control without moving the page.
  • Stop shared sessions by returning borrowed tabs and closing only session-created tabs.
  • Preserve the host window during teardown. Chrome closes a normal window when its last tab is removed or moved out, so session stop and tab return share one check that creates an unowned about:blank page when the operation would empty the host. Returning a tab to the same host does not create a placeholder. If the operation is cancelled or fails, remove the unused placeholder unless it is the host's last remaining tab or the user has navigated it elsewhere (including a pending navigation). Rollback failures are reported.
  • End a shared session when its last controlled tab is closed or moved away. The extension emits session.tabs_closed and the daemon drops the session record. Normal teardown does not emit an unexpected session.window_closed event.
  • Keep tab creation protected through CDP setup: recheck ownership and window location before failure cleanup decides whether the new tab may be closed.
  • Add protocol 1.4 gating on both sides: the CLI refuses --in-window against an older daemon, the daemon refuses it against an older extension, and session list reports the container mode.

Scope and safety

  • Opt-in and local-only. Ordinary sessions and record start keep their dedicated-window behavior.
  • --in-window conflicts with --width/--height, is rejected for remote connections, and window_resize is refused for shared sessions.
  • Tab grouping is not included; isolation is enforced by tab ownership.
  • When teardown would otherwise empty the host window, it deliberately leaves one unowned about:blank tab to keep the window open.

Validation

  • Extension test suite: 2,460 passed, 122 skipped. Type checking, production build, formatting and diff checks passed.
  • Regression tests cover shared-session startup and host eligibility, two sessions sharing one window, dialog auto-accept refusal on non-owned pages, same-window borrow and return, deferred empty-session cleanup during tab_close, and tabs moved during CDP setup.
  • Teardown tests cover host preservation during stop and return, fallback to the same host, cancellation, move/remove failures, rollback failures, and preserving placeholders that the user has navigated or is navigating elsewhere.
  • Exercised the actual extension session/tab handlers against an isolated headless Chrome 154 on Windows. Normal tab return and session stop preserved the host; cancellation, fallback to the same host, and injected move/remove failures left no unused placeholder and emitted no window-closed event.

Arc and macOS live-browser testing was not performed.

@IRONICBo IRONICBo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One shared-window teardown edge case remains.

// Moving a shared session page out is a user reclaim, including
// when onAttached has not yet reached the service worker.
if (tab.windowId === ctx.container.hostWindowId)
await this.sharedWindow.remove(tabId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--in-window can still close the host window here. If the user has closed or moved every ordinary tab, the last live tab may be in agentCreatedTabs; Chrome closes a normal window when its final tab is removed (the window handler already notes this case). Shared teardown is not wrapped in withExpectedWindowClose, so it may also emit session.window_closed while normal stop completes. Please preserve an unowned survivor before deleting the final owned tab and mark the browser event as expected. A regression with only this session tab left should assert that the host survives and no close event is sent. I maintain socai, where persistent Chrome teardown keeps container lifetime separate from tab ownership.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. Fixed in 7c5a124.

Shared-session teardown now creates an unowned about:blank tab before removing or moving out the host’s last tab. This covers both session stop and borrowed-tab return. Shared teardown uses ctx.stopping to suppress unexpected session.window_closed notifications.

If the operation is cancelled or fails, the unused placeholder is removed, unless it is the window’s last remaining tab or the user has already started navigating it elsewhere.

Regression tests assert that the host survives when only session tabs remain and that no window-closed notification is sent. I also verified the stop and return paths against an isolated headless Chrome on Windows.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants