aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTianren Dong <tianrendong.dtr@gmail.com>2026-06-22 21:04:05 -0400
committerTianren Dong <tianrendong.dtr@gmail.com>2026-06-22 21:04:05 -0400
commit2c1a4b3cf42c149f52d491131680e062e1935f5f (patch)
tree9ab27497cd52c5a04db63ae1a7bdba07e4b78134
parentc1ed934dd20931b177e233ae0327a290a8001631 (diff)
0.15.40: keep automation targets in session group
-rw-r--r--CHANGELOG.md4
-rw-r--r--extensions/chrome-profile-bridge/browser-extension/manifest.json2
-rw-r--r--extensions/chrome-profile-bridge/browser-extension/service_worker.js22
-rw-r--r--package.json2
-rw-r--r--test-suite/unit/automation-target.test.mjs8
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. =====