DeepSeek Harness 原生浏览器插件:源码、生命周期与测试
插件源码目录是 deepseek-browser-use/plugins/deepseek-browser-use。它保留 Java 服务现有协议,只在 Harness 和 HTTP 之间增加适配层。安装步骤见 上一章。
1. 文件分工
plugins/deepseek-browser-use/
package.json npm 包与 Harness bundle 清单
package-lock.json 固定开发依赖
cordis.patch.yml 宿主插件配置
locale/ 插件管理器的中英文标题
src/
index.ts Cordis apply、Config 和资源释放
client.ts 有界 HTTP JSON 客户端
backend.ts 后台部署操作、进度与就绪等待
sessions.ts 按活跃 Agent 绑定任务、队列与作业
tools.ts 14 个原生工具(dsb_backend 由 index.ts 注册,共 15 个)、参数校验和结果展示
commands.ts Java 命令表名称快照
test/plugin.test.mjs 协议、生命周期和 ToolRuntime 测试
test/backend.test.mjs 后端管理、就绪等待与 Windows 脚本测试
scripts/smoke.mjs 对运行中服务做真实浏览器验证
scripts/backend.ps1 Windows 依赖安装、克隆、更新、构建与启动
scripts/isolated-smoke.mjs 启动独立 Java 服务后验证并清理
2. 插件装载与工具注册
index.ts 导出 name、inject、Config 和 apply,依赖 Harness 的 tools 与 agents 服务。Schemastery 负责配置默认值与范围检查;apply 创建 HTTP 客户端、后端管理器和会话管理器,再把工具交给 ctx.tools.register。启用 backendAutoStart 时,HTTP 客户端先等待后端就绪(close、cancel_job、get_job 例外,避免清理动作把已停的后端重新拉起);新增 dsb_backend 管理工具后共 15 个工具。
tools.ts 使用 Zod 校验运行参数。工具 schema 投影成 Harness 支持的 JSON Schema 子集,保留对象字段、类型、必填项、枚举和数组;URL 格式、长度、整数范围等更严格的规则仍在执行前通过 Zod 验证。
这样做是因为 PTC 需要从同一份 schema 生成工具调用类型,而 Zod 的完整 JSON Schema 包含递归引用和其他关键字,不能假设 Harness 的 schema 子集全都支持。
工具返回规范 JSON,output.render 负责面向模型的文本与图片展示。普通结果超过 maxTextChars 时明确标记展示截断,程序化返回仍保留完整 JSON。HTTP 层另有 maxResponseBytes 硬上限,避免无限制读取响应。
3. HTTP 协议保持不变
BrowserClient.command 发出:
{
"id": 123456789,
"method": "get_browser_state",
"params": { "highlight": false, "includeElements": false },
"responseMode": "compact"
}
responseMode 位于信封层,与 id/method/params 平级。HTTP 请求使用 fetch,正文通过 JSON.stringify 编码,中文、引号与换行不经过 shell 转义。
客户端同时组合调用者的取消信号与单请求截止时间,拒绝重定向,按块累计响应并检查字节上限。非 2xx、无效 JSON、没有 ok:boolean 的响应和超时都会变成传输错误,不尝试自动重发。ok:false 的合法服务业务响应则作为完整结果返回。
4. 会话、任务与清理
浏览器状态归活跃 Agent 对象所有。模型不接受 taskId 参数,不能用工具切换到任意任务。工具执行还会比较 ctx.agents.get(exec.agent.id) 与执行上下文里的对象,拒绝过期或伪造的活跃身份。
任务 ID 使用 48 位随机正整数,可在 Java long 与 JavaScript number 之间精确传输。没有使用服务端自动生成的 Snowflake 数字,以避免 JSON number 超过 JavaScript 安全整数范围。
每个 Agent 维护一条 Promise 队列。首次浏览器调用先执行 start,后续轮次复用;同一会话两个并发调用也按队列顺序送达。已取消的排队调用在发送 HTTP 前被拒绝,后续调用不会因前一条失败而永远卡住。
显式 dsb_close 只关闭自己任务,下一次浏览器调用可以重新启动。Agent scope 释放和插件卸载都会触发资源清理,这条路径不发送 shutdown。后端管理器的显式 restart 是单独的部署操作,只对已验证身份且无活动任务的自有后端执行浏览器关闭与 Java 重启,见 35。
恢复或重新加载的会话是新的活跃 Agent,使用新任务。插件不声称从 Session 日志恢复了真实浏览器页面。共享 profile 仍保留其自身登录态,这与恢复页面是两件事。
5. 异步作业与未知结果
普通 HTTP 请求返回后,队列可以放行下一项;异步批次只返回接收回执,浏览器工作仍在服务端运行。因此队列还需要 activeJob 状态:
dsb_batch验证每一步的准确方法名,转换为服务单键格式,带async:true提交。- 保存返回的
jobId到本会话集合;其他 Agent 无权通过dsb_job使用这个句柄。 - 作业运行期间拒绝其他普通浏览器操作。
- 查询读到
done/failed/cancelled后解除阻塞。 - Agent 清理时请求
cancel_job,在有界时间内等待终态,再close自己任务。
cancel_job 是协作式取消,当前动作不会被撤销。请求取消后不能立即声称所有操作都已停止。
如果异步提交响应丢失或缺少 jobId,插件没有可靠句柄判断作业是否完成。此时设置 batchUnknown,阻止再次操作,也拒绝自动关闭可能仍在工作的任务。错误报告 task ID,交给运维在服务端查询 list_jobs、匹配 browserId、等待或取消作业并关闭任务。资源清理失败会向宿主报告,不能伪装成清理成功。
普通单条命令超时也可能已经执行,错误会提示先观察再决定;插件没有服务器端事务回滚或远程硬取消能力。
6. 通用命令与边界
通用工具必须使用 commands.ts 中的准确命令名,不能依赖服务端模糊别名来绕过适配。commands、run_recipe 和全局管理等方法禁止通过通用入口调用;批次逐项执行相同检查,避免把全局操作藏进嵌套命令。
这套限制用于任务管理和避免误操作,不是安全沙箱。页面 JavaScript、文件与 Cookie 功能仍然存在,同一服务的 profile 可以共享登录态。需要租户隔离时应在部署层使用不同实例、profile 与访问控制。
新增 Java 命令后更新名称快照;一致性测试会对照 CommandTable.java 检查名称与顺序,防止文档宣称支持而插件拒绝。测试用全文正则提取该文件的 put("…") 名单,因此名单末尾的 commands 其实来自 runRecipe 的参数注册,不是命令表条目。
7. 图片与上传
dsb_screenshot 默认 view:false,让服务落盘并返回 URL。view:true 分为两步:
- 检查 Harness 附件服务、当前 provider/model 路由以及该模型的 image 输入声明。
- 调用
screenshot(inline:true),把 base64 解码并交给attachments.saveImage,从规范结果中移除 base64,保留持久附件引用。
output.render 由这个引用产生 image 内容块,避免把图片字节当作普通文本塞进模型上下文。实际图像校验、存储与展示仍由宿主附件实现负责。
dsb_upload 在 Harness 主机检查普通文件大小,读取字节并发送 filename/contentBase64。因此“客户端文件路径”和“服务端文件路径”不会混淆。当前实现是有大小上限的内存传输,大文件不在第一版支持范围内。
8. 自动化测试
插件目录运行 npm test(等于 npm run build && node --test test/*.test.mjs):
npm test
覆盖重点:
- 中文、引号、换行完整传输;
responseMode层级正确。 - HTTP 非 2xx、无效协议、重定向、超限、超时和取消不自动重试。
- 同会话只启动一次、顺序执行,不同会话 task ID 不同。
- 取消排队动作不发送 HTTP,也不破坏后续队列。
- 阻止模型注入 task ID、全局管理、模糊别名和嵌套批次。
- 作业归属、终态解锁、清理时取消并等待、未知提交结果冻结。
- 文件实际字节传输及大小上限。
- 附件服务缺失拦截、附件保存并剥离 base64、文本展示截断。
- 命令名称快照与 Java 注册表一致。
- 使用真实
@deepseek-ai/dsh-toolsToolRuntime 注册并派发,而不只调用工具函数。 - 后端管理:本机端点与绝对路径校验、操作单飞与失败保留、健康服务复用不跑安装、失败后普通动作不重复触发安装、取消等待不打断共享安装、就绪检查先于命令、清理类请求不触发安装,以及 Windows 脚本的 status/prepare(其中两项仅 win32 运行)。
9. 真实浏览器联调
服务已运行且和 Harness 测试进程在同一台机器时:
$env:DSB_BASE_URL = 'http://127.0.0.1:10049'
npm run smoke
脚本启动本地 HTML 页面,经过真实 ToolRuntime、HTTP 和浏览器完成导航、读取、中文填表、点击回读、文件上传回读、截图、批次查询和关闭。它只关闭自己任务,不关闭外部共享服务。该测试页使用 loopback,因此默认脚本不适用于异机浏览器服务。脚本还认 DSB_BROWSER(默认 chrome)并默认以无头模式创建任务;隔离 runner 另认 JAVA(默认 java)。
使用独立 Java 服务验证,在浏览器仓库根目录执行:
mvn -q -o -pl playwright-server compile dependency:build-classpath '-Dmdep.outputFile=target/plugin-smoke-classpath.txt'
cd plugins/deepseek-browser-use
npm run build
node scripts/isolated-smoke.mjs
第一条的 -o 是离线模式,前提是本地 Maven 仓库已经有该工程的全部依赖(此前至少完整构建过一次);依赖不全时去掉 -o,让它联网补齐。
runner 使用独立随机端口、工作目录、profile 和 jdk.net.unixdomain.tmpdir。本机 JDK 21 的默认 Unix-domain loopback 曾在启动 Selector 时失败,测试 runner 将临时 socket 文件也放入自己目录后通过;它不修改系统 JDK 设置。日志和页面产物保留在仓库 logs/plugin-smoke/,runner 最后仅关闭自己启动的服务。
已验证 TypeScript 构建、真实 ToolRuntime 派发以及上述 Java/浏览器链路。当前开发环境未连接目标 Harness 的插件管理器,因此 bundle 在用户实际运行 profile 中的安装、激活和 Web UI 图片显示仍需按上一章回读确认;这些结果不能由 HTTP smoke 代替。
