webhelm 1.0.8 → 1.0.9

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/README.md CHANGED
@@ -35,7 +35,7 @@ The daemon-managed Chrome Extension is installed from the npm Runtime and does n
35
35
 
36
36
  The popup has a confirmation step to stop one Session, clear Sessions in one workspace, or clear Sessions across all workspaces. These actions are executed by the same Node daemon as the CLI, over the existing CDP-backed Extension RPC. No shell access or HTTP listener is granted to the Extension. The persistent ChatGPT queue is not deleted, and queued work may create new Sessions afterward.
37
37
 
38
- New installations use the default Chrome tab group `WebHelm` and the idle page `helm.html`. Idle URLs use only `workspace` and `role=idle`; Lease URLs use Chrome's actual `tabId` for initial CDP target discovery. There is no separate slot identity. Legacy `Harness` default groups migrate only when their tabs are idle. Custom workspace names are unchanged; the old `workspace.html` path and lease titles remain readable until their Sessions finish. Closing an allocated Session returns its managed tabs to the idle page, retaining physical Chrome targets, while unleased popup children are closed. Do not manually remove active managed tabs.
38
+ New installations use the default Chrome tab group `WebHelm` and the idle page `helm.html`. Idle URLs use only `workspace` and `role=idle`; Lease URLs use Chrome's actual `tabId` for initial CDP target discovery. There is no separate slot identity. The default `WebHelm` tab group is placed at the first position after pinned tabs when created or restored, while Session allocation does not override later user rearrangement. Chrome-saved tab group shortcuts in the bookmarks bar are separate from active groups and cannot safely be deduplicated through the Extension API. Legacy `Harness` default groups migrate only when their tabs are idle. Custom workspace names are unchanged; the old `workspace.html` path and lease titles remain readable until their Sessions finish. Closing an allocated Session returns its managed tabs to the idle page, retaining physical Chrome targets, while unleased popup children are closed. Do not manually remove active managed tabs.
39
39
 
40
40
  For a real browser integration check on macOS, run `node test/e2e/extension-control.e2e.mjs`; it creates a temporary isolated Chrome profile and daemon and does not touch the current production Sessions.
41
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webhelm",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Agent browser automation in a visible managed Chrome with persistent JavaScript sessions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "WebHelm",
4
- "version": "0.4.1",
4
+ "version": "0.4.2",
5
5
  "description": "Own named Chrome tab-group workspaces and lazy reusable tab pools for browser agents.",
6
6
  "permissions": [
7
7
  "storage",
@@ -128,6 +128,22 @@ export class WorkspaceManager {
128
128
  return await this.chrome.tabs.query({ groupId });
129
129
  }
130
130
 
131
+ // Only position the default group when it is created, migrated or restored.
132
+ // Never move it during routine Session allocation or periodic health checks,
133
+ // since the user may intentionally reorder the tab strip.
134
+ async positionDefaultGroupFirst(groupId, name) {
135
+ if (name !== "WebHelm") return false;
136
+ const groupTabs = await this.groupTabs(groupId);
137
+ if (!groupTabs.length) return false;
138
+ const windowId = groupTabs[0].windowId;
139
+ const windowTabs = await this.chrome.tabs.query({windowId});
140
+ const firstUnpinnedIndex = windowTabs.filter(tab => tab.pinned).length;
141
+ const firstGroupIndex = Math.min(...groupTabs.map(tab => tab.index));
142
+ if (firstGroupIndex === firstUnpinnedIndex) return false;
143
+ await this.chrome.tabGroups.move(groupId, {index: firstUnpinnedIndex});
144
+ return true;
145
+ }
146
+
131
147
  tabUrl(tab) {
132
148
  return String(tab?.pendingUrl || tab?.url || "");
133
149
  }
@@ -377,6 +393,7 @@ export class WorkspaceManager {
377
393
  for (const tab of created) this.markOwned(tab.id);
378
394
  const groupId = await this.chrome.tabs.group({ tabIds: created.map((tab) => tab.id) });
379
395
  await this.chrome.tabGroups.update(groupId, { title: name, collapsed: true });
396
+ await this.positionDefaultGroupFirst(groupId, name);
380
397
  for (const tab of created) await this.waitForIdleTab(tab.id, name);
381
398
  return await this.status(name);
382
399
  }
@@ -455,6 +472,8 @@ export class WorkspaceManager {
455
472
  for (const name of Object.keys(config.workspaces || {}).sort()) {
456
473
  restored.push(await this.ensureWorkspace(name));
457
474
  }
475
+ const defaultGroup = restored.find(item => item.name === "WebHelm" && item.initialized);
476
+ if (defaultGroup) await this.positionDefaultGroupFirst(defaultGroup.groupId, "WebHelm");
458
477
  return { workspaces: restored };
459
478
  }
460
479
 
@@ -492,6 +511,9 @@ export class WorkspaceManager {
492
511
  await this.chrome.tabs.update(tab.id, {url: this.idleUrl(newName), active: false});
493
512
  updated.push(tab);
494
513
  }
514
+ // Position before committing the new config so move failures still
515
+ // unwind the legacy migration coherently in the catch block.
516
+ if (group) await this.positionDefaultGroupFirst(group.id, newName);
495
517
  config.workspaces[newName] = config.workspaces[oldName];
496
518
  delete config.workspaces[oldName];
497
519
  await this.saveConfig(config);