Repository navigation
v2.3.0 — 每个 agent 一块自己的地方
v2.3.0 — 每个 agent 一块自己的地方
上一版让每个连接有了自己的目标,这一版把它补成自己的空间:一个组、一个上限、一个
标签栏上看得见的位置。
为什么还要再改一次
用户的原话:"我看 agent 还是不知道去操作其它标签,只会弄当前激活的显示的那个标签。"
这是三个原因叠在一起,只修一个都不够:
- 默认行为:会话还没有绑定时,目标回落到
activeTab—— 也就是人正在看的那一页。
单 agent 时它碰巧等于"agent 该干的活",多 agent 时就是灾难。 - 没人告诉它:工具描述和技能里只有一句"省略 tab 就是你自己的页",而"你自己还没有页"
这件事从来没有被说出来过。 - 习惯:模型在别处学到的浏览器操作就是"操作当前页",没有理由自己想到去开一页。
所以这一版把"给自己开一页"从要求变成事实。
自动工作区
MCP / CLI 建连后立刻自报家门(ui.hello)。声明自己是 agent 的连接,第一次要动页面而还没
指定目标时,浏览器给它开一页空白标签、归进它自己的组,并在这次结果里附一次提示:
{"workspace": {"tab": 7, "group": "Claude", "created": true, "hint": "..."}}Agent 于是永远不会默认开在人眼前那一页上。想看人的页面仍然便宜:list 里
group: "human" 那条,显式传 tab —— 读一下不会把它抢走。
四条不触发:① 没发 ui.hello(裸 WS / e2e / 脚本保持老行为);② 方法本身在管标签页;
③ 显式点了 tab 或已有绑定;④ AI_BROWSER_AUTO_WORKSPACE=0。CLI 发
auto_workspace:false —— 人坐在终端前敲命令时,"不传 tab 就打当前页"才是他要的。
组与上限
group = 'human' | 'agent:<会话>'
每组默认 8 页(AI_BROWSER_TAB_LIMIT)。new_tab 撞上限时拒绝,不回"悄悄挤掉一页":
挤掉是破坏性的(agent 可能正在那一页跑流程),拒绝是一次必须做的决定 —— 这正是上限的
理由:后台运行时没人看得见它的标签页。
-32001 your tab group is full (8/8) — close one before opening another
data: { reason: "group-full", limit: 8, tabs: [{tab, url}, ...] }
页面自己弹的窗口没法拒绝(站点脚本在等它),只能牺牲:本组里"最久没用、不在人眼前、
没有挂着对话框"的那一页,并在 list 的 evicted 里回采一次。
上限只约束声明过自己是 agent 的连接。裸 WS 拿到的组只是归属标签,不限量 —— 否则那些
一次性开二十个标签的回归套件会突然全红。
标签栏
人的标签页平铺;每个 agent 组折叠成一个 pill(组色 + 名字 + 页数,满额变暖色)。悬停展开,
点击钉住;正在看的那一组强制展开,否则人切过去就找不到自己在看什么。点开组里的页只是
"你看",不改 agent 的目标。
没有做成"竖着掉下来的下拉菜单":标签栏是固定高度的 BrowserView,往下弹会被自己裁掉;要能弹
就得每次悬停把标签栏长高、把页面下推 —— 那正是本项目历史上闪烁/几何脱节的来源。就地展开
拿到同样的折叠效果,不动布局。
验证
- 新增
tests/test_tab_group.js(12 条)与tests/test_tab_bar_layout.js(10 条):
上限判定、弹窗牺牲顺序(不许牺牲正在看的、正在被开的、挂着对话框的)、折叠状态机。
交互逻辑抽成纯函数才测得了 —— 靠人肉把鼠标移上去验证等于没验证。 npm run multiagent从 32 条断言扩到 46 条:M7 自动工作区(含"人的页面没被换掉")、
M8 上限与拒绝回执、M9 组间互不影响。- 全量:vitest 73 文件 / 1024 条;e2e:all 22 套件 / 743 断言。
顺手修掉的一个老 bug:打开的目标页会自己跳回首页
npm run background 这次开始稳定失败,追下去发现不是新代码的问题 ——
在 v2.2.0 的 tag 上跑同样稳定失败:
新标签页的占位页(loading.html)后面挂着一个"垫片装好后就去加载目标地址"的延迟
动作。垫片安装偶尔会挂十几秒,这期间 agent 已经把这一页导航走了;等它终于 resolve,
那个延迟动作又把启动页加载回来 —— 现象是"打开的是目标页,下一刻自己跳回首页",
读到的树也成了首页的。
修法是一句判据:延迟动作执行前先看"现在还在占位页上吗",不在就什么都不做。
(page_manager.newTab 的 proceed()。)
升级注意
- 以前"不传 tab 就打当前页"的 agent 行为变了:第一次调用会得到一页空白工作页。
这是刻意的(隔离的前提),回执里有提示;要恢复老行为设AI_BROWSER_AUTO_WORKSPACE=0。 - 一个 agent 同时开超过 8 页会被拒绝,需要先关。