diff options
| author | Igor Soarez <igor@soarez.org> | 2026-11-08 12:37:44 +0000 |
|---|---|---|
| committer | Igor Soarez <igor@soarez.org> | 2026-11-08 12:37:44 +0000 |
| commit | 66fbd3015ff68aa94f1faff1ad84740052b86a0d (patch) | |
| tree | df9a7be3c1d0f4fb91fcfa501eb33778a9a6ac6e /extensions | |
| parent | e538c3e53e203fabb8a9fed94b6810c345ee6f80 (diff) | |
tabs: agent sessions share one window instead of opening one each
A session's first tab used chrome.windows.create, so every new session name (and
every Claude Code process) got its own window. It now goes into the window that
already holds any 'Pi Session:' group; a window is created only when none exists.
chrome_tab new for a session with no group also uses that window, not the user's
focused one. The primer and the chrome_tab description say so.
Diffstat (limited to 'extensions')
| -rw-r--r-- | extensions/chrome-profile-bridge/browser-extension/service_worker.js | 34 | ||||
| -rw-r--r-- | extensions/chrome-profile-bridge/index.ts | 5 |
2 files changed, 34 insertions, 5 deletions
diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js index c62aff9..7fbbbba 100644 --- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js +++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js @@ -226,11 +226,26 @@ function isPiChromeOwnedTarget(tabId, sessionKey) { return false; } +// The window agents share. Every session has its own tab group and a group cannot span windows, +// but sessions do not need their own windows: new tabs go to the window that already holds any +// "Pi Session:" group, so agents stay together instead of opening one window each. Returns +// undefined when no such window exists (the first session then creates one). +async function sharedPiWindowId() { + if (!chrome.tabGroups || typeof chrome.windows?.get !== "function") return undefined; + const groups = await chrome.tabGroups.query({}).catch(() => []); + for (const group of groups) { + if (!/^pi session:/i.test((group.title || "").trim()) || typeof group.windowId !== "number") continue; + const win = await chrome.windows.get(group.windowId).catch(() => null); + if (win && (win.type === undefined || win.type === "normal")) return win.id ?? group.windowId; + } + return undefined; +} + // Create a fresh automation target for `sessionKey`. If this session already has a tab group, // create the tab inside that group's window so one Pi session keeps one Chrome tab group (Chrome -// groups cannot span windows). If no group exists yet, prefer an isolated window; fall back to a -// tab. When the tab is created in a pre-existing group window, leave windowId unset so cleanup only -// closes our tab, never that whole window. +// groups cannot span windows). If it has none, use the shared Pi window; only when there is no +// Pi window yet, create one; fall back to a tab. When the tab is created in a pre-existing window, +// leave windowId unset so cleanup only closes our tab, never that whole window. async function createAutomationTarget(sessionKey, groupTitle) { const existingGroup = groupTitle ? await findGroupRecordByTitle(groupTitle) : null; if (existingGroup && typeof existingGroup.windowId === "number") { @@ -239,6 +254,14 @@ async function createAutomationTarget(sessionKey, groupTitle) { await persistAutomationTargets(); return tab; } + const sharedWindowId = await sharedPiWindowId(); + if (sharedWindowId !== undefined) { + // Someone else's window: leave windowId unset so cleanup only ever closes our tab. + const tab = await chrome.tabs.create({ url: "about:blank", active: false, windowId: sharedWindowId }); + automationTargets.set(sessionKey, { windowId: undefined, tabId: typeof tab.id === "number" ? tab.id : undefined }); + await persistAutomationTargets(); + return tab; + } if (chrome.windows && typeof chrome.windows.create === "function") { try { const win = await chrome.windows.create({ url: "about:blank", focused: false }); @@ -1825,6 +1848,11 @@ async function dispatch(action, params) { const existingGroup = await findGroupRecordByTitle(groupTitle); const createParams = { url: params.url || "about:blank", active: foregroundRequested(params) }; if (existingGroup && typeof existingGroup.windowId === "number") createParams.windowId = existingGroup.windowId; + else { + // No group of this session yet: use the shared Pi window, never the user's focused one. + const sharedWindowId = await sharedPiWindowId(); + if (sharedWindowId !== undefined) createParams.windowId = sharedWindowId; + } // Register the creation as in flight until the tab is tracked, so early capture (which only // runs in tracked tabs) waits for it instead of skipping the new tab's first page load. const creation = (async () => { diff --git a/extensions/chrome-profile-bridge/index.ts b/extensions/chrome-profile-bridge/index.ts index 90f080e..32ae79f 100644 --- a/extensions/chrome-profile-bridge/index.ts +++ b/extensions/chrome-profile-bridge/index.ts @@ -1276,7 +1276,8 @@ Usage rules: 6. /chrome background on (default) is a hard policy: per-call \`background=false\` cannot override it, new tabs stay inactive, and \`chrome_tab activate\` is blocked. Ask the user to run /chrome background off when they want foreground/watch mode. With background off, per-call \`background=true\` still avoids explicit focus/tab activation. Screenshots use CDP without activating background tabs; debugger failures never fall back to tab activation. Page scripts, trusted input, native prompts, and Chrome/OS behavior can still affect focus. 7. If you hit a native file-picker or privileged browser prompt gate, tell the user; generic clicks/typing/CSP gates are handled by Chrome input. 8. Run /chrome doctor when in doubt about connectivity or capabilities. -9. A page can open dialogs you cannot see: alert, confirm, prompt, and the \"Leave site? Changes you made may not be saved\" prompt of a page with unsaved changes. They are dismissed for you (stay, cancel). \`chrome_navigate\` reports one by failing, and \`leaveIfAsked: true\` leaves anyway; use that only when you know nothing unsaved matters, and ask the user when unsure. If a click or key press that should have navigated did nothing, a Leave-site prompt may have stopped it: check the page, then ask the user before discarding anything.`; +9. A page can open dialogs you cannot see: alert, confirm, prompt, and the \"Leave site? Changes you made may not be saved\" prompt of a page with unsaved changes. They are dismissed for you (stay, cancel). \`chrome_navigate\` reports one by failing, and \`leaveIfAsked: true\` leaves anyway; use that only when you know nothing unsaved matters, and ask the user when unsure. If a click or key press that should have navigated did nothing, a Leave-site prompt may have stopped it: check the page, then ask the user before discarding anything. +10. All agent sessions share one browser window; your tabs sit in your own colored tab group inside it. Reuse a tab you already have (navigate it) rather than opening new ones, and open a new tab only when you need two pages at once. Do not open new windows unless the user asks.`; // Shared handlers, dispatched by the unified /chrome command below. const doctorHandler = async (ctx: ExtensionContext) => { @@ -1651,7 +1652,7 @@ Usage rules: pi.registerTool({ name: "chrome_tab", label: "Chrome Tab", - description: "List, create, activate, close, group, ungroup, or inspect tabs in the user's existing Chrome profile via the companion extension. New/grouped tabs always use this session's Pi tab group. Background mode keeps new tabs inactive and blocks activate; ask the user to run /chrome background off for foreground/watch mode. activate/close/group/ungroup require a target (targetId/urlIncludes/titleIncludes); with no target they act on this session's pi-chrome automation tab if one exists, and otherwise error rather than touching the user's active tab.", + description: "List, create, activate, close, group, ungroup, or inspect tabs in the user's existing Chrome profile via the companion extension. New/grouped tabs always use this session's Pi tab group, inside the one window all agent sessions share; prefer navigating a tab you already have to opening another. Background mode keeps new tabs inactive and blocks activate; ask the user to run /chrome background off for foreground/watch mode. activate/close/group/ungroup require a target (targetId/urlIncludes/titleIncludes); with no target they act on this session's pi-chrome automation tab if one exists, and otherwise error rather than touching the user's active tab.", promptSnippet: "List/open/activate/close/group existing Chrome tabs through the companion extension.", parameters: Type.Object({ action: StringEnum(tabActionValues), |
