<!-- Source: https://deepseek-browser-use.io.nexus/34 -->

# DeepSeek Harness 原生浏览器插件：源码、生命周期与测试

插件源码目录是 `deepseek-browser-use/plugins/deepseek-browser-use`。它保留 Java 服务现有协议，只在 Harness 和 HTTP 之间增加适配层。安装步骤见 [上一章](<https://deepseek-browser-use.io.nexus/33>)。

## 1. 文件分工

```text
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` 发出：

```json
{
  "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](<https://deepseek-browser-use.io.nexus/35>)。

恢复或重新加载的会话是新的活跃 Agent，使用新任务。插件不声称从 Session 日志恢复了真实浏览器页面。共享 profile 仍保留其自身登录态，这与恢复页面是两件事。

## 5. 异步作业与未知结果

普通 HTTP 请求返回后，队列可以放行下一项；异步批次只返回接收回执，浏览器工作仍在服务端运行。因此队列还需要 `activeJob` 状态：

1. `dsb_batch` 验证每一步的准确方法名，转换为服务单键格式，带 `async:true` 提交。
2. 保存返回的 `jobId` 到本会话集合；其他 Agent 无权通过 `dsb_job` 使用这个句柄。
3. 作业运行期间拒绝其他普通浏览器操作。
4. 查询读到 `done/failed/cancelled` 后解除阻塞。
5. 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` 分为两步：

1. 检查 Harness 附件服务、当前 provider/model 路由以及该模型的 image 输入声明。
2. 调用 `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`）：

```powershell
npm test
```

覆盖重点：

- 中文、引号、换行完整传输；`responseMode` 层级正确。
- HTTP 非 2xx、无效协议、重定向、超限、超时和取消不自动重试。
- 同会话只启动一次、顺序执行，不同会话 task ID 不同。
- 取消排队动作不发送 HTTP，也不破坏后续队列。
- 阻止模型注入 task ID、全局管理、模糊别名和嵌套批次。
- 作业归属、终态解锁、清理时取消并等待、未知提交结果冻结。
- 文件实际字节传输及大小上限。
- 附件服务缺失拦截、附件保存并剥离 base64、文本展示截断。
- 命令名称快照与 Java 注册表一致。
- 使用真实 `@deepseek-ai/dsh-tools` ToolRuntime 注册并派发，而不只调用工具函数。
- 后端管理：本机端点与绝对路径校验、操作单飞与失败保留、健康服务复用不跑安装、失败后普通动作不重复触发安装、取消等待不打断共享安装、就绪检查先于命令、清理类请求不触发安装，以及 Windows 脚本的 status/prepare（其中两项仅 win32 运行）。

## 9. 真实浏览器联调

服务已运行且和 Harness 测试进程在同一台机器时：

```powershell
$env:DSB_BASE_URL = 'http://127.0.0.1:10049'
npm run smoke
```

脚本启动本地 HTML 页面，经过真实 ToolRuntime、HTTP 和浏览器完成导航、读取、中文填表、点击回读、文件上传回读、截图、批次查询和关闭。它只关闭自己任务，不关闭外部共享服务。该测试页使用 loopback，因此默认脚本不适用于异机浏览器服务。脚本还认 `DSB_BROWSER`（默认 `chrome`）并默认以无头模式创建任务；隔离 runner 另认 `JAVA`（默认 `java`）。

使用独立 Java 服务验证，在浏览器仓库根目录执行：

```powershell
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 代替。
