fix(extension): never return a borrowed tab into another session's Agent Window

When a borrowed tab's original window is gone, tab_return's fallback
picker called getLastFocused({windowTypes:["normal"]}) and only
excluded the *current* session's Agent Window. Agent Windows are
created with type:"normal", so the picker could move a returned user
tab into *another* session's Agent Window. That session could then
write to the tab (it passes enforceAgentWindow), and the tab would be
destroyed when that session stops and closes its window — a
cross-session sandbox break plus silent user-data loss.

Thread an isAgentWindowId predicate (derived from SessionManager in
the production handlers, mirroring the existing filter in
borrow-confirmation's listConfirmationCandidates) through
returnBorrowedTab -> chooseFallbackWindow so every live Agent Window
is skipped as a fallback target.

Adds two regression tests (both fail on the unpatched code).

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Test User
2026-07-02 20:23:02 +08:00
co-authored by Claude
parent 9c229b2489
commit 8e8dd3840b
3 changed files with 97 additions and 5 deletions
+7 -1
View File
@@ -113,7 +113,13 @@ export async function handleSessionStop(
const borrowedIds = Array.from(ctx.borrowedTabs.keys());
for (const tabId of borrowedIds) {
try {
const outcome = await returnBorrowedTab(ctx, tabId, deps.tabManagement ?? {});
const tabManagement = {
...(deps.tabManagement ?? {}),
isAgentWindowId:
deps.tabManagement?.isAgentWindowId ??
((windowId: number) => manager.findByWindowId(windowId) !== null),
};
const outcome = await returnBorrowedTab(ctx, tabId, tabManagement);
if (typeof outcome === "object" && "code" in outcome) {
console.warn(`[bsk session_stop] auto-return failed for tab ${tabId}`, outcome);
returnFailures?.push({