dsh-debug-web — 让 agent 在页面里执行 JS、读控制台
DSH 官方给模型的客户端感知只有只读 Inspect:能读槽位树、主题令牌、Service/Event 契约,读不到 DOM、读不到控制台、不能执行代码。本插件补的就是这一半:一个模型工具 dsh_debug_web,请求浏览器页面执行 JavaScript 并取回结果,同时把页面自己捕获的 console/异常缓冲交给模型。
它不新造协议:请求走的是 DSH 已有的 Cordis Host→Client Inspect 通道(cordis/inspect-query),本包只在两端各挂一层——宿主侧一个工具,页面侧一个 Inspect Provider。
| 文档 | 只写 | 不写 |
|---|---|---|
| README(本文) | 怎么装、怎么用、参数口径、排错、验证、边界 | 设计取舍、给 agent 的约束 |
| AGENTS.md | 给 agent 的硬规则与改动连带项 | 用户用法 |
功能
模型能力(一个工具,四个动作)
| action | 作用 | 必填 |
|---|---|---|
pages |
列出响应页面:pageId、URL、标题、视口、可见性、已捕获条数 | — |
eval |
在页面里执行表达式或语句体,支持 await,返回 JSON 值 |
code |
logs |
读页面捕获的 console/异常缓冲,可按 since / level / limit 切片 |
— |
clear |
清空缓冲并返回清掉多少条 | — |
页面能力(客户端半边自动做,不需要用户操作)
- 包一层
console.log/info/warn/error/debug,保留原输出。 - 挂
window.onerror(捕获阶段,含<script>/<img>资源加载失败)与unhandledrejection。 - 300 条环形缓冲,单条正文截到 2000 字符,序号单调递增,不落盘、不发送。
不提供:截图、网络瀑布、DOM 快照接口、宿主文件访问。这些不在本插件的通道里(见已知边界)。
快速开始
1. 装插件
方式一:本地目录(开发时用这个)
dsh plugin --profile web add link:/data/ai/dsh/dsh_debug_web
装成 link: 依赖,改代码立刻是「已装的那份」。
方式二:命令行 · git 源(分发)
dsh plugin --profile web add github:dongasai/dsh_debug_web
dsh plugin --profile web add github:dongasai/dsh_debug_web#0.1
仓库地址 https://github.com/dongasai/dsh_debug_web,#0.1 钉在已推送的 tag 上;tag 被改动或删除后这条会装失败。
方式三:Web 界面
DSH Web → 左侧栏插件 → 添加插件 → 填本仓库绝对路径 → 安装 → 点立即启用。
四条通用坑:
--profile必填,不写直接报required option '--profile <name> not specified'。- 只加依赖不加
dsh.profile.bundles= 装了不挂载(本包带dsh.bundle,装完会自动进 bundles)。 - git 规格必须
github:/git+,git@…会被 pnpm 当本地路径,装出死链。 - 钉版本的 tag 必须真实存在。
2. 让两半各就各位
| 改了哪半 | 怎么生效 |
|---|---|
client.js(页面侧 Provider) |
刷新浏览器页面即可,不需要重启 Harness |
index.js / cordis.patch.yml / package.json 的 dsh.* |
重启 dsh web |
装完第一次必须重启一次:工具是在宿主启动合成插件行时注册的。
3. 确认它在工作
dsh --profile web --dump-config | grep -A1 dsh-debug-web
期望:
- id: dsh-debug-web
name: '@dongasai/dsh-debug-web'
重启后让模型调一次 dsh_debug_web,action=pages。成功的样子:
响应页面 1 个(查询广播到所有已连接页面,宿主只采纳第一个有效结果):
- pageId=page-xxxxxxxx visible 1440x900 Harness <http://127.0.0.1:3080/...>;已捕获 0 条,缓冲区 0 条
看不到工具 → 宿主半边没挂上(重启);工具报 provider-missing → 页面还是旧的客户端半边(刷新)。
结构
| 文件 | 作用 |
|---|---|
| index.js | 宿主半边:注册 dsh_debug_web,把 action 转成 Inspect 查询并渲染结果 |
| client.js | 页面半边:捕获 console/异常,注册 DebugWeb provider 执行 eval |
| cordis.patch.yml | 宿主层装配行(- id: dsh-debug-web) |
| package.json | dsh.bundle + dsh.client(platform: web、immediately: true) |
Host inject:tools、cordisInspect。后者由 @deepseek-ai/dsh-cordis-host-runner 提供,缺失时本插件不会激活(没有别的通道可用)。
占用的扩展点:
| 扩展点 | 注册物 |
|---|---|
| 宿主工具 | dsh_debug_web(全局层,本 profile 每个 agent 都可见) |
| 客户端 Inspect Provider | id DebugWeb,方法 pages / logs / eval / clear |
槽位、页签、路由都不占——本插件没有界面。
通道契约
一次调用的完整路径:
模型 → dsh_debug_web(index.js)
→ ctx.cordisInspect.query('client', 'DebugWeb', <method>, <input>, agent, signal)
→ 宿主广播 cordis/inspect-query 给所有已连接页面
→ 每个页面的 DebugWeb provider 执行,第一个有效结果被采纳
→ 结果校验(无损 JSON + outputSchema)后原路返回
action → method 一一对应。input 只在下面这些字段里取值,多传未声明字段会被宿主 validateInput 拒绝(additionalProperties: false):
| method | input | 说明 |
|---|---|---|
pages |
无 | 空对象即全部省略 |
logs |
since? level? limit? |
since 是 seq;limit 默认 100、上限 300 |
eval |
code(必填) pageId? |
pageId 不匹配的页面直接拒绝本次查询 |
clear |
pageId? |
返回 cleared 条数 |
共通口径:
- 超时:宿主默认等 10000ms(
cordis-host-runner的clientInspectTimeoutMs),页面侧eval自己 3000ms 先超时,所以模型拿到的通常是可读的错误而不是"通道超时"。 - 首答优先:查询是广播,所有已连接的 Harness 页面都会执行,只有第一个有效结果被采纳,其余被取消(
cancelled)。 - 错误口径:页面 provider 抛错 →
provider-error: <message>;方法不存在 →method-missing;页面没连 →has no connected Harness page。
安全模型
eval等于页面同权:它在用户的浏览器标签页里以该页面的权限执行,能读写 localStorage、发同源请求、改 DOM。想安全就只用只读表达式。- 通道里没有审批闸门:
cordis_inspect_query与 Inspect 通道本身不做二次确认;本插件也没加。要不要给eval加审批是使用者的选择,不是内建保证。 - 信任围栏只有"本机 + 已连接页面":谁能操作这个 Harness 页面,谁就能让模型在页面里执行代码。它不是沙箱,也不隔离同源资源。
- 缓冲不离开页面:捕获的日志只存在页面内存里,由宿主按需拉取,不写磁盘、不发第三方。
这不是什么:不是权限隔离,不是审计日志,不是远端浏览器控制(它只作用于已经打开这个 URL 的那个标签页)。
与 Agent 的关系
- 工具只有一个:
dsh_debug_web。参数里没有"宿主路径""shell""目标机器"——它只能作用于当前连接的页面。 - 多标签页有歧义:先
action=pages拿pageId,再把它传给eval/logs,否则可能打到后台标签页。 - 页面侧读的是插件加载之后的日志;插件装好前的历史输出拿不到。
- 不给模型截图与网络请求的能力:那需要 CDP 层,不在本通道里。
排错
| 症状 | 原因 | 处置 |
|---|---|---|
工具列表里没有 dsh_debug_web |
宿主半边没生效 | 重启 dsh web;确认 --dump-config 里有 dsh-debug-web 行 |
provider-missing: Client inspect provider "DebugWeb" is unavailable |
页面还是旧 bundle | 刷新浏览器页面 |
has no connected Harness page |
没有页面连着(全关或断线) | 打开/刷新 Harness 页面 |
报超时 after 10000ms |
页面在执行长任务、或标签页被节流 | 把慢活拆小;切到该标签页再试 |
rejected input: ... additionalProperties |
传了方法未声明的字段 | 只传表里那几列 |
returned non-JSON value |
provider 返回了函数/循环引用 | 用能 JSON 化的表达式(DOM 节点会自动转 outerHTML 前缀) |
eval 失败:... 超时:超过 3000ms |
页面侧 Promise 没在 3s 内完成 |
缩短等待,或改用 logs 看异步结果 |
页面 page-xxx 跳过 |
pageId 指到了另一个标签页 |
用 action=pages 的当前 pageId |
| 日志条数比自己看到的多 | 别的页面也在回 | 只有一个响应是正常的,缓冲各自独立 |
eval 里读不到别人控制的变量 |
页面是打包产物,模块不挂 window |
用 DOM/fetch/localStorage 侧证据,别指望内部符号 |
| 刷新后日志全没了 | 缓冲在内存里,随页面重载清空 | 重要的先 logs 读完再刷新 |
| 装了但界面没有任何变化 | 本插件没有界面 | 它只加工具,去工具列表/模型侧看 |
怎么验证
命令行
node --check index.js && node --check client.js # 两半语法
node probe-debug-web.mjs # 31 例探针,见下表(探针不进仓库)
| 覆盖面 | 例数 | 覆盖点 |
|---|---|---|
| 客户端 manifest | 2 | id、方法清单、描述与 schema 齐全 |
eval |
6 | 表达式 / await / 语句体 / DOM 兜底 / 语法错 / 执行期 console |
| 捕获与缓冲 | 5 | console 三级别、error 事件、unhandledrejection、环形上限 300、seq 不回退 |
logs |
2 | since 增量、level 过滤、limit clamp |
| 定向与清理 | 3 | pageId 不匹配拒绝 / 匹配执行、clear 保留 seq |
| HMR | 1 | 重载后钩子不重复安装(一次 console.log 只记 1 条) |
| 宿主工具 | 12 | 注册形态、四个 action 透传、缺参拒绝、无 agent 兜底、通道失败文案 |
浏览器清单
- 重启后工具列表出现
dsh_debug_web。 -
action=pages返回当前 pageId 与真实视口。 -
eval读document.title与页面标签一致。 -
eval计算样式值(如getComputedStyle(document.body).backgroundColor)与 F12 一致。 -
eval里console.warn('x')后,action=logs level=warn能查到,且 F12 里也有这条。 - 页面上故意抛一个未捕获异常,
action=logs level=error能看到消息与来源行。 -
clear之后pages的buffered归零、captured不回退。 - 多标签页时传错
pageId得到"跳过"报错,传对能执行。 - 刷新页面后缓冲清空、
pageId变化。
已知边界
| 边界 | 原因 | 代价 |
|---|---|---|
| 查询广播到所有已连接页面 | 通道没有"定向某个页面"的字段 | 不传 pageId 时,eval 的副作用可能在每个标签页各跑一次 |
| 10 秒通道上限 | clientInspectTimeoutMs 默认 10000 |
长轮询、watch 型需求做不了;页面侧 3s 先失败 |
| 只有第一个结果被采纳 | 首答优先 | 多页面时无法比较各页结果 |
| 无 push | 通道是请求/响应 | 日志只能模型主动拉,不能"出错即通知" |
| 装好之前的日志拿不到 | 钩子在插件加载时安装 | 排查早期启动错误得先刷新再复现 |
| 缓冲区在内存里 | 不落盘 | 页面重载即丢;单页上限 300 条 |
只覆盖 console 五级与两类未捕获异常 |
没包 console.trace/assert、没接 fetch 失败 |
网络错误要从 error 事件或自己的 try/catch 里看 |
| 返回值必须是无损 JSON | Host 侧 validateOutput 硬校验 |
返回函数/循环引用会整条查询失败 |
eval 不支持顶层 await import() 的模块图操作以外的注入 |
只有 AsyncFunction 上下文 |
拿不到打包器的模块表,只能用页面现有全局 |
| 不能截图、不能读网络瀑布 | 那需要 CDP,本通道没有 | 视觉回归、性能分析要另找工具 |
相关文件
| 文件 | 关系 |
|---|---|
| index.js | 宿主半边;改工具参数、渲染、错误文案看这里 |
| client.js | 页面半边;改捕获范围、缓冲口径、eval 语义看这里 |
| cordis.patch.yml | 装配行;改插件 id 要连 package.json 与 notes 一起改 |
| AGENTS.md | 给 agent 的硬规则:两半生效、provider id、schema 一致性 |
| LICENSE | MIT |
@deepseek-ai/dsh-cordis-host-runner/lib/index.js |
通道宿主侧:queryClient、超时、输出校验 |
@deepseek-ai/dsh-cordis-client-runner/lib/client.js |
通道页面侧:ClientCordisInspectRegistry.register/query |
许可证
MIT © 2026 dongasai
No comments yet. Be the first to write one.