Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ Starting from 0.2.0, CLI / Extension / DSH Plugin share the same version number.
- CLI cancellation is armed before sending a browser command, so an immediate
Ctrl-C can cancel the operation and return its outcome instead of terminating silently.

### Removed

- Automatic native Agent Window tab groups and the `tabGroups` permission, to
prevent new `BrowserSkill` groups from accumulating across browsers through
Chrome sync. Existing saved groups are not deleted; page control indicators,
background window behavior and user-tab routing remain available.

## [0.4.0] - 2026-10-30

### Changed
Expand Down
1 change: 0 additions & 1 deletion apps/extension/PRIVACY.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ The Extension requests the following Chrome permissions. Each is used solely for
- **`scripting`** — Inject the full-page screenshot helper into the selected page when it is missing, such as after an extension reload.
- **`webNavigation`** — Track page navigation and frames so captures, recordings, and human-help completion checks follow the correct document.
- **`tabs`** — Inspect, create, and close tabs in the Agent Window; query tab metadata.
- **`tabGroups`** — Label controlled tabs in Agent Windows with an orange BrowserSkill group, and remove that group when the task ends.
- **`windows`** — Create and manage the dedicated Agent Window that isolates agent activity from the user's normal browsing.
- **`alarms`** — Periodically wake the service worker to keep the selected connection alive and renew remote device authorization.
- **`idle`** — Detect when the device returns from idle/locked so the Extension can promptly re-establish the selected WebSocket connection after the machine wakes. No idle data is stored or transmitted.
Expand Down
1 change: 0 additions & 1 deletion apps/extension/PRIVACY.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ BrowserSkill 让用户指定的 AI 助手通过 `bsk` 命令行工具操作 Chro
- **`scripting`** —— 选定页面缺少长截图辅助脚本时补充注入,例如扩展重新加载后。
- **`webNavigation`** —— 跟踪页面导航和 frame,让截图、录制与人工协助完成检查对应到正确的文档。
- **`tabs`** —— 在代理窗口内查询、创建、关闭标签页;读取标签页元数据。
- **`tabGroups`** —— 在代理窗口中以橙色 BrowserSkill 标签组标识受控标签页,并在任务结束时移除该分组。
- **`windows`** —— 创建并管理独立的代理窗口,将助手的活动与用户的日常浏览隔离开。
- **`alarms`** —— 周期性唤醒 service worker,保持所选 WebSocket 连接并续期远程授权。
- **`idle`** —— 检测设备从空闲或锁定状态恢复,以便及时重新建立所选 WebSocket 连接;不保存或传输空闲状态数据。
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { OverlayController, shouldShowAgentControlOverlay } from "@/content/over
import { watchDaemonConnection } from "@/lib/daemon-connection-preference";
import { OVERLAY_AGENT_STATE, type OverlayAgentStateMessage } from "@/lib/overlay-bridge";
import { attachSessionsLiveFlag } from "@/lib/sessions-live-flag";
import { AgentWindowTabGroups } from "@/session-manager/agent-window-tab-group";
import { isAgentControlledTab } from "@/session-manager/manager";
import { AgentWindowRouting } from "@/session-manager/window-routing";
import { ToolDispatcher } from "@/tools/dispatcher";
import { hasActiveHelp } from "@/tools/human-loop";
import { hasActiveRecording } from "@/tools/record";
import { handleTabCreate } from "@/tools/tabs";
import { VideoManager } from "@/video/manager";

vi.hoisted(() => {
Expand Down Expand Up @@ -38,7 +38,6 @@ vi.mock("@/lib/recording/frame-coordinator");
vi.mock("@/lib/sessions-live-flag");
vi.mock("@/long-screenshot/background");
vi.mock("@/session-manager/event-handler");
vi.mock("@/session-manager/agent-window-tab-group");
vi.mock("@/session-manager/window-routing");
vi.mock("@/session-manager/agent-window", () => ({
AGENT_WINDOW_HOME: "about:blank",
Expand Down Expand Up @@ -93,13 +92,22 @@ async function fixture() {
sendMessage,
get: vi.fn(async () => ({ url: "https://example.test" })),
query: vi.fn(async () => []),
group: vi.fn(async () => 100),
ungroup: vi.fn(async () => {}),
onDetached,
onActivated: event(),
onUpdated: event(),
onCreated,
onAttached,
onRemoved: event(),
},
tabGroups: {
query: vi.fn(async () => []),
get: vi.fn(async () => ({ id: 100, windowId: 10 })),
update: vi.fn(async () => {}),
onRemoved: event(),
onMoved: event(),
},
debugger: { onDetach: event() },
webNavigation: {
onBeforeNavigate: event(),
Expand Down Expand Up @@ -132,7 +140,48 @@ async function fixture() {
};
}

it("refreshes released-page controls and groups after attachment completes", async () => {
it("keeps task tabs ungrouped while showing controls after creation and attachment", async () => {
vi.useFakeTimers();
const f = await fixture();
const home = { id: 10, windowId: 10, groupId: -1, pinned: false } as chrome.tabs.Tab;
const created = { ...home, id: 20, url: "https://example.test" };
const tabs = [home];
Object.assign(chrome.tabs, {
query: vi.fn(async () => tabs),
get: vi.fn(async (id: number) => tabs.find((tab) => tab.id === id)),
create: vi.fn(async () => {
tabs.push(created);
f.onCreated.emit(created);
return created;
}),
});

f.deps.onSessionsChanged?.();
const result = await handleTabCreate(f.deps.sessions, {
session_id: f.task.sessionId,
url: created.url,
});
expect(result).toMatchObject({ tab_id: created.id, window_id: home.windowId });
if ("code" in result) throw new Error(result.message);
// Use the real background callbacks invoked by the dispatcher and browser.
f.deps.onAgentTabClaimed?.(result.tab_id, result.window_id);
f.onAttached.emit(created.id, { newWindowId: home.windowId, newPosition: 1 });
await vi.advanceTimersByTimeAsync(0);

expect(isAgentControlledTab(f.task, created.id)).toBe(true);
for (const tab of tabs) {
expect(tab.groupId).toBe(-1);
expect(f.sendMessage).toHaveBeenCalledWith(
tab.id,
expect.objectContaining({ sessionId: f.task.sessionId, mode: "control" }),
);
}
expect(chrome.tabs.group).not.toHaveBeenCalled();
expect(chrome.tabs.ungroup).not.toHaveBeenCalled();
expect(chrome.tabGroups.update).not.toHaveBeenCalled();
});

it("refreshes released-page controls after attachment completes", async () => {
const f = await fixture();
f.overlay.applyAgentControlMode("one", "control");
f.deps.sessions.release("one");
Expand All @@ -143,18 +192,6 @@ it("refreshes released-page controls and groups after attachment completes", asy
10,
expect.objectContaining({ sessionId: null, mode: "hidden" }),
);
const groups = vi.mocked(AgentWindowTabGroups).mock.instances.at(-1)!;
expect(groups.sync).toHaveBeenCalledOnce();
});

it("refreshes native window identification after session changes and explicit tab claims", async () => {
const f = await fixture();
const groups = vi.mocked(AgentWindowTabGroups).mock.instances.at(-1)!;
expect(AgentWindowTabGroups).toHaveBeenLastCalledWith(f.deps.sessions);
f.deps.onSessionsChanged?.();
expect(groups.sync).toHaveBeenCalledTimes(1);
f.deps.onAgentTabClaimed?.(10, 10);
expect(groups.sync).toHaveBeenCalledTimes(2);
});

it.each([false, true])("hides an observed tab after detachment, remote=%s", async (remote) => {
Expand Down
8 changes: 1 addition & 7 deletions apps/extension/src/entrypoints/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import { attachSessionsLiveFlag } from "@/lib/sessions-live-flag";
import { captureTaskPreview, focusTask } from "@/lib/task-preview";
import { attachUiChannel } from "@/lib/ui-channel";
import { attachLongScreenshot } from "@/long-screenshot/background";
import { AgentWindowTabGroups } from "@/session-manager/agent-window-tab-group";
import { createDisconnectCleanup } from "@/session-manager/disconnect-cleanup";
import { attachSessionEventHandler } from "@/session-manager/event-handler";
import { isAgentControlledTab, SessionManager } from "@/session-manager/manager";
Expand Down Expand Up @@ -92,7 +91,6 @@ export default defineBackground(() => {
},
});
const sessions = new SessionManager({ remote: () => remoteEndpoint !== null });
const windowTabGroups = new AgentWindowTabGroups(sessions);
// Capture the original owner before cooperation releases a detached page.
attachSessionEventHandler({
manager: sessions,
Expand Down Expand Up @@ -333,7 +331,6 @@ export default defineBackground(() => {
}

function onOverlaySessionStateChanged(): void {
windowTabGroups.sync();
cooperation.syncSessions();
windowRouting.sync();
debug.sync();
Expand Down Expand Up @@ -374,10 +371,8 @@ export default defineBackground(() => {
void pushOverlayStateForTab(tab.id, tab.windowId);
});
chrome.tabs.onAttached.addListener((tabId, { newWindowId }) => {
// Reconcile again after Chrome finishes the move; editing a group or
// messaging a page can fail transiently while it is being dragged.
// Refresh controls after Chrome finishes moving the page.
void pushOverlayStateForTab(tabId, newWindowId);
windowTabGroups.sync();
});
chrome.tabs.onDetached.addListener((tabId) => {
void video.stopTab(tabId, "session_ended").catch(() => {});
Expand Down Expand Up @@ -460,7 +455,6 @@ export default defineBackground(() => {
onSessionsChanged: onOverlaySessionStateChanged,
onBrowserControlResumed,
onAgentTabClaimed: (tabId, windowId) => {
windowTabGroups.sync();
void cooperation.observeTab(tabId).catch(() => {});
void pushOverlayStateForTab(tabId, windowId);
},
Expand Down
Loading
Loading