From 2c1a4b3cf42c149f52d491131680e062e1935f5f Mon Sep 17 00:00:00 2001 From: Tianren Dong Date: Mon, 22 Jun 2026 21:04:05 -0400 Subject: 0.15.40: keep automation targets in session group --- CHANGELOG.md | 4 ++++ .../browser-extension/manifest.json | 2 +- .../browser-extension/service_worker.js | 22 ++++++++++++++++------ package.json | 2 +- test-suite/unit/automation-target.test.mjs | 8 ++++++++ 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eae1975..7228c67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable user-facing changes to `pi-chrome`. +## 0.15.40 — 2026-06-22 + +- **Automation targets reuse the session tab group.** When `chrome_navigate` / implicit page actions create a new pi-chrome automation tab, it is now created in this session's existing tab-group window when possible and joins that same group, avoiding duplicate same-title `Pi Session: ...` groups. + ## 0.15.39 — 2026-06-07 - **Dedicated automation tab/window (no more hijacking your active tab).** When a chrome_* action runs without an explicit target, pi-chrome now opens and reuses a dedicated automation window it owns (falling back to a dedicated tab if a separate window can't be created) instead of navigating whatever tab you currently have open. Your existing tabs/windows are left untouched. Pass `targetId`/`urlIncludes`/`titleIncludes` to act on a specific existing tab. diff --git a/extensions/chrome-profile-bridge/browser-extension/manifest.json b/extensions/chrome-profile-bridge/browser-extension/manifest.json index 7d16294..d233208 100644 --- a/extensions/chrome-profile-bridge/browser-extension/manifest.json +++ b/extensions/chrome-profile-bridge/browser-extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Pi Chrome Connector", - "version": "0.15.39", + "version": "0.15.40", "description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.", "permissions": [ "tabs", diff --git a/extensions/chrome-profile-bridge/browser-extension/service_worker.js b/extensions/chrome-profile-bridge/browser-extension/service_worker.js index 2b5f96f..e04eafd 100644 --- a/extensions/chrome-profile-bridge/browser-extension/service_worker.js +++ b/extensions/chrome-profile-bridge/browser-extension/service_worker.js @@ -86,9 +86,19 @@ function isPiChromeOwnedTarget(tabId, sessionKey) { return false; } -// Create a fresh dedicated automation target for `sessionKey`. Prefer an isolated window; fall -// back to a tab. Structured so a window can be the default while tab-fallback stays a one-liner. -async function createAutomationTarget(sessionKey) { +// 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. +async function createAutomationTarget(sessionKey, groupTitle) { + const existingGroup = groupTitle ? await findGroupRecordByTitle(groupTitle) : null; + if (existingGroup && typeof existingGroup.windowId === "number") { + const tab = await chrome.tabs.create({ url: "about:blank", active: false, windowId: existingGroup.windowId }); + 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 }); @@ -125,8 +135,8 @@ async function resolveOwnedAutomationTarget(sessionKey) { // Return the session's dedicated automation target, creating it on first use (or after the user // closed it). Used by page/navigation actions that need a live surface to drive. -async function getOrCreateAutomationTarget(sessionKey) { - return (await resolveOwnedAutomationTarget(sessionKey)) || createAutomationTarget(sessionKey); +async function getOrCreateAutomationTarget(sessionKey, groupTitle) { + return (await resolveOwnedAutomationTarget(sessionKey)) || createAutomationTarget(sessionKey, groupTitle); } // Close only the session's pi-chrome-owned window/tab, and only if it still exists. Never touches @@ -1317,7 +1327,7 @@ async function getTabByParams(params, { createOwnedTarget = true } = {}) { // existing tab pass targetId/urlIncludes/titleIncludes above. const sessionKey = sessionKeyOf(params); tab = createOwnedTarget - ? await getOrCreateAutomationTarget(sessionKey) + ? await getOrCreateAutomationTarget(sessionKey, params.sessionGroupTitle) : await resolveOwnedAutomationTarget(sessionKey); if (!tab) { throw new Error( diff --git a/package.json b/package.json index 8f2b5fa..89d306a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pi-chrome", - "version": "0.15.39", + "version": "0.15.40", "scripts": { "test": "node test-suite/unit/csp-eval.test.mjs && node test-suite/unit/automation-target.test.mjs", "version": "node scripts/sync-manifest-version.js", diff --git a/test-suite/unit/automation-target.test.mjs b/test-suite/unit/automation-target.test.mjs index 9c8618c..8a6ea2b 100644 --- a/test-suite/unit/automation-target.test.mjs +++ b/test-suite/unit/automation-target.test.mjs @@ -232,6 +232,14 @@ async function run() { const blankTitle = await w.dispatch("tab.new", { url: "https://pi.test/blank-title", groupTitle: "", group: false, sessionKey: SK }); ok(typeof blankTitle.tab.groupId === "number" && blankTitle.tab.groupId >= 0, "tab.new-group: groupTitle:'' still creates a grouped tab"); ok(blankTitle.group.title === "Pi", "tab.new-group: blank groupTitle falls back to a group instead of opting out"); + + const nav2 = await w.dispatch("page.navigate", { + url: "https://pi.test/new-automation-target", waitUntilLoad: false, + sessionKey: "session:beta", joinSessionGroup: true, sessionGroupTitle: groupTitle, + }); + ok(state.groups.size === groupsBefore + 1, "automation-target-group: reused the existing session group, only blank-title Pi group was extra"); + ok(nav2.groupId === groupId, "automation-target-group: new automation target joined the existing session group"); + ok(nav2.windowId === nav.windowId, "automation-target-group: new automation target was created in the existing group's window"); } // ===== tab.new never leaves an ungrouped tab behind when grouping fails. ===== -- cgit v1.2.3