Skip to content

feat(extension): expose tab group control via hub WS protocol#440

Open
ykswang wants to merge 1 commit intoalibaba:mainfrom
ykswang:feat/hub-tab-group-control
Open

feat(extension): expose tab group control via hub WS protocol#440
ykswang wants to merge 1 commit intoalibaba:mainfrom
ykswang:feat/hub-tab-group-control

Conversation

@ykswang
Copy link
Copy Markdown

@ykswang ykswang commented Apr 12, 2026

Motivation

Every call to the hub's execute creates a new tab group named PageAgent(${task}) with a random color. The hub never cleans these up on its own, so callers that drive many tasks in a row end up with a long list of visually-identical groups and:

  • no way to tell which group is the one currently running, and
  • no way to free them afterwards (tab group APIs are not reachable from outside the extension — not via AppleScript, not via CDP for external callers).

Instead of baking a specific cleanup / visibility policy into the extension (auto-collapse? auto-ungroup? keep forever?), this PR exposes the underlying mechanism over the existing hub WS protocol and lets callers decide policy.

Protocol additions

Outbound: result carries tabGroupId

{ type: "result", success: boolean, data: string, tabGroupId?: number }

tabGroupId is the id of the tab group created for the task that just finished, or undefined if no group was created (e.g. includeInitialTab: false with no new tabs opened).

Inbound: new tab_group action

{ type: "tab_group", groupId: number,
  action: "close" | "ungroup" | "collapse" | "expand" }
  • close — close every tab in the group (the empty group disappears)
  • ungroup — detach tabs from the group, keep them open
  • collapse / expand — change visual collapsed state

Fire-and-forget: the hub applies the action best-effort and does not reply. Failures are logged on the hub side only; error stays reserved for task-scoped errors.

Compatibility

Purely additive. Callers that don't read tabGroupId or send tab_group see zero behavior change. The reference caller packages/mcp/src/hub-bridge.js ignores unknown message types and doesn't break.

Example (caller policy)

// close on success, collapse on failure so you can inspect it later
ws.on('message', (raw) => {
  const msg = JSON.parse(raw)
  if (msg.type !== 'result' || msg.tabGroupId == null) return
  ws.send(JSON.stringify({
    type: 'tab_group',
    groupId: msg.tabGroupId,
    action: msg.success ? 'close' : 'collapse',
  }))
})

Implementation notes

  • TabsController exposes activeTabGroupId; MultiPageAgent and useAgent forward it up so useHubWs can read it without peeking into agent internals.
  • applyTabGroupAction uses chrome.tabs / chrome.tabGroups directly from the hub extension page — tabGroups permission already exists in wxt.config.js.
  • Previously private field tabGroupId on TabsController stays private; a read-only getter activeTabGroupId is added so no internal code paths change.

Test plan

  • npm run typecheck passes
  • npx eslint clean on all touched files
  • Manual: run a task, observe result.tabGroupId is present
  • Manual: send each of close / ungroup / collapse / expand and verify the corresponding Chrome behavior
  • Manual: old caller (only branches on result / error) still works unchanged

The hub creates a new tab group per task (`PageAgent(task)`) but never
cleans it up. Callers that run many tasks in a row end up with a long
list of visually identical groups and no way to tell which one is
running, nor to free them afterwards.

Extend the hub protocol so that callers own the policy:

  Outbound:
    `result` now carries `tabGroupId?: number`.

  Inbound (new):
    `{ type: "tab_group", groupId, action }` where action is one of
    `close` | `ungroup` | `collapse` | `expand`.

The hub itself still takes no opinion on what to do with groups on
task completion — callers choose whatever fits their flow (close on
success / keep on failure, collapse non-active groups, etc.). Existing
callers that don't read `tabGroupId` or send `tab_group` see zero
behavior change; the new message types are purely additive.

Implementation notes:
- `TabsController` exposes `activeTabGroupId`; `MultiPageAgent` and
  `useAgent` forward it up so `useHubWs` can read it without peeking
  into the agent internals.
- `applyTabGroupAction` uses `chrome.tabs` / `chrome.tabGroups`
  directly from the hub extension page (already permissioned).
- `tab_group` actions are fire-and-forget; failures are logged on the
  hub side and do not emit `error`, which stays reserved for
  task-scoped errors.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant