Skip to content

Releases: angelky7/dsh-browser-control

v1.1.0 — npm 包名迁移到 @angelky7/dsh-browser-control(无功能变更)

Choose a tag to compare

@angelky7 angelky7 released this 24 Oct 12:22

v1.1.0

这一版只做一件事:npm 包名随账号改名迁移。插件代码、扩展代码与 v1.0.19 完全相同,没有功能变更。

包名变更

GitHub 账号已由 caob23 改名为 angelky7,而 npm 的用户名无法修改,因此新建同名组织 angelky7 承接 scope。

旧 新
npm 包名 @caob23/dsh-browser-control @angelky7/dsh-browser-control
安装命令 dsh plugin --profile web add @caob23/dsh-browser-control dsh plugin --profile web add @angelky7/dsh-browser-control
  • 旧包在 npm 上保留,但已 npm deprecate,安装时会提示去向
  • 也评估过去掉 scope 的 dsh-browser-control、dsh-browser-bridge 等名字,均已被他人占用,最终回到带 scope 的方案

升级步骤(装过旧版的用户)

dsh plugin --profile web remove @caob23/dsh-browser-control
dsh plugin --profile web add @angelky7/dsh-browser-control

profile 覆盖层里手写过 name: '@caob23/dsh-browser-control' 的配置需一并更新;只写 id 的不受影响(宿主合并按 id 认)。

扩展不用重装

extension/ 未改动,DSH-Browser-Control-v1.0.19.zip 仍是当前版本,继续用它即可。

安装

dsh plugin --profile web add @angelky7/dsh-browser-control@1.1.0
# 撞上 ERR_PNPM_NO_MATCHING_VERSION(镜像滞后)时,这一次显式走官方源:
dsh plugin --profile web add @angelky7/dsh-browser-control@1.1.0 --registry=https://registry.npmjs.org/

ERR_PNPM_NO_MATURE_MATCHING_VERSION 是你机器上的 24 小时新包门槛,不是包坏了——判别与三条绕法见 README。

资产

资产 说明
dsh-browser-control-plugin-v1.1.0.zip 插件离线包;解压后 dsh plugin --profile web add file:<解压目录>

扩展资产不重复发布,仍见 v1.0.19。

资产校验和

资产 大小 SHA-256 MD5
dsh-browser-control-plugin-v1.1.0.zip 113 341 B 2af8aadf04071f399c00ccf5dfb1356aeea9849baf6cdb4c2938a9c4046c789d 2c2d4fd7b83ee48e1f38ccd71820f519

发布前本地解包复验:zip 头 504b0304、CRC 全过、无反斜杠条目、含 package.json 与 cordis.patch.yml、package.json 的 name 为 @angelky7/dsh-browser-control。

v1.0.19 — 接管浮层 browser_hold(防误触的软层)+ 四处复审修补

Choose a tag to compare

@angelky7 angelky7 released this 20 Oct 12:52

v1.0.19

一个外部 PR 带来新工具 browser_hold(接管浮层),加上合并后复审抓到的四处问题。这一版改了 extension/,扩展必须重装。

