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 /test-suite/unit/shared-window.test.mjs | |
| 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 'test-suite/unit/shared-window.test.mjs')
| -rw-r--r-- | test-suite/unit/shared-window.test.mjs | 78 |
1 files changed, 78 insertions, 0 deletions
diff --git a/test-suite/unit/shared-window.test.mjs b/test-suite/unit/shared-window.test.mjs new file mode 100644 index 0000000..7afeb77 --- /dev/null +++ b/test-suite/unit/shared-window.test.mjs @@ -0,0 +1,78 @@ +// Agent sessions share one window: a new session's tab goes into the window that already holds a +// "Pi Session:" group, and a window is created only when there is none. +import assert from "node:assert/strict"; +import fs from "node:fs"; +import vm from "node:vm"; +import { test } from "node:test"; + +const workerSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/browser-extension/service_worker.js", import.meta.url), "utf8"); + +function worker({ groups = [], windows = { 5: { id: 5, type: "normal" } } } = {}) { + const calls = []; + const listener = { addListener() {}, removeListener() {} }; + let nextTab = 100; + const w = { + console, setTimeout, clearTimeout, setInterval: () => 0, navigator: { userAgent: "unit-test" }, fetch: async () => { throw new Error("offline"); }, + chrome: { + runtime: { id: "test", getManifest: () => ({ version: "0.0.0" }), onInstalled: listener, onStartup: listener }, + alarms: { create() {}, onAlarm: listener }, action: { onClicked: listener }, webNavigation: { onCommitted: listener }, + debugger: { onDetach: listener, onEvent: listener, getTargets: (cb) => cb([]) }, + storage: { session: { get: async () => ({}), set: async (v) => { calls.push({ method: 'storage.set', json: JSON.stringify(v) }); } } }, + tabGroups: { query: async () => groups }, + windows: { + get: async (id) => windows[id] ?? Promise.reject(new Error("No window")), + create: async (props) => { calls.push({ method: "windows.create", props }); return { id: 9, tabs: [{ id: 900, windowId: 9 }] }; }, + }, + tabs: { + create: async (props) => { calls.push({ method: "tabs.create", props }); return { id: nextTab++, windowId: props.windowId }; }, + query: async () => [], get: async () => null, onUpdated: listener, + }, + }, + }; + w.self = w; + vm.runInNewContext(workerSource, w); + return { w, calls }; +} + +const piGroup = (windowId, name = "Pi Session: claude-code 1 other") => ({ id: 1, windowId, title: name }); + +test("a new session's tab goes into the window that already holds another Pi session", async () => { + const { w, calls } = worker({ groups: [piGroup(5)] }); + await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine"); + assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["tabs.create"]); + assert.equal(calls[0].props.windowId, 5); +}); + +test("with no Pi window yet, the first session creates one", async () => { + const { w, calls } = worker({ groups: [] }); + await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine"); + assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["windows.create"]); +}); + +test("groups that are not Pi sessions do not attract agent tabs", async () => { + const { w, calls } = worker({ groups: [{ id: 2, windowId: 5, title: "Work" }] }); + await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine"); + assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["windows.create"]); +}); + +test("a Pi group whose window is gone, or is not a normal window, is skipped", async () => { + const { w, calls } = worker({ groups: [piGroup(7), piGroup(8)], windows: { 8: { id: 8, type: "popup" } } }); + await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine"); + assert.deepEqual(calls.filter((c) => c.method !== "storage.set").map((c) => c.method), ["windows.create"]); +}); + +test("the shared window is never recorded as ours, so cleanup cannot close it", async () => { + const { w, calls } = worker({ groups: [piGroup(5)] }); + await w.createAutomationTarget("claude-code 2 mine", "Pi Session: claude-code 2 mine"); + const stored = calls.filter((c) => c.method === "storage.set").map((c) => c.json).join(""); + assert.doesNotMatch(stored, /"windowId":5/); +}); + +test("chrome_tab new for a session with no group uses the shared window, not the focused one", async () => { + const { w, calls } = worker({ groups: [piGroup(5)] }); + w.groupTab = async (tab) => tab; + w.bringToFront = async () => {}; + await w.dispatch("tab.new", { url: "about:blank", groupTitle: "Pi Session: claude-code 2 mine", sessionKey: "claude-code 2 mine" }); + const created = calls.find((c) => c.method === "tabs.create"); + assert.equal(created.props.windowId, 5); +}); |
