pi-chrome 0.15.39 → 0.15.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable user-facing changes to `pi-chrome`.
4
4
 
5
+ ## 0.15.40 — 2026-06-22
6
+
7
+ - **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.
8
+
5
9
  ## 0.15.39 — 2026-06-07
6
10
 
7
11
  - **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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Pi Chrome Connector",
4
- "version": "0.15.39",
4
+ "version": "0.15.40",
5
5
  "description": "Lets Pi control tabs in Chrome via a local connector at 127.0.0.1.",
6
6
  "permissions": [
7
7
  "tabs",
@@ -86,9 +86,19 @@ function isPiChromeOwnedTarget(tabId, sessionKey) {
86
86
  return false;
87
87
  }
88
88
 
89
- // Create a fresh dedicated automation target for `sessionKey`. Prefer an isolated window; fall
90
- // back to a tab. Structured so a window can be the default while tab-fallback stays a one-liner.
91
- async function createAutomationTarget(sessionKey) {
89
+ // Create a fresh automation target for `sessionKey`. If this session already has a tab group,
90
+ // create the tab inside that group's window so one Pi session keeps one Chrome tab group (Chrome
91
+ // groups cannot span windows). If no group exists yet, prefer an isolated window; fall back to a
92
+ // tab. When the tab is created in a pre-existing group window, leave windowId unset so cleanup only
93
+ // closes our tab, never that whole window.
94
+ async function createAutomationTarget(sessionKey, groupTitle) {
95
+ const existingGroup = groupTitle ? await findGroupRecordByTitle(groupTitle) : null;
96
+ if (existingGroup && typeof existingGroup.windowId === "number") {
97
+ const tab = await chrome.tabs.create({ url: "about:blank", active: false, windowId: existingGroup.windowId });
98
+ automationTargets.set(sessionKey, { windowId: undefined, tabId: typeof tab.id === "number" ? tab.id : undefined });
99
+ await persistAutomationTargets();
100
+ return tab;
101
+ }
92
102
  if (chrome.windows && typeof chrome.windows.create === "function") {
93
103
  try {
94
104
  const win = await chrome.windows.create({ url: "about:blank", focused: false });
@@ -125,8 +135,8 @@ async function resolveOwnedAutomationTarget(sessionKey) {
125
135
 
126
136
  // Return the session's dedicated automation target, creating it on first use (or after the user
127
137
  // closed it). Used by page/navigation actions that need a live surface to drive.
128
- async function getOrCreateAutomationTarget(sessionKey) {
129
- return (await resolveOwnedAutomationTarget(sessionKey)) || createAutomationTarget(sessionKey);
138
+ async function getOrCreateAutomationTarget(sessionKey, groupTitle) {
139
+ return (await resolveOwnedAutomationTarget(sessionKey)) || createAutomationTarget(sessionKey, groupTitle);
130
140
  }
131
141
 
132
142
  // 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 } = {}) {
1317
1327
  // existing tab pass targetId/urlIncludes/titleIncludes above.
1318
1328
  const sessionKey = sessionKeyOf(params);
1319
1329
  tab = createOwnedTarget
1320
- ? await getOrCreateAutomationTarget(sessionKey)
1330
+ ? await getOrCreateAutomationTarget(sessionKey, params.sessionGroupTitle)
1321
1331
  : await resolveOwnedAutomationTarget(sessionKey);
1322
1332
  if (!tab) {
1323
1333
  throw new Error(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-chrome",
3
- "version": "0.15.39",
3
+ "version": "0.15.40",
4
4
  "scripts": {
5
5
  "test": "node test-suite/unit/csp-eval.test.mjs && node test-suite/unit/automation-target.test.mjs",
6
6
  "version": "node scripts/sync-manifest-version.js",
@@ -232,6 +232,14 @@ async function run() {
232
232
  const blankTitle = await w.dispatch("tab.new", { url: "https://pi.test/blank-title", groupTitle: "", group: false, sessionKey: SK });
233
233
  ok(typeof blankTitle.tab.groupId === "number" && blankTitle.tab.groupId >= 0, "tab.new-group: groupTitle:'' still creates a grouped tab");
234
234
  ok(blankTitle.group.title === "Pi", "tab.new-group: blank groupTitle falls back to a group instead of opting out");
235
+
236
+ const nav2 = await w.dispatch("page.navigate", {
237
+ url: "https://pi.test/new-automation-target", waitUntilLoad: false,
238
+ sessionKey: "session:beta", joinSessionGroup: true, sessionGroupTitle: groupTitle,
239
+ });
240
+ ok(state.groups.size === groupsBefore + 1, "automation-target-group: reused the existing session group, only blank-title Pi group was extra");
241
+ ok(nav2.groupId === groupId, "automation-target-group: new automation target joined the existing session group");
242
+ ok(nav2.windowId === nav.windowId, "automation-target-group: new automation target was created in the existing group's window");
235
243
  }
236
244
 
237
245
  // ===== tab.new never leaves an ungrouped tab behind when grouping fails. =====