1. 接管浮层:browser_hold(合并 PR #18,@long-arm-ape)

给某个标签页加一层可撤销的提示层:页面变暗,中间一张卡片写明模型在做什么,挡住误触。定位是告知 + 防误触的软层,不是安全边界——桥的 token 模型读得到也改得动;真正能冻住浏览器的是 v1.0.18 的弹窗「锁定」。

  • 标题由扩展写死(DSH Browser Control),调用方只能给正文 note(压平换行、截到 400 字符)。否则模型能写一句系统口吻的话冒充提示。
  • 关掉 = 用户接管,不拦模型:双击任意处 / 连按两下 Esc / 按住 Esc ≈0.6 秒。关掉之后模型照常操作这一页。
  • 卡片上的「中止模型操作」才拦:两次点击确认(第二次 3 秒内),之后 click / input / press / nav 在扩展的 runCommand 入口被拒,零 CDP 派发。模型要恢复必须先与用户确认,再调 browser_hold action=off——那一步等于「确认继续」。
  • 状态在 chrome.storage.session、按 tabId 记:活过 service worker 回收(内存标志 30 秒就丢,那正是浮层「以为挡着、其实没挡」的失败方式),不落盘,浏览器关掉即清。
  • 盖不住就如实说:covered:false + reason(error-page / browser-page / devtools / inject-failed)。已知盖不住:严格 CSP、全屏、跨源 iframe、PDF 阅读器、原生弹窗。
  • 导航即消失:页面换了浮层就没了,此时命令放行、存储里那条顺手清掉、status 如实报 active:false。
  • 模型的错误面也接上了同一套线协议:中止拒绝的前缀 browser-hold-aborted: → code=held_by_user、effectState=not_started,提示指向 browser_hold action=off,而不是「换个选择器重试」。

2. 合并后复审抓到的四处

  1. 模型自己的点击被自己的浮层吃掉。 浮层全屏、z-index 最大,click 按坐标派发先命中的是它——页面什么都没发生,结果只轻描淡写一句 hitVerified:false;更糟的是同一坐标连点两下会被浮层的「双击接管」逻辑当成用户操作,以用户的名义把浮层关掉。现在命中到自己那层时临时把宿主设成 pointer-events:none、重测命中点、派发完在 finally 里恢复,结果带 bypassedOwnOverlay:true。浮层挡的是人,不是模型。
  2. AX 快照把浮层自己编进去了。 卡片上的「中止模型操作」是标准 button,会进无障碍树:模型读到的快照里就有自己挂的提示语,甚至能按 ref 点到它。现在 stamping 函数遇到 [data-dsh-hold] 子树直接跳过:不占 ref、不计入 total(ax.ownOverlay 单独计数)。DOM 模式原本就排除了,这次补齐 AX 模式。
  3. hold on 会把「中止」静默清掉。 模型被拦一次之后只要再 on 一次就绕过去了,既不用问用户也不必说「继续」。现在中止期间 hold on 被拒,只有 off 能解。
  4. 只读档把 hold status 也拒了。 status 只读(读 session 条目 + 探一下页面那层还在不在),不是写操作;「只读」不该连看一眼浮层状态都不让。闸门改为按动作判:policyRefusal(command, flags, params),on / off 仍拒、status 放行。

3. 验证

  • npm run verify 548 项断言,exit 0(535 来自 PR #18 与合并,+13 是本轮的台子):浮层台 44→52、AX 台 18→22、策略台 41→42。
  • 点击旁路那条不是断言假件返回值:台子里有一台真的执行定位表达式的假 DOM,浮层在场时 elementFromPoint 先返回宿主,宿主 pointer-events:none 之后才返回目标。
  • 四处修法各自做过变异测试:摘掉 AX 排除 → 4 项红(快照里真的出现「中止模型操作」);去掉 pointer-events 旁路 → 2 项红(hitVerified:false, hitInstead:"div");去掉中止检查 → 2 项红(hold on 成功且注入次数变 2);只读档那条第一次只改了一半分支,新断言当场把它抓红。
  • PR #18 自带的证据:页面侧三个函数在 Edge 156 + 真 CDP 上跑过 13 条断言(含几何探测:position:fixed、满视口、z-index:2147483647、elementFromPoint 命中浮层);lib/ 与 src/ 重建一致。
  • 仍未覆盖(如实写):vm 台子里的 chrome.debugger 是假的,真 MV3 worker 走真扩展安装的注入链没有被端到端断言;严格 CSP / 全屏 / 跨源 iframe 只保证如实回报 covered:false,没有逐项做真机断言。

4. 安装

dsh plugin --profile web add @caob23/dsh-browser-control@1.0.19
# 撞上 ERR_PNPM_NO_MATCHING_VERSION(镜像滞后)时,这一次显式走官方源:
dsh plugin --profile web add @caob23/dsh-browser-control@1.0.19 --registry=https://registry.npmjs.org/

ERR_PNPM_NO_MATURE_MATCHING_VERSION 是你机器上的 24 小时新包门槛,不是包坏了——判别与三条绕法见 README。

扩展:解压 DSH-Browser-Control-v1.0.19.zip → chrome://extensions → 开发者模式 → 移除旧版(或点扩展卡片上的刷新)→「加载已解压的扩展程序」。不重装就还是旧代码(browser_hold 与上面的修法都在扩展侧)。

5. 资产校验和

资产 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.19.zip 61 267 B 1ee9f08b36a22bf47ff8716a3981c5c1ea8c4c3309af6fddf54d069d468a2c76 9ff23c7dfdd81fb1ea2a62c34a5982bd
dsh-browser-control-plugin-v1.0.19.zip 203 264 B 19126d0c472a538ee145aae08b4efd48ee3c3e1cd03fbae2368722423dc2c8d4 c17e0111f1d0d7a66e23997722bb3158

发布前本地解包复验(zip 头 504b0304、CRC 全过、无反斜杠条目、插件包含 cordis.patch.yml、扩展 manifest version 为 1.0.19、权限仍是 debugger / tabs / tabGroups / storage / alarms),发布后匿名重下逐字节比对。按惯例不放 .sha256 附件。

v1.0.18 — 弹窗一键冻结(只有用户能解)+ 只读档与脚本开关

Choose a tag to compare

@angelky7 angelky7 released this 20 Oct 11:11

v1.0.18

两件事是从外部评审来的:扩展弹窗里的一键冻结(PR #15)与插件侧的只读档 / 脚本开关(PR #16);第三件事是我合并后复审抓到的四个缺口,都补在这版里。

这一版改了 extension/——扩展必须重装,只更新插件不够。装完扩展之后弹窗里会多一个「锁定」按钮。

1. 本地硬锁:用户一键冻结,模型解不开(合并 PR #15,@long-arm-ape)

桥的唯一实际边界是回环地址 + token,而 token 明写在 profile 的 cordis.patch.yml 里——模型读得到、改得动,还能重启桥。所以「模型自己把浏览器控制关掉」在当前架构里本来就是可行的,缺的是反过来那一句:用户想要一个「别碰我的浏览器,而且只有我能解除」的开关。

  • 点扩展图标 → 按钮行中间的「锁定」,单击即生效(打开弹窗本身已经是人的动作,panic switch 不该再加一次确认)。
  • 冻结期间 WebSocket 保持连接:调用方看到「连得上但被冻结」,不会误判成掉线去重试。
  • 拒绝发生在扩展 runCommand 的入口,任何 handler 都不执行 ⇒ 零 CDP 派发、零标签页改动,只读命令也一样(冻结的意思是什么都别做,不是「只别写」)。
  • 徽标从绿色 ON 换成红色 LOCK。
  • 解锁只有一条路:同一个弹窗里点「已锁定」。这是结构性而非承诺——锁通过 chrome.runtime 消息设置,只有扩展页面能发;桥那边没有任何解锁命令,lockSet 也不在 COMMANDS 表里,模型塞一条同名命令帧只会被当作普通命令拦下。
  • 状态落 chrome.storage.local,PROTOCOL_VERSION 仍是 1。

2. 只读档与脚本开关:readOnly / allowScript(合并 PR #16,@long-arm-ape)

profile 覆盖层里给 browser-bridge 加两个字段(默认值就是老行为,装上不会突然变只读):

config:
  readOnly: true      # 只读:点击/输入/按键/导航/滚动/标签页操作/视口模拟/清缓冲全拒
  allowScript: false  # 再把 browser_evaluate(在页面里跑任意 JS)单独关掉
  • 闸门只有一个位置:BridgeController.execute(),在命令发给扩展之前。被拒的命令零副作用,错误码 blocked_by_policy、effectState=not_started,文案写明改哪个字段并劝退原地重试。
  • 为什么要两项而不是一项:只让读工具可用不够——browser_evaluate 能在页面里跑任意 JS,等于把点击、输入、改页面全绕过去。
  • 只读白名单是「读页面 + 读扩展缓冲」那 11 条,默认拒绝:扩展以后新增命令不会自动放行,verify/verify_policy.mjs 会把扩展 COMMANDS 表 26 条逐条对账。
  • 与 WRITE_COMMANDS 是两张故意不同的表(那张判「重试会不会多造成一次副作用」,所以 tabs.group 算只读;这张判「会不会改变状态」,所以不算)。理由写在 src/policy.ts 顶部。
  • 边界如实写着:这两个开关约束的是模型那条工具面。HTTP 面(/api/command,要你的 token)是你的通道、不受影响;真要冻结整个扩展请用上面那个「锁定」。

3. 合并后复审抓到的四处

  1. browser_cleanup 整个绕过了只读档。 它不调 execute(),而是走 controller.cleanup() 在插件进程里逐条 rm ——所以 readOnly: true 开着时,模型仍能一条命令删掉用户整个截图目录,而删除恰恰是「只读」最该拦住的动作。现在 src/policy.ts 里有第三张表 LOCAL_DESTRUCTIVE_COMMANDS,cleanup() 自己站闸门,拒绝文案说的是「会删除本机保存的截图与临时文件……没有任何文件被删除」。台子不只断言文案:先往临时目录写一个文件,被拒之后再读一次目录确认文件还在。
  2. cleanup 从没进过写/读分类表,它的失败也没有错误契约。 它是循环里逐个 await rm、没有 per-entry catch,半路失败(Windows 上被占用的截图 EPERM)时前面已经删掉了;而原来只有「尚未加载配置」那一句调了 classify(),其余异常是裸 fs 错误直接冒出来——模型既拿不到 code/effectState/hint,默认含义还是 not_started「没动过,重试安全」。现在 classify('cleanup', error) + 登记为写操作。对账口径同时从「扩展 COMMANDS 表」扩到「src/index.ts 里每个 classify('…') 用到的名字」——这是 v1.0.17 那次 tabs.closeMany 漏登记的同一族问题,只是发生在插件本地这一侧,原来那条对账永远看不见它。
  3. 硬锁读不出锁状态时原来是 fail-open。 catch 里只记一句 lastError、标志保持「没锁」,而且照样把「已加载」置真——一次瞬时读盘失败之后不再重试,锁永久没了,徽标还是绿色 ON,没人会知道。现在读不到就拒绝,且不置「已加载」,下一条命令重读、存储恢复后自愈;用户仍解得开(弹窗那条通道走内存里的 setHardLock,不依赖这次失败的读)。
  4. 只有 runCommand 会触发重读,所以 service worker 被回收再唤醒之后、第一条命令到来之前,图标显示的是 ON——用户以为锁着,桥那边一切正常。现在 init() 先读锁再连接。

另外把锁的拒绝做成了模型读得到的形状。结果帧只有一个 error 字符串能带信息,扩展抛裸 Error 会被分类成 command_failed,模型于是读到 effectState=unknown:动作可能已经落到页面上 + hint「通常是选择器没命中」——拦在 handler 之前,这两句都指向错误的下一步。现在扩展在文本最前面写 browser-locked-by-user: / browser-lock-unreadable: 前缀,classify() 认它 ⇒ code=locked_by_user、effectState=not_started(写操作也是),标记本身从给模型的那句话里剥掉;两个字符串是跨 extension/ 与 src/ 的线协议,verify_errors 对账两侧必须逐字一致。两种来源的文案也分开了:读不到状态时不说「已被用户锁定」,免得模型转告用户去点一个他根本没按过的按钮。

4. 验证

  • npm run verify 478 项断言,exit 0:385(v1.0.17 基线)→ 411(硬锁台 26)→ 440(策略台 29)→ 451(cleanup 过闸门 + classify 对账)→ 478(锁的 fail-closed / 开机即 LOCK / 端到端错误形状)。
  • 端到端那条是真桥 + 真 background.js + 真 WebSocket:锁着的 click 在 HTTP 面拿到 locked_by_user / not_started,且 Input.dispatch* 的派发次数增量为 0。
  • 每条修法都做过变异测试(改回旧行为确认台子会红):fail-open → 4 项红(其中一条报 no tab available,证明命令真的执行到了 handler);去掉 init() 的预读 → 3 项红(徽标 OFF);扩展侧前缀改名 → 对账红;classify() 去掉锁分支 → 5 项红;摘掉 policyGate('cleanup') → 3 项红;cleanup 从写清单摘掉 → 3 项红。
  • PR #16 提交的 lib/ 与重新构建的结果逐字节一致(npm run build 之后 git diff lib/ 为空)。
  • 还没做的一项如实写着:readOnly / allowScript 出现在 dsh 0.1.7+ 的设置页里,是按宿主投影 Config schema 这个已实测机制推出来的,这两个新字段本身还没在真宿主设置页里看过。
  • CI:ubuntu 与 windows-latest 两个 job 都在 main 上跑通。

5. 安装

在线装(写这一节时官方源 latest 已经是 1.0.18;npmmirror 的同步有延迟,latest 还停在 1.0.17,所以下面两种失败都可能出现,判别见 README 的安装一节):

dsh plugin --profile web add @caob23/dsh-browser-control@1.0.18
# 撞上 ERR_PNPM_NO_MATCHING_VERSION(镜像滞后)时,这一次显式走官方源:
dsh plugin --profile web add @caob23/dsh-browser-control@1.0.18 --registry=https://registry.npmjs.org/

如果撞 ERR_PNPM_NO_MATURE_MATCHING_VERSION 那是你机器上的 24 小时新包门槛,不是包坏了;两条的判别与三条绕法见 README。离线包就是下面这两个 zip。

扩展:解压 DSH-Browser-Control-v1.0.18.zip → chrome://extensions → 右上角开发者模式 → 「加载已解压的扩展程序」。旧版本必须先移除或点扩展卡片上的刷新,否则跑的还是 1.0.17 的代码(没有锁,键盘修复也一样带着)。

6. 资产校验和

资产 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.18.zip 53 789 B 8991fdf364d3939fe2952f01f015c2ce91cbeed603301b89f43dac6918332bc8 d25c5f415de2a767c55a4277fc07e08d
dsh-browser-control-plugin-v1.0.18.zip 189 500 B 3203b2369dc106afa309d528aabfaab402e3464e01b9663116b54cfb90b3b30e 7c5754224c2ee638d4be26a7ab66f64c

两份包都在发布前本地解包复验过(zip 头 504b0304、CRC 全过、无反斜杠条目、插件包含 cordis.patch.yml、扩展 manifest 里 version 为 1.0.18、权限仍是 debugger / tabs / tabGroups / storage / alarms),发布后又匿名重新下载逐字节比过一次。按本仓库惯例不放 .sha256 附件,校验和只写在这里。

v1.0.17 — 键盘双倍输入修复 + 批量关标签(每个 id 都有交代)

Choose a tag to compare

@angelky7 angelky7 released this 13 Oct 10:53

v1.0.17

三件事:browser_press 一次按键插入两次(表单被提交两遍)修好了、browser_tabs close 现在能一次关一批且每个 id 都有交代(合并 PR #11)、以及批量关闭的超时分类修正——合并进来之后我自己复审时发现的缺口。

这一版改了 extension/——扩展必须重装,只更新插件不够。

1. 键盘:一次按键只许插入一次(issue #14)

cmdPress 在 Input.dispatchKeyEvent 的 keyDown 已经带 text(Chrome 据此就已插入字符)之后,又补发一个独立的 char 事件——两处各插一次:

  • 输入框里按一下 x → 得到 xx
  • 按一下 Enter → 表单提交两次
  • browser_type 的 submit:true 走同一条通道,所以提交类操作会做两遍

同一个仓库里这个坑早就在 cmdInput 的 type 模式修过,注释就写着「an explicit char event duplicates the input」,CHANGELOG v1.0.2 也记了一笔——只有 cmdPress 没跟上,从 v0.2.0(be6c302)一路留到现在。取报告方(@wangyuncai2024)的方案 B:删掉 char 分支,与 cmdInput 一致(他的方案 A 改 rawKeyDown 保留 char,实测等价,但仓库里会同时存在两种写法)。

同一函数里另外两处也一并修了:

  • 组合键现在真能用:"Ctrl+A" / "Shift+ArrowRight",或 modifiers: ["ctrl"]。以前扩展层支持 modifiers 而工具层没暴露,写 "Shift+ArrowRight" 只是静默无效(不报错也不选中文本)。shift 单独存在时照常打字,ctrl/meta/alt 抑制插入。
  • 认不出的键名改为抛错,不再带着 undefined 的 keyCode/code 照样派发、再回一句 pressed:true 让调用方以为成功。

为什么以前测不出来:报告方的判断成立——verify/ 对 browser_press 只测过模型可见文本,整个仓库没有任何一条按键事件计数。新增 verify/verify_press_events.mjs(19 项)第一次按派发次数测:单字符 / Enter / 数字键 / type 模式逐字符都必须「插入型事件恰好一个、不得有 char」,外加组合键解析与未知键名零派发。这个台子可以传入另一份 background.js:对修复前的版本跑同一批断言红 12 项并如实报出 keyDown,char,keyUp,所以它不是跟着新代码写的空断言。

2. 批量关标签页(合并 PR #11,@long-arm-ape)

browser_tabs action:"close" 收 tabId(一个)或 tabIds(一批)。一批在链路上只发一条命令(扩展侧 tabs.closeMany → 一次 chrome.tabs.remove([ids])),不再 N 次往返中间掉线。结果统一为 {requested, closed, alreadyGone, failed:[{tabId,error}]},单数与批量同一个形状(cmdTabsClose 直接委托给 cmdTabsCloseMany)。以前的 for (id of ids) await tabs.close(id) 第一个失败就抛出,会把已经真的关掉的标签藏起来,模型以为什么都没发生于是重放整批。alreadyGone(早就没了)算幂等成功;并且不猜——remove 之后逐个复查,「还在」的才进 failed,被拒但其实已关掉的如实进 closed。

他们顺手挖出了我们自己的对账缺陷:verify_surface.mjs 的命令名正则 [a-z_.]+ 会把 tabs.closeMany 截成 tabs.close,新命令名进不了对账——已放宽。

3. 合并后的复审抓到的缺口:tabs.closeMany 没登记为写操作

effectState 是按命令名判写/读的。批量关闭是新命令名,没进写清单就会静默落到 not_started,于是超时报文会写着「effectState=not_started:这次调用没有动到页面,重试安全」——而实际可能已经关掉了一半标签。这正是本仓库这一路在修的那类不诚实,只不过这次出现在我刚合并进来的代码上。

修法:登记为写操作,并加一条对账护栏——从 extension/background.js 的 COMMANDS 表解析出全部 26 条命令,每条要么在写清单里,要么在测试侧显式登记为读/幂等并写理由;新增命令不做判断就直接红。护栏做过变异测试:临时摘掉 tabs.closeMany 的登记,两条断言立刻点名该命令并 FAIL。

关于 1.0.16

1.0.16 只存在于 npm(没有 tag、没有 Release、没有在任何地方推荐安装)。原因是发布流程走到一半——npm publish 之后、建 Release 之前——上面第 3 条缺口被发现,而 npm 的版本不可改写。为避免有人按确切版本号装到那个半修好的版本,已对 1.0.16 下 npm 弃用提示指向本版。请直接装 1.0.17。

测试台

npm run verify 331 → 350(键盘台)→ 381(PR #11 自带 31 项)→ 385 项断言,exit 0。CI 在合并提交上 ubuntu 与 windows-latest 两个 job 都 success。

还没验证的部分(说实话)

  • 键盘修复没有在真实 Chrome 上跑过。 断言来自「有状态假 chrome.debugger + 真实扩展代码」,测的是我们能控制的那一层(派发序列);「Chrome 收到带 text 的 keyDown 之后到底插几个字符」是浏览器的行为——报告方给了裸 CDP 三组对照支持方案 B,但那是他的环境。你重装扩展后请按一次 x 看是否只出一个字符、按一次 Enter 是否只提交一次;若仍翻倍,请在 #14 直接回我,我改按方案 A(rawKeyDown + char)。
  • 批量关闭的 closed / alreadyGone / failed 划分在假 chrome.tabs 上验过,真实 Chrome 里「remove 之后复查」的时序仍需一次实机确认。
  • 前面几版留下的:归组的视觉效果、AX 快照在真实页面上的表现与耗时(扩展升到本版后即可测 AX)。

资产校验和

不附 .sha256 文件,直接列在这里:

文件 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.17.zip 50969 B bb117b0f0f8c597540eb21e64916062d8db5f4d8e9a40dbae011b19efeb708e9 bcd59b1f2bdf7ff40d06e8191f72782a
dsh-browser-control-plugin-v1.0.17.zip 172353 B b774702ee1e4cb8c0ccf0a24bb198ff7e6b2830cce1910c8c66d504165faf64f 6dc197519825d8ea04a68d40fedfcbac

两份都验过:文件头 504b0304、条目全用正斜杠、CRC 全过;插件包含 cordis.patch.yml(dsh.bundle.patch 指向它),扩展 manifest 版本 1.0.17、权限 debugger, tabs, tabGroups, storage, alarms。

安装(两步都要做)

  1. 扩展:下载 DSH-Browser-Control-v1.0.17.zip → 解压 → chrome://extensions → 开发者模式 → 先移除旧版再「加载未打包的扩展程序」,确认版本显示 1.0.17。不重装扩展,键盘修复不会生效。
  2. 插件:dsh plugin --profile <你的profile> add @caob23/dsh-browser-control@1.0.17。刚发布 24 小时内可能撞 dsh 自带的包龄闸门,报 ERR_PNPM_NO_MATURE_MATCHING_VERSION 等满即可;报 ERR_PNPM_NO_MATCHING_VERSION 才是源没同步,那一次加 --registry=https://registry.npmjs.org/,或用上面的离线插件包。
  3. 自检:http://127.0.0.1:9777/ 状态页应显示已连接;在输入框让 agent 按一次字母,页面里只应多出一个字符;browser_tabs close 传 tabIds:[a,b] 时结果会把每个 id 的去向写出来。

v1.0.15 — 真实宿主×真实浏览器接入验证;旧扩展静默忽略 mode:ax 现在如实回报

Choose a tag to compare

@angelky7 angelky7 released this 12 Oct 15:09

v1.0.15

这一版的价值主要在验证方式上:第一次把真实宿主和真实浏览器接进验证链,而它当场挖出一个假扩展台子永远发现不了的缺陷——旧扩展会静默忽略 mode:"ax"。extension/ 代码没有改动(manifest 只是版本戳),老扩展不用升级就能受益于这次的修复。

1. 真实宿主 × 真实浏览器,第一次跑通

用本机已安装的 dsh(@deepseek-ai/dsh 全家 0.2.0-rc.2,版本号从 app.asar 里直接读出)在隔离的 DSH_HOME 里建 profile、装上插件、让真实 Chrome 扩展连上桥,然后把只读工具跑在真实代码路径上:

  • 宿主组装出的层:- id: browser-bridge / name: '@caob23/dsh-browser-control' / config: {enabled: true, port: 9777, token: dsh-local} —— issue #4 那条「装上即监听」第一次被真实宿主证实,而不是靠假 cordis 推断。
  • 顺带实测回答了一条含糊配置:profile 里只写 - id: browser-bridge + disabled: true(不带 name)就足以关掉本插件——宿主的合并按 id 认。
  • 真实 Chrome 153 + 扩展 1.0.10 下 9 条只读工具全过:browser_read 交出 1341 字符真实正文、browser_snapshot 交出 14 条带 [e1] a "首页" -> /apply/main.do 的清单、每条输出都零 schema 违规。这同时证明 1.0.12 那两个修复在真实链路上确实生效。

2. 挖出来的缺陷:旧扩展静默忽略 mode:"ax"

扩展 1.0.10(早于 1.0.11 引入的 AX 快照)收到 mode:"ax" 时不报错,回一份 DOM 结果,而且既不带 mode 也不带 axFallback——本机实测两次请求(ax 与默认)的响应完全一样。模型于是只看到 (mode dom),读不出「我要的 ax 被旧扩展降级了」,会把标签名当成可访问名,在按名字找元素时判断就错了。

修法全在插件侧:比对请求的模式 vs 回报的模式(mode === 'ax' 或带 ax 计数才算真走了 AX),不一致就自己写 axFallback:

(mode ax fell back to dom: this extension ignored mode:"ax" and returned CSS-DOM matches
 (AX mode needs DSH Browser Control extension v1.0.11 or newer) — the names below are tag names, not accessible names)

老扩展不升级也立刻受益;想要真的 AX 语义,把扩展升到 ≥ 1.0.11(本版 zip 即可)。补了 4 项断言:静默降级 / 真走了 AX 不许凭空报降级 / 扩展自带降级原因时以它为准 / 降级原因必须进得了模型可见文本。

3. 顺带纠正一条我自己的说法

ERR_PNPM_NO_MATURE_MATCHING_VERSION 与 #13 里的 ERR_PNPM_NO_MATCHING_VERSION 是两个不同的原因,本机实测区分开了:dsh 的 profile 自带 24 小时「包龄」供应链闸门(node_modules/.pnpm-workspace-state-v1.json 里 minimumReleaseAge: 1440),刚发布的版本被它拦下时报前者,报文里直接写 was published at …, within the minimumReleaseAge cutoff (…);而 NO_MATCHING_VERSION 是「你配置的源里没有这个版本」(镜像滞后)。README 与 CONTRIBUTING 现在把两条判据都写上了——两种都只能等,但只有后者换 registry 或离线包有用。

测试台

npm run verify 331 项断言 exit 0(v1.0.14 是 327),ubuntu 与 windows-latest 两个 job 都跑。

还没验证的部分(说实话)

  • 真实浏览器这一跑只覆盖只读命令;click / type / navigate / tabs.open(含归组)与写操作的 effectState 路径仍未在真实 Chrome 上验过——不是不想,而是不该在你的浏览器里留下副作用。
  • browser_emulate 这次仍然只有假 chrome.debugger 的证据(1.0.14 修的是输出契约层)。
  • 归组的视觉效果(颜色、分组条)与 AX 快照在真实页面上的耗时仍未实测:本机扩展是 1.0.10,压根没有 AX 通路,要看 AX 得先装 ≥1.0.11 的扩展 zip。

资产校验和

不附 .sha256 文件,直接列在这里:

文件 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.15.zip 49210 B 2c56a99cc64a5e6424ee3207d880febe86224922848021b429f09c6a38dfe915 0bb24bbf4de285fbbd10ea3d36b12c05
dsh-browser-control-plugin-v1.0.15.zip 166836 B 6420f1ed8922b0747d480df42a549e6c5d49c9f5c20bf13751bc21e7b0bb0f58 5497142b78c2c6168d012803973dd286

两份都验过:文件头 504b0304、条目全用正斜杠、CRC 全过;插件包含 cordis.patch.yml(dsh.bundle.patch 指向它),扩展 manifest 版本 1.0.15、权限 debugger, tabs, tabGroups, storage, alarms。

发布时的源状态(实测,别照着报错猜)

时间(UTC) 官方源 registry.npmjs.org 镜像 registry.npmmirror.com
2026-10-12 15:08 latest = 1.0.15(发布时间 14:53:49Z) latest = 1.0.14,还没有 1.0.15

也就是说到本 Release 建立时为止,镜像已经滞后约 15 分钟(历史上从 1 分钟到几小时都出现过)。如果你的 npm config get registry 指向 npmmirror,按下面安装方式第 1 条加 --registry=https://registry.npmjs.org/,或用离线包,或直接 git 通道(#v1.0.15,不经过 npm)。这一步我会在镜像跟上后回来更新这一节。

安装

  • 插件:dsh plugin --profile <你的profile> add @caob23/dsh-browser-control@1.0.15。刚发 24 小时内可能撞上包龄闸门(NO_MATURE_MATCHING_VERSION),等满 cutoff 即可;报 NO_MATCHING_VERSION 才是源没同步,那一次加 --registry=https://registry.npmjs.org/。
  • 离线兜底:解压 dsh-browser-control-plugin-v1.0.15.zip 后 dsh plugin --profile desktop add file:<解压目录>(完全不过 registry,也不受包龄闸门影响)。
  • 扩展:只在想要真 AX 快照时才需要升级到 ≥1.0.11(本版 zip 即可);不升也不会再被静默降级误导。
  • profile 里想关掉插件:- id: browser-bridge + disabled: true 就够(实测按 id 合并)。

v1.0.14 — emulate 的 null 覆盖不再打穿输出契约;Windows 进 CI

Choose a tag to compare

@angelky7 angelky7 released this 12 Oct 14:04

v1.0.14

browser_emulate 的一个必然失败修好了,CI 多了一道 Windows 门。extension/ 代码没有改动(manifest 只是版本戳),已装 1.0.12/1.0.13 扩展的人不用重装。

1. browser_emulate:不覆盖 UA 的调用以前每次必失败

扩展在无覆盖时回的是显式 userAgent: null(extension/background.js:719,userAgent: userAgent || null,而 desktop 预设的 UA 本来就是空串),插件这边的 output.schema 声明的是 userAgent: {type:'string'}。宿主的 checkValue 只对 undefined 放行(!hasOwn(value,key) || value[key] === void 0 才跳过),对 null 会下钻判类型,于是:

  • device:"desktop" → 每次抛 ToolOutputError
  • 只给 width/height 的自定义视口 → 每次抛 ToolOutputError
  • device:"mobile-iphone-13" / mobile-pixel-7 / tablet-ipad(预设里带真 UA)→ 正常,所以一直没暴露

用宿主自己的校验器实测这条:旧行为 ["\"value.userAgent\" must be a string"],归一化后 []。这条从 v1.0.7 坏到 v1.0.13,和之前那两条(browser_navigate 少声明 siteUnreachable、两条日志工具的数组项)是同一个缺陷家族,只是这次藏在 null 上。

改法全在插件侧(扩展不用重装):null 与空串一起剥掉,其余字段按声明类型逐条归一。另外 UA 真的被覆盖时,UA="…" 现在会写进给模型的那句话——以前它只待在进不了模型的 value 里,模型只知道视口、不知道浏览器身份被改了。

2. 审计台补了 null 的通用护栏

verify/verify_render_contract.mjs(110 → 126 项)现在对全部 11 条封闭 schema 工具递归断言 value 里不得出现任何 null——出现即判失败,不必等下一个真实载荷踩到。加上 desktop / 自定义宽高 / 带 UA / reset 四种形状各一条用例。npm run verify 311 → 327 项,exit 0。

3. Windows 进 CI 了

这个插件的实际宿主平台是 Windows(用户和开发者都在 Windows 上跑 dsh),但 CI 以前只有 ubuntu:换行符、path.join/resolve、临时目录、回环绑定这些差异一个都拦不住。新增的 windows-latest job 跑:安装 → 类型检查 → 重新构建 → 「lib/ 必须与源码同步」→ publint → 327 项实测台。首跑十步全 success(56 秒)。

配套加了 .gitattributes 把仓库锁成 LF:不换行的话那道同步门在 Windows runner 上是必然假红(本机 core.autocrlf=true 一直刷的「LF will be replaced by CRLF」是同一个根因)。加完 git status 只多一个新文件,说明索引里本来就是 LF,这次不产生 renormalize 噪声。

还没验证的部分(说实话)

  • 「browser_emulate 在真 Chrome 上真的把视口/UA 切过去了」仍然只有 node:vm + 假 chrome.debugger 的证据;这版修的是输出契约层的必然失败,不是 CDP 行为本身。
  • Windows CI 跑通不等于所有 Windows 用户机器跑通:runner 的 Node 24 / Git / 路径长度限制与你本机环境不同。
  • 前几版留下的两项照旧:归组的视觉效果、AX 快照在真实页面上的表现与耗时。

资产校验和

不附 .sha256 文件,直接列在这里:

文件 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.14.zip 49209 B 87a84dbe4d5dabfd059c1172511a4545741887f7f620d7b6f88c59cd0913a557 981af910f5b532cf449bcb6491f57c71
dsh-browser-control-plugin-v1.0.14.zip 164151 B 03f4974693127b814086263e99d4aab95c7d78a58f4eda33b17184a13d951ade 3521a9167ede29ec961e54a34aebb21c

两份都验过:文件头 504b0304、条目全用正斜杠、CRC 全过;插件包含 cordis.patch.yml(dsh.bundle.patch 指向它),扩展 manifest 版本 1.0.14、权限 debugger, tabs, tabGroups, storage, alarms。

安装

  • 插件(在线):dsh plugin --profile <你的profile> add @caob23/dsh-browser-control@1.0.14。若你的 npm 源是滞后镜像(registry.npmmirror.com),加 --registry=https://registry.npmjs.org/;这个「解析与安装吃不同源」的问题我已在上游开单:dshplugin/dsh-plugin-hub#175。
  • 插件(离线兜底):解压 dsh-browser-control-plugin-v1.0.14.zip 后 dsh plugin --profile desktop add file:<解压目录>。
  • 从仓库装:dsh plugin --profile web add "github:caob23/dsh-browser-control#v1.0.14"。
  • 扩展:不用动。
  • 想验证这版修好了:让 agent 跑 browser_emulate device:"desktop"——v1.0.13 及以前会报输出校验错误,这版应返回 Tab N: 1280×800 …。

v1.0.13 — 失败文案把「该不该重试」交给模型

Choose a tag to compare

@angelky7 angelky7 released this 12 Oct 13:19

v1.0.13

一件事:错误契约里「该不该重试」那半句,以前模型一个字都看不到。 另外把「模型可见内容」这台审计铺满全部 20 条工具。extension/ 代码没有改动(manifest 只是版本戳),已经装着 1.0.12 扩展的人不用重装。

1. 失败文案现在自带决策依据

宿主把异常渲染成模型可见内容时只取一句 Error: ${message}(@deepseek-ai/dsh-tools lib/index.js:3616-3623),而 code / effectState / hint 只进 error.info 与 HTTP 面。也就是说 v1.0.9 设计「写操作超时不许盲目重试」这个信号时,唯一的接收方(模型)拿不到它,只能凭一行 browser command timed out after 800ms: click 猜该不该重试。上一版修的「render 只写摘要」是同一个缺陷,只是发生在失败路径上。

现在同一个失败,模型看到的是:

Error: [command_timeout] browser command timed out after 800ms: click — effectState=unknown:
动作可能已经落到页面上,先用只读工具看当前状态,再决定要不要重做,不要直接重试。
下一步:扩展超时未回应:先别重试。用 browser_find / browser_snapshot 看页面当前状态,判断动作是否其实已经生效。
  • 写操作(click / input / nav / tabs.open / tabs.close / eval…)超时或在途掉线 → effectState=unknown + 明确的「先观察再重做」。
  • 只读操作(screenshot / content / find / 日志…)任何失败 → effectState=not_started(重试安全)。
  • 标签归组仍按可重复操作处理(再归一次是空操作),所以它落在 not_started 一侧。
  • HTTP 命令面的形状没变:新增 detail 字段承载原始失败描述,toJSON().error 仍是那句原话,hint 不会在 error 里重复出现。

2. 模型可见内容审计铺满 20 条工具

新增一张地面真相表:每条工具都用 extension/background.js 真实回传的形状当载荷,同时过两关——过宿主自己的 validateJsonSchemaValue,并且模型可见文本必须命中这次调用真正要的片段([e41]、页面正文、DIALOG-GT、412×915、PDF 路径…)。截图 / PDF / browser_cleanup 会真写文件,所以 shotsDir 指到系统临时目录,跑完删掉。

这一轮没有再挖出新缺陷——剩下 11 条本来就合格。价值在广度:抽查 9 条意味着漏掉的 11 条里下一个人还会踩,而这类缺陷上一次是「活了 12 个版本没人发现」。

测试台

npm run verify 311 项断言 exit 0(v1.0.12 是 242)。CI 在 f31421e 上:类型检查、lib/ 与源码同步、publint、311 项实测台全绿;只有「文档里的 release 下载链接不得为死链」这一步在建成本页 Release 之前必然红(它检查的就是这些链接),建好 Release 后重跑为绿。

还没验证的部分(说实话)

  • 新的失败文案还没在真实 dsh 宿主里跑过一次:Error: ${message} 这条渲染路径是从 @deepseek-ai/dsh-tools 源码实测出来的(含行号),但「模型读了这句话之后真的改变行为(先去 browser_find 而不是直接重试)」只能靠实机观察。
  • 20 条工具的地面真相表用的是从扩展源码抄下来的载荷形状,不是真 Chrome 吐的字节。字段名与可选性以 extension/background.js 当前实现为准;真页面上多出来的字段仍可能触发封闭 schema 违规——这正是 browser_console_log 那次的教训,所以这条限制写在这儿而不是藏起来。

资产校验和

不附 .sha256 文件,直接列在这里:

文件 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.13.zip 49210 B 4a68e45acea1105dce2fe35d681a756d0b5d5cbcd2b14d2f91be157ffd11be8d eba0497dc8dc24ecf117ec4552220587
dsh-browser-control-plugin-v1.0.13.zip 162759 B 8b8f79fa36c88fa87d92e925c0b75e0ee9d679b2f7abe657d9b83eefb04fbfc1 90f59c6f2f6beccef2e954045ab1906f

两份都验过:文件头 504b0304、条目全用正斜杠、CRC 全过;插件包含 cordis.patch.yml(dsh.bundle.patch 指向它),扩展 manifest 版本 1.0.13、权限 debugger, tabs, tabGroups, storage, alarms。

安装

  • 插件(在线):dsh plugin --profile <你的profile> add @caob23/dsh-browser-control@1.0.13;若你的 npm 源是滞后镜像(registry.npmmirror.com),加 --registry=https://registry.npmjs.org/。
  • 插件(离线兜底):解压 dsh-browser-control-plugin-v1.0.13.zip 后 dsh plugin --profile desktop add file:<解压目录>。
  • 从仓库装:dsh plugin --profile web add "github:caob23/dsh-browser-control#v1.0.13"。
  • 扩展:不用动。要新装才下载 DSH-Browser-Control-v1.0.13.zip(解压后在 chrome://extensions 开发者模式加载)。
  • 装完打开 http://127.0.0.1:9777/ 看链路状态;验证是否装到 1.0.13:故意让一次 browser_click 超时,返回文本里应出现 effectState=unknown。

v1.0.12 — 模型可见内容契约(正文 / ref / 日志条目真的给到模型)

Choose a tag to compare

@angelky7 angelky7 released this 12 Oct 12:38

v1.0.12

这一版只修一件事:模型到底看到了什么。结果是四个工具自 v1.0.0 起从来没把自己要取的数据交给过模型,另外两条日志工具只要有条目就必然调用失败。无破坏性变更:命令名、帧契约、配置形状都没改,扩展代码一个字没动。

1. 根因:render() 才是模型能看到的全部内容

宿主实测(@deepseek-ai/dsh-tools):成功结果里 content = snapshotProjection(tool.output.render(args, value))(lib/index.js:3548-3551),而 canonical value 是 execution-local 的,注释明写 "deliberately omitted from durable events"(lib/types/index.d.ts:415),presentationMeta 只喂 UI。render 少写一个字段,模型就永远看不到那个字段。

以前的 browser_read 只交出一句 Title (url) — 4321 chars,browser_snapshot 只交出 12 interactive elements …; click or fill them by ref.(一个 ref 都没给,而系统提示词教的正是「先快照、再按 ref 点」),两条日志工具只给计数。数据一直躺在模型看不见的 value 里。

2. browser_read 现在是窗口式读取

正文进模型可见文本,同时加 offset / limit:单次默认与上限 20 000 字符,头部直接写 chars 0-20000 of 43210; call again with offset=20000,value 带 offset / totalChars。一次调用不会把整站灌进上下文,长页面靠 offset 走 —— 也顺手消掉了旧设计里「value 留到 120 000 字符但模型一个字也看不到」这种两头不靠的状态。

3. browser_snapshot 把清单本身给出去

[e13] button "Save" -> /profile 逐条列出(AX 模式给 role + 可访问名,DOM 模式给 tag),8 000 字符预算,超出时截断;mode:"ax" 降级回 DOM 的原因(axFallback)也一起写进文本,agent 据此知道这次的名字来自哪里。ref 用法完全照旧。

4. 两条日志工具:有条目就失败,且正文从没给过模型

browser_console_log / browser_network_log 的 items 声明是 {type:'object', additionalProperties:false, properties:{}}。宿主 checkValue(lib/index.js:468-470)对 additionalProperties:false 是逐键判违规,properties 为空 = 任何键都违规;用宿主自己的 valueSchemaSpecToJsonSchema 实测编译后这个 items 原样保留。所以 count>=1 的每一次调用都会抛 ToolOutputError —— 它活了五个版本没被发现,因为「日志正好是空的」恰好是最常测到的路径,而空数组合法。

现在按扩展真实回传的字段声明行形状,execute 归一化(console:level / text / time / location;network:method / url / status / resourceType / bytes / time / failed / errorText 等),render 逐条列出,时间统一 UTC 秒,count < total 时明说「只给了最新的 N 条」。headers / responseHeaders / postData 不再进结果:它们到不了模型,只会把载荷变大。

5. 截断信号改成机器可读(+ 三处漏网的硬切片)

超出预算时,切片之后追加一行稳定标记:

… [renderedTruncated omittedChars=8036 limitChars=4000] narrow the query and call again to see the rest

标记在切片之后拼上,所以它必然出现在模型看到的文本里;数字实测对齐。看到它的正确反应是缩小查询(limit / pattern / urlPattern / offset),不是假设页面就这么长。同时补掉 v1.0.11 那次清理漏掉的三处 JSON.stringify(value).slice(0, 200|600|1200)(browser_wait / browser_find / browser_dialog)—— 它们此前仍会把 JSON 从 token 中间切断。

6. 扩展不用重装

extension/ 的代码一行没改(manifest.json 只是版本戳跟着走),协议仍是 protocol: 1,老扩展配新插件照常工作。要更新的只有插件侧:dsh plugin add @caob23/dsh-browser-control@1.0.12,或下面那份离线插件包。

还没验证的部分(说实话)

  • 这批改动第一次让真实数据进模型,所以「模型拿到页面正文 / ref 清单 / 日志条目」这件事本身还没有在真实 dsh 宿主 + 真实 Chrome 里跑过端到端。242 项断言是在真桥 + 假扩展上跑的,载荷形状取自 extension/background.js 的真实返回。
  • 20 000 字符的读取窗口、8 000 / 6 000 的列表预算是我按 token 成本定的经验值,没有实测过 DeepSeek 侧的实际表现;如果一次读下来上下文吃紧,把 limit 调小即可。
  • 上一版仍未验证的两项照旧:归组的视觉效果、AX 快照在真实页面上的表现与耗时。

测试台

新增 verify/verify_render_contract.mjs:真 BridgeServer + 假扩展 + 宿主自己的 validateJsonSchemaValue / valueSchemaSpecToJsonSchema,把每条工具的真实返回值同时喂给它的输出 schema 和它的 render,逐条问「schema 过不过」「模型可见文本里有没有这次调用真正要的数据」「超预算时 [renderedTruncated] 的数字对不对」。这一层以前没有任何测试覆盖,所以两类缺陷分别活了 12 个(v1.0.0–v1.0.11)与 5 个(v1.0.7–v1.0.11)已发布版本。npm run verify:191 → 242 项断言,exit 0;CI run#16、run#17 十步全 success(顺带把 CI 里匿名调 GitHub API 的死链审计改成带令牌 —— 那条红是配额 403,不是死链)。

资产校验和

不附 .sha256 文件,直接列在这里:

文件 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.12.zip 49210 B 64255a1e82616b4b03b63fefd299029fe39e1713efee12e55d24c37dffa39d5d 628400eccf3f26e415ef3ed2be52e483
dsh-browser-control-plugin-v1.0.12.zip 161066 B cf3f8103c7c21a0fae9c09bfeb1fbc69d5006529d38a62b57553b08a27368a64 f76a5f28228a91591fbd966d6d7589f5

两份都验过:文件头 504b0304、条目全用正斜杠、CRC 全过;插件包含 cordis.patch.yml(dsh.bundle.patch 指向它),扩展 manifest 版本 1.0.12、权限 debugger, tabs, tabGroups, storage, alarms。

安装

  • 插件(在线):dsh plugin add @caob23/dsh-browser-control@1.0.12;若你的 npm 源是滞后镜像(registry.npmmirror.com),加 --registry=https://registry.npmjs.org/。
  • 插件(离线兜底):解压 dsh-browser-control-plugin-v1.0.12.zip 后 dsh plugin --profile desktop add file:<解压目录>。
  • 从仓库装:dsh plugin --profile web add "github:caob23/dsh-browser-control#v1.0.12"。
  • 扩展:已经装着 v1.0.11 的不用动;新装才需要 DSH-Browser-Control-v1.0.12.zip(解压后在 chrome://extensions 开发者模式加载)。

v1.0.11

Choose a tag to compare

@angelky7 angelky7 released this 12 Oct 11:40

v1.0.11

四个方向一起进:agent 开的标签看得见、快照换成语义定位、导航不再假报成功、装机元数据修好。外加合了三个社区 PR。无破坏性变更:命令名、帧契约、配置形状都没改。

1. agent 新建的标签自动归组

Agent 用 browser_tabs open 打开的标签会收进当前窗口的 DSH 蓝色分组,一眼能分辨哪些页面是机器开的。

  • browser_tabs 新增 group / ungroup:ungroup 带 windowId 只拆那个窗口,不带则所有窗口都拆;只解除归属,不关任何标签页。
  • open 支持 group: false 跳过归组;group 能把早就存在的散标签拉进来。
  • 三条实测出来的约束:刚 tabs.create 立刻 tabs.group 会跟 Chrome 的标签簿记打架,所以 50/150/400ms 退避、最多 4 枪封顶;groupId 只在内存里,service worker 重启或用户手动拆组后按标题反查重建,绝不往已失效的 id 上塞标签;归组失败绝不让「打开标签」失败,只在结果里附 grouped:false 与原因。
  • manifest 加 tabGroups(Chrome ≥ 89 具名权限,不读网页内容)。没有加 host_permissions:「控制期间地址栏显示『正在被调试』」这条边界保持原样。

2. browser_snapshot 的 AX 语义模式

mode:"ax" 走 Chrome 的 Accessibility.getFullAXTree,role 与 name 是浏览器算好的可访问名(aria-label、<label>、alt、可见文本合成),比让 agent 猜 CSS 选择器抗改版。ref 契约一点没变:AX 节点经 DOM.resolveNode + Runtime.callFunctionOn 找到真实元素后写的还是同一个 data-dsh-ref,browser_click / browser_type 的 ref 用法照旧,默认仍是 DOM 模式。四条硬规则都有断言:只收可操作角色白名单(GenericContainer/StaticText/InlineTextBox 一律滤掉,否则 limit 会被文本节点吃光);隐形元素不占 ref 号也不写属性;AX 报了节点而 DOM 已没有 → 记 ax.unresolved;取树之前先清旧 ref。AX 域拿不到时降级回 DOM 并回报 axFallback,不让整条命令失败。

3. 导航不再假报成功(含一个发布版里的线上缺陷)

  • 等待器改在触发加载之前挂上,并坚持看到新文档:此前一次还没开始的导航会 0ms 返回、把上一个文档的 URL 当成功报出去(实测)。
  • 两个 CDP 探针都上了预算:导航未 commit 时 CDP 不答话,此前 browser_navigate 会挂死远超自己的 timeoutMs(实测 >8s,作者实测 40s)。
  • output.schema 是封闭的却没声明 siteUnreachable,扩展发的又是对象 {reason} —— 拿宿主自己的校验器实测:"value.siteUnreachable" is not a declared property (additionalProperties: false),也就是说装 1.0.10 的人导航到不可达站点时,模型收到的是输出校验错误而不是「站点不可达」。现在字段声明齐了、{reason} 归一成 siteUnreachable:true + 原因,loadPending / pendingUrl 也能到模型眼前。
  • 再补一刀:上面那版仍会把「导航压根没发生」(链接变成下载、目标被 Chrome 拒载)在 1.2 秒宽限后报成成功;现在宽限期后比对落到的 URL vs 要去的那个(忽略 hash 与末尾斜杠)。两个不误报方向都测了:导航到已经开着的同一地址仍算成功,真重定向走不到这条分支。

4. 装机元数据

  • peer 范围两个读者语义相反(宿主门禁拿宿主版本 + includePrerelease;pnpm 拿包的版本列表、不带),此前 >=0.1.0-rc.1 <0.3.0-0 这种写法过得了门禁却只能解析到 0.1.0-rc.8 那代。现在 0.2.0-rc.2 || >=0.1.0-rc.1 <0.3.0-0:精确支当锚点、宽支保兼容。
  • 删掉 engines.node(官方 dsh 插件一个都没写,宿主门禁也不读它)。
  • @deepseek-ai/schemastery 按官方约定放回 dependencies: ~3.18.4;JsonValue 改本地声明(0.2 的 dsh-tools 不再导出它)。

5. 合并的社区改动

#7 CDP 会话失效缓存(DevTools 抢会话后能重连)、#10 模型可见内容不再被 300/400 定长切片砍断、#12 导航诚实性。谢谢 @BeiWay1145 与 @long-arm-ape。#9、#11 还在评审中(理由写在 PR 里)。

还没验证的部分(说实话)

  • 归组的视觉效果(颜色、标题、折叠态)与 AX 快照在真实页面上的表现/耗时 都只在 node:vm + 有状态假 chrome.* 上测过,没进真实 Chrome 复验。装上新扩展后如果发现分组不出现,先确认 chrome://extensions 里版本号是 1.0.11、popup 里权限含 tabGroups。
  • CI 全绿是本机之外的第二双眼睛,不等于真实浏览器验证。

测试台

npm run verify 191 项断言 exit 0:新增 verify_tab_groups(23)、verify_extension_e2e(真桥 + 真扩展代码 + 真 WebSocket,10)、verify_peers(34)、verify_cdp_retry(7)、verify_snapshot_ax(18)、verify_nav_report(PR #12 带来)、verify_nav_landing(10)。CI 的「重新构建后 lib/ 必须与提交一致」这道门也在 runner 上真跑过了。

资产校验和

不附 .sha256 文件,直接列在这里:

文件 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.11.zip 49210 B 8fe9c27434d0c6dfc66045791bc63f6ebd241bca4fe31b9f6e273e9c0ca8cd01 551fb18bfeb7d5021a80f8e2397dd030
dsh-browser-control-plugin-v1.0.11.zip 155170 B c02215e4ad8fc97ca672a7dc72121e519e8e0cc18b7292703acf6ee102bc7f00 7214b90a57934ddd1d1e2b89fd602d7b

两份都验过:文件头 504b0304、条目全用正斜杠、CRC 全过;插件包含 cordis.patch.yml(dsh.bundle.patch 指向它),扩展 manifest 版本 1.0.11、权限 debugger, tabs, tabGroups, storage, alarms。

安装

  • 扩展:下载 DSH-Browser-Control-v1.0.11.zip,解压 → chrome://extensions → 开发者模式 → 先移除旧版再「加载未打包的扩展程序」。这次改了 extension/,旧扩展必须重装。
  • 插件:dsh plugin --profile <你的profile> add @caob23/dsh-browser-control@1.0.11。若你的 npm 源是镜像(国内默认常见 registry.npmmirror.com),新版可能还没同步过去,会报 ERR_PNPM_NO_MATCHING_VERSION——那就这一次显式走官方源:加 --registry=https://registry.npmjs.org/,或直接用上面的离线插件包 add file:<解压目录>。
  • 装完打开 http://127.0.0.1:9777/ 看链路状态(扩展图标应为绿色 ON)。

v1.0.10

Choose a tag to compare

@angelky7 angelky7 released this 11 Oct 12:03

v1.0.10

两件事:agent 开的标签页从此看得见,以及插件市场装不上时所暴露的插件侧元数据病根修掉了(#13)。无破坏性变更——命令名、帧格式、配置形状都没改,老扩展、老配置照常工作。

1. agent 新建的标签自动归组

Agent 用 browser_tabs open 打开的标签页,现在会收进当前窗口的一个 DSH 蓝色标签分组,你扫一眼就知道哪些页面是机器在开、哪些是自己开的,事后也能一把拆掉。

  • browser_tabs 新增 group / ungroup 两个动作:ungroup 带 windowId 只拆那一个窗口,不带就所有窗口都拆;只解除归属,不关任何标签页。
  • open 加 group: false 可以让新标签留在组外;group 能把早就存在的散标签拉进来。
  • 三条实测出来的硬约束:刚 tabs.create 立刻 tabs.group 会跟 Chrome 自己的标签簿记打架,所以按 50/150/400ms 退避、最多 4 枪封顶;groupId 只存内存,service worker 重启或用户手动拆组后按标题 DSH 反查重建,绝不往已失效的 id 上塞标签;归组失败绝不让「打开标签」这件事失败,只在结果里附 grouped:false 与原因。
  • extension/manifest.json 增加 tabGroups(Chrome ≥ 89 的具名权限,不涉及读取网页内容)。没有顺手加 host_permissions——「控制期间地址栏显示『正在被调试』、所有操作实时可见」这条边界保持原样。

⚠️ 视觉部分未在真实 Chrome 复验:归组的 23 项断言全部跑在 node:vm + 一台有状态的假 chrome.tabs/chrome.tabGroups 上。建组、复用、退避、脏缓存、拆组、无权限降级这些行为是有凭据的,颜色和分组条长什么样要装上新扩展才看得见。

2. 安装元数据修复(issue #13 的插件侧部分)

同一条 peerDependencies 范围其实有两个读法完全相反的读者:

读者 拿什么去匹配 带 includePrerelease 吗
宿主兼容门禁 @deepseek-ai/dsh-app-boot(lib/index.js:294-300) 宿主版本号 带;不过就 throw,整个插件 bundle 被跳过
pnpm / npm 包自己的已发布版本列表 不带

而 @deepseek-ai/dsh-* 这一族没有任何稳定版(latest 停在 0.0.1-rc.1)。v1.0.8 写的 ">=0.1.0-rc.1 <0.1.0 || >=0.1.0-rc.1 <0.3.0-0" 在门禁那一关全过,但包管理器最大只能解析到 0.1.0-rc.8(实测:npm view "@deepseek-ai/dsh-settings@<该范围>" version 只吐 0.1.0-rc.*)。后果是仓库自己的锁文件把宿主 API 钉在半年前那代,历次 typecheck 校的都是旧类型;pnpm 默认 auto-install-peers=true,干净目录安装会真拉一整棵 0.1.0 时代的依赖树。

反过来只钉精确版 "0.2.0-rc.2" 会死在另一头:dsh 0.1.7 / 0.2.1 宿主被门禁判不兼容。所以最终形态是两条并列:

"@deepseek-ai/dsh-tools": "0.2.0-rc.2 || >=0.1.0-rc.1 <0.3.0-0"

精确支给包管理器当锚点(实测 pnpm why 现在拿到 0.2.0-rc.2 / cordis 4.0.4),宽支让四代宿主都过门禁。另外:@deepseek-ai/schemastery 按官方插件的约定放回 dependencies: ~3.18.4;删掉了 engines.node(宿主门禁根本不读它,官方四个插件一个都没写);JsonValue 改为本地声明——钉回真宿主版本后 tsc 才第一次报出 dsh-tools@0.2 不再导出这个类型。

没说谎的部分:这一条不等于 #13 已经修好。旧范围并不会让 pnpm 报错(它能解析到 0.1.0-rc.8,本机安装已发布的 1.0.8 实测 exit 0),而报告标题里那句 “Cause: plugin-side install failure” 是 dsh-plugin-hub classifyFailure() 对任何没匹配上的失败的兜底标签;dsh plugin add 又是继承 stdio 跑的 pnpm,pnpm.log 里并不会包含 pnpm 自己的输出。定案还需要报告者的日志与他的 registry 配置。

3. 测试台又补了一层

  • verify_extension_e2e.mjs:真的 BridgeServer + 真的 extension/background.js + 真的 WebSocket(只有 chrome.* 是假的)。断言 hello 的 protocol 被认下、HTTP 命令面的 tabs.open/group/list/ungroup 确实落到扩展的 COMMANDS 表并原样回来、扩展抛的错变 502 + command_failed + effectState。
  • verify_peers.mjs:直接用宿主同款 semver 把上面两张表都跑一遍(34 项)。注入旧的宽范围报 6 项 FAIL,注入纯精确钉版报 9 项 FAIL——这个台就是拦下上面那次回退的。
  • verify_surface.mjs 开始对账 llms.txt 里的工具数量(它写着 16,实际 20,同一类漂移的第三个藏身处)。
  • npm run verify 共 144 项断言,exit 0;CI 里“重新构建后 lib/ 与提交不一致就 fail”那道门在 runner 上真跑过了。

资产校验和

不附 .sha256 文件,直接列在这里:

文件 大小 SHA-256 MD5
DSH-Browser-Control-v1.0.10.zip 44705 B 8cc17010fa73af7a83d8669f905c114950904ff3c4b588e39ead550362ae4037 4b4cc2c63d3cd80d788ebac509f99871
dsh-browser-control-plugin-v1.0.10.zip 143139 B 3beba6a8e7dc226444539c6cc8b6288d9e4c8274ff73b7fcfe245720e09fb83b 8472119c91df827b1a2b9489b64d136d

两份都验过:文件头 504b0304(PK\x03\x04,资源管理器可直接展开)、条目全用正斜杠、CRC 全过;插件包内含 cordis.patch.yml(package.json 的 dsh.bundle.patch 就指向它)。

安装

  • Chrome 扩展:下载 DSH-Browser-Control-v1.0.10.zip,解压后在 chrome://extensions 开开发者模式→「加载已解压的扩展程序」。已经装过旧版的请重新加载一次,本次改了 extension/。
  • dsh 插件:dsh plugin add @caob23/dsh-browser-control(npm 同步发布 1.0.10)。
  • 装完打开 http://127.0.0.1:9777/ 看链路状态;扩展图标应为绿色 ON。