# DSH WorkBuddy Kit [中文](README.md) · [English](README.en.md) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) [![Node](https://img.shields.io/badge/node-%3E%3D22.19-brightgreen.svg)](https://nodejs.org) [![MCP](https://img.shields.io/badge/MCP-stdio-8A2BE2.svg)](https://modelcontextprotocol.io) > **DeepSeek Harness 负责想清楚并验收,本机 WorkBuddy 负责动手做:一次派发、等到终态、自己复核证据、按完整 sessionId 归档。** 一个本机工具包,把「DSH 派活」和「WorkBuddy 干活」接成闭环。两个前门共用同一张工具表: - **原生 DSH 工具**(Cordis 插件 `dsh-workbuddy-kit`,注册 `wb_*` 到 `ctx.tools`) - **stdio MCP 服务**(`dsh-workbuddy-mcp`,任何 MCP 客户端可用) 两者行为完全一致,因为它们调用的是同一份 `src/tools.mjs`。 ## 这是插件、技能,还是 MCP 服务? 四个形态,共用同一张 src/tools.mjs,所以行为一致;区别是**谁在用**和**怎么装**: | 形态 | 文件 | 怎么装 | 作用 | 改完要重启吗 | | --- | --- | --- | --- | --- | | **DSH 插件**(主体) | plugin/index.mjs + cordis.patch.yml | dsh plugin --profile add . | 把 18 个 wb_* 注册进 ctx.tools,在 DSH 里直接调用 | **要**(工具表在启动时快照) | | **技能**(附带,可选) | skills/delegate-to-workbuddy/SKILL.md | 拷到 /agents-home/skills/ | 教 agent 何时派发、怎么等终态、怎么复核证据、怎么按 ID 归档 | 不要(热加载) | | **MCP stdio 服务** | mcp/server.mjs | 写进任意 MCP 客户端配置 | 给非 DSH 的客户端(Claude / Codex / 自研)用同一套工具 | 不要 | | **CLI** | in/wb.mjs | ode bin/wb.mjs … 或 pm i -g | 手工验证、脚本化,不接任何客户端 | 不要 | **插件 = 能力(工具),技能 = 行为指南(什么时候用、按什么顺序用)。** 插件可以单独用;技能离开插件没有意义。只想要工具就装插件;想让 agent 自觉按「派发 → 等终态 → 复核 → 归档」的规矩办事,再叠加技能。 ## 解决什么问题 | 没有它的时候 | 有了它 | | --- | --- | | 想让 WorkBuddy 干活,只能把上下文、路径、要求手打一遍,还得自己盯着它跑完没有 | 一次派发拿到 `runId`,`wait_run` 拉到终态;`state` / `stopReason` / `toolCalls` / `workspaceChanges` 都是结构化字段,超时不算终态 | | 「它说做完了」= 做完了? | 工具返回的是**证据**:git 前后对比、工具调用数、完整转录、sha256 归档副本;调用方自己复核才算完 | | 派出去的会话在桌面 App 里看不见,等于没发生过 | 默认自动登记进 App 索引,会话就出现在**同一个工作区**下(跟随 DSH 当前目录),点开就是完整对话 | | 想批量盯桌面 App 的定时任务,只能一个个点开看 | 三个只读工具直接读 `workbuddy.db`:任务清单、每次运行、成功率与失败码、逾期任务 | | 它的权限边界靠猜 | 实测过的矩阵写死在文档里:工作区内读写用 `acceptEdits`;跨目录读、`WebFetch`/`curl` 要 `bypassPermissions`;`WebSearch` 无头下不可用 | ## 它控制的是什么 本机 WorkBuddy 桌面版(腾讯,实测 `5.8.3`)在自己的安装目录里**自带一个 CodeBuddy agent CLI**: ```text D:\Program Files\WorkBuddy\resources\app.asar.unpacked\cli\ bin\codebuddy # @genie/agent-cli 入口(实测 2.161.0) dist\codebuddy-lite-wb.mjs # 当前发行只带 lite-wb 包 ``` 这个 CLI 支持无头运行:`-p/--print`、`--output-format stream-json`、`--session-id`、`--permission-mode`、`--model`、`--max-turns`、`--effort`、`--mcp-config`……本工具包就是把这条通道变成一组可验收的工具: | 工具 | 作用 | | --- | --- | | `wb_doctor` | 只读自检:CLI、Node、数据目录、版本、自定义模型、最近是否撞到登录墙 | | `wb_list_capabilities` | 真实可用的 permission mode / 自定义模型 | | `wb_list_projects` / `wb_list_sessions` | 找到 WorkBuddy 的工程目录与会话 | | `wb_start_review` | **只读**派发(`--permission-mode plan`,并拒绝 Write/Edit/NotebookEdit) | | `wb_start_run` | 实施派发(默认 `acceptEdits`) | | `wb_wait_run` / `wb_get_run` | 盯到终态;超时**不算**终态(`hostMustCallWaitRunAgain`) | | `wb_cancel_run` | 终止整个子进程树 | | `wb_list_runs` | 本机派发台账 | | `wb_read_session` | 直接读对话记录(消息、工具调用、模型),自己复核 | | `wb_archive_session` | 按完整 sessionId 归档:带 SHA-256 的副本 + manifest + 索引 | | `wb_list_automations` | **桌面 App 调度器**:定时任务清单(节奏/下次触发/最近结果/模型/权限/推送) | | `wb_get_automation_runs` | 某个定时任务的运行历史、成功率、失败码 | | `wb_automation_health` | 调度器体检:生效 vs 软删、总运行/失败率、失败码分布、过期未触发 | 调度器三个工具**只读** `workbuddy.db`(`automations` / `automation_runs` / `automation_runtime_state`),不写任何调度状态。 ## 最后效果 真实跑过一次的形态(这就是本仓库在制作机上的验收记录): ```text 你 ❯ 用 WorkBuddy 只读核对 notes.md 里的标记,别改文件 DSH ❯ wb_start_review { workspace:"E:\\Code\\demo", task:"读出形如 DWB-ACCEPT-XXXX 的标记,原样引用,不要修改任何文件" } → runId, sessionId:, permissionMode:"plan", state:"running" wb_wait_run { runId } → state:"succeeded", stopReason:"end_turn", toolCalls:3, durationMs:22485 (assistantText 里就是 "DWB-ACCEPT-9F3A,位于第 3 行",自己去文件对一遍) wb_archive_session { sessionId:"" } → archived:true, bytes:15124, sha256:a0c6cc74…, target:…\\session-archive\\\\e-Code-demo\\ ``` 拿到的不是「已完成」四个字,而是:结论 + 工具调用次数 + 真实 sessionId + 归档摘要。 ## 安装 前置:本机已装 WorkBuddy 桌面版;Node ≥ 22.19(跑 DSH 用,本工具包自身零依赖)。 ### A. 丢给 AI(推荐,零手工) 把下面这段原样发给任何有终端权限的 AI(DSH / Claude / Codex 都行): ``text 请安装并验收 https://github.com/ksoulness/dsh-workbuddy-kit 这个 DSH 插件。 先读 https://raw.githubusercontent.com/ksoulness/dsh-workbuddy-kit/main/AGENTS.md 并按它执行;读不到就按下面四步: 1. 前置检查:node -v 需 >= 22.19;git、dsh 可用;确认本机存在 WorkBuddy 的 CLI 目录 (Windows: C:\Program Files\WorkBuddy\resources\app.asar.unpacked\cli\bin)。 2. 安装:git clone 仓库后 dsh plugin --profile <我的profile> add ., 再用 dsh --profile --dump-config 确认出现 dsh-workbuddy-kit 行。 3. 验收: pm test(应 pass 65 / fail 0)、 pm run verify(应 18 tools / 0 failures)、 ode bin/wb.mjs doctor(应 found true)。 4. 报告:用表格列出每一步的命令、真实输出、结论;最后告诉我是否需要重启 DSH。 铁律:不要提权、不要 sudo、不要写 workbuddy.db、不要改我的 models.json 和会话文件、 不要调用 wb_publish_session、不要在没有亲眼看到输出时声称成功。失败就贴原始报错。 `` 装完记得**重启 DSH**(工具表在启动时快照);技能是热加载的,改了立刻生效。 ### B. 原生 DSH 工具(手工) ```sh git clone dsh-workbuddy-kit cd dsh-workbuddy-kit dsh plugin --profile web add . dsh --profile web --dump-config | grep -n workbuddy ``` 重启 DSH(Web 端刷新页面)后,`wb_*` 就是原生工具。 ### C. MCP stdio(任何 MCP 客户端) `cordis.patch.yml` 里追加一行,不要在同一个 profile 同时开 A 和 B(工具名会重复): ```yaml - id: mcp-workbuddy name: '@deepseek-ai/dsh-mcp-client' config: serverName: workbuddy transport: stdio command: node args: ['E:\\Code\\dsh-workbuddy-kit\\mcp\\server.mjs'] ``` ### D. 离线 tarball(不联网、不装 npm 包) 每次 Release 都附带 dsh-workbuddy-kit-.tgz,内含 in/ mcp/ plugin/ src/ docs/ skills/ scripts/ LICENSE README*: `sh npm pack # 或直接从 Release 下载 npm i -g ./dsh-workbuddy-kit-0.1.0.tgz # 拿到 dwb / dsh-workbuddy-mcp 两个命令 dwb doctor ` 不装全局也行:解包后 ode package/bin/wb.mjs doctor 就能用。 ### E. 手工验证(不接任何客户端) ```sh node bin/wb.mjs doctor node bin/wb.mjs review --workspace E:\Code\demo --task "只读核对 README 第 1 节,给出精确行号" --wait 300 node bin/wb.mjs sessions --limit 5 ``` ## 关键约束(都是实测出来的,不是猜的) - **CLI 必须指向 WorkBuddy 的数据目录**:不设 `CODEBUDDY_CONFIG_DIR` / `WORKBUDDY_CONFIG_DIR`,CLI 会去读 `~/.codebuddy`,那里没有登录态。本工具包自动指向 `~/.workbuddy`。 - **当前发行只带 `lite-wb` 包**:必须带 `CODEBUDDY_FORCE_LITE_WB_BUNDLE=1`,否则启动器会去找不存在的 `dist/codebuddy`。工具包自动判定。 - **无头 CLI 用不了桌面版的登录态**:账号默认模型会直接返回 `Authentication required`。工具包默认挑 `/models.json` 里的第一个自定义模型(本机实测 `custom-local:cn:space-bunny` 可用),并把这个选择记在 `modelSource` 里;用 `DWB_DEFAULT_MODEL` 可覆盖。 - **无头下工具调用要 `-y`**:腾讯文档明确「`-y` 是非交互模式必需参数」,否则读写/命令会被拦(本仓库实测:`plan` 模式下 Bash 被拒,模型改走 Grep/Read)。`wb_start_run` 提供显式的 `skipPermissions: true`,从不默认打开;HIGH/CRITICAL 命令仍可能要求确认。 - **会话落在 `/projects/<工程名>/.jsonl`**,工程名是工作目录的映射(`E:\Code\acme-app` → `e-Code-acme-app`)。归档就是对这个文件做带摘要的副本。 - **归档不动厂商数据**:默认 `copy`,WorkBuddy 自己的会话列表不变;`move` 是显式的可逆选项。 ## 桌面 App 的调度器(定时任务) 定时任务**不在文件里**,在 `~/.workbuddy/workbuddy.db` 的三张表(`automations.db` 是 0 字节的遗留空文件): | 表 | 内容 | | --- | --- | | `automations` | 任务定义:名字、完整 prompt、状态、`rrule`、`next_run_at`、`last_run_at`、模型、权限模式、工作区、推送渠道、`deleted_at` | | `automation_runs` | 每次触发:`thread_id`、状态、`result_success`、`failure_code`、`reason_code`、标题 | | `automation_runtime_state` | `running`、`last_error` | ```sh node bin/wb.mjs automations # 生效中的任务 node bin/wb.mjs automations --all # 含软删除 node bin/wb.mjs automations --prompt # 附带 prompt 预览(默认不返回) node bin/wb.mjs automation-runs --name "研究流水线监护" --limit 5 --failures node bin/wb.mjs automation-health ``` 两个必须知道的坑(实测): - **软删除行 `status` 仍是 `ACTIVE`**:真正的活性信号是 `deleted_at`。本工具包输出 `softDeleted` 与 `live` 两个显式字段,避免把僵尸任务当活跃任务。 - **prompt 全文就在库里**(很长、很私人):默认只返回 `promptBytes`,要预览得显式 `includePrompt: true`,且截断到 2000 字符。 读法是 `readOnly: true` 打开 SQLite(WAL 读者不影响正在运行的桌面 App)。调度器**只读**:暂停/改 RRULE/新建/删除都要走桌面自己的 IPC 或 UI,直写会让界面与库不一致。 ## 让派发的会话出现在桌面 App 里(写库,可撤销) 默认情况下 CLI 会话**不会**出现在桌面 App 的会话列表里:它只写 `projects//.jsonl`,不写 App 自己的索引。桌面列表的查询是: ```sql SELECT … FROM sessions WHERE transport = 'local' AND deleted_at IS NULL AND (user_id = ? OR user_id = '') AND id IN (…) ``` 所以转录再完整,**没有那一行 `sessions` 记录就是不可见**。补上那一行(外加一行 `session_usage`)就会出现: ```sh node bin/wb.mjs publish --session --title "【DSH 派发】…" node bin/wb.mjs published # 本工具包发布过哪些 node bin/wb.mjs unpublish --session # 只删自己插的那两行 ``` 工具:`wb_publish_session` / `wb_unpublish_session` / `wb_list_published`。 `publish` 也接受 `--run `,直接从派发台账解析 sessionId。**默认就会发布**:派发跑完自动登记,结果记在运行的 `publishResult` 字段里,发布失败不会改写运行的终态。要**不**污染 App 列表就显式传 `publishToApp: false`(CLI 是 `--no-publish`),此时只写转录、不进 App。 > **工作区归属 = DSH 会话当前的工作目录**(不是写死的路径)。省略 `workspace` 时插件每次调用都会向 `workingDirectory` 服务要当前目录,所以你在 DSH 里 `cd` 到哪,WorkBuddy 就在哪开工;解析不到就报错,不会退回 harness 安装目录。`workspace` 只在你需要**故意**换目录时显式传。 > > **工作区归属由 `workspace` 决定**:桌面 App 按会话的 `cwd` 分组,所以派发时把 `workspace` 设成你平时用 DSH 的那个目录(例如 `E:\Code\acme-app`),这个会话就会出现在同一个工作区下;换成别的目录,就会多出一个新工作区。`wb_start_run` / `wb_start_review` 现在 `workspace` 可省略,省略时用当前 DSH 会话的工作目录(插件注入 `workingDirectory` 服务);解析不到时**直接报错**而不是退回 `process.cwd()`——那是 harness 安装目录,发到那里会让 App 多出一个垃圾工作区。 写什么:一行 `sessions`(`id`、`cwd` 归一成 `E:/Code/x`、`user_id` 取当前登录用户、`title`、`status`、`created_at`/`updated_at`/`last_activity_at`、`transport='local'`、`source_mode='craft'`、`mode='craft'`、`model`)+ 一行 `session_usage`(`size` = 转录字节数)。**转录文件一个字节都不改。** 安全边界: - 会话不存在、读不到 `cwd`、或库里已有该 id → **拒绝**并说明原因; - 每次插入记进 `/published.jsonl`;`unpublish` 只删账本里登记过的行,且不动其他会话行; - 建议先备份库(`docs/setup.md` 有一条实测命令); - 手法不是本文独创:GitHub 上的 [`workbuddy-account-migrate`](https://github.com/xiaoliuzhuan666/workbuddy-account-migrate) 用同样方式(写 `sessions`/`session_usage` + 搬 `projects//`)做跨账号会话复制,并明确写了「不写 sessions 行,客户端就找不到」。 ## 与已有项目的关系 搜过一圈,方向不同,没有一个在做「DSH 派活给 WorkBuddy 并验收」: | 项目 | 它做什么 | 与本仓库的区别 | | --- | --- | --- | | [`dsh-connect-workbuddy`](https://dsh.directory/plugins/dingminhua/dsh-connect-workbuddy) / [`corrinehu/dsh-workbuddy-connect`](https://github.com/corrinehu/dsh-workbuddy-connect) | 把 WorkBuddy 的**模型**接进 DSH 模型选择器 + 账号池/积分 | 解决「用哪个模型」,不涉及把任务交给 WorkBuddy agent 执行 | | [`ithtelab/workbuddy-manager`](https://github.com/ithtelab/workbuddy-manager) | WorkBuddy/CodeBuddy 账号池 + OpenAI 兼容反代(`workbuddy2api`) | 网关与账号运维,不是 agent 委派 | | 腾讯官方 [无头模式文档](https://www.workbuddy.ai/docs/zh/cli/headless) | `-p` / `--output-format` / `--resume` 等 CLI 用法 | 只用 CLI,无派发台账、无终态机、无会话归档 | | [`ksoulness/dsh-codex-kit`](https://github.com/ksoulness/dsh-codex-kit) | Codex → DSH 的委派(本仓库的结构参照) | 方向相反:本仓库是 DSH → WorkBuddy | ## 安全与边界 - `permissionMode: plan` 与拒绝写工具是**给模型的声明**,不是进程级沙箱:CLI 仍以自己的权限运行,Bash 之类的工具能做什么由它自己的策略决定。 - 派发内容是「任务描述」,不是源码:只说工作区、范围、授权、验收标准,让 WorkBuddy 自己去读文件。 - 不读取、不打印、不落盘任何 token / cookie / API key;`models.json` 只取 `id`、`vendor` 这类身份字段。 - 归档在会话仍活跃、或本工具包没有该会话的终态记录、或目标文件已存在时会**拒绝**并给出原因,不伪造 `archived:true`。 - 本包不是腾讯 / DeepSeek 官方产品。 ## 已验证 / 未验证 制作机(Windows、Node `v24.19.0`、WorkBuddy `5.8.3`、CLI `2.161.0`、DSH `0.2.1-alpha.2`)实测: - ✅ 真实只读派发 → `succeeded` / `end_turn` / 3 次工具调用 / 22.5s,`assistantText` 与工作区文件一致; - ✅ 真实实施派发 → `succeeded` / `end_turn`,工作区里确实多出 `hello.txt`,内容与任务要求逐字一致(`DWB-RUN-OK`); - ✅ 真实归档两次:`archived:true`,15124 / 7973 字节,`sha256:a0c6cc74…` / `9887ba08…`; - ✅ `wb_doctor` 正确识别安装路径、自带 Node、`liteBundleOnly`、自定义模型; - ✅ `node --test` 65/65:流解析、工程名映射、schema 子集、MCP 握手、归档的全部拒绝分支、调度器只读读取(含软删除与失败码)、git 侧改动证据、发布/撤销/默认发布; - ✅ MCP stdio 真实握手(`initialize` + `tools/list`); - ✅ 在真实 cordis 里挂载插件(`inject: ['tools', 'workingDirectory']`)→ 18 个工具全部通过 `dsh-tools` 自己的 `assertObjectJsonSchema` / `assertSupportedJsonSchema` 校验(`npm run verify`); - ✅ 委派真实产出:WorkBuddy 按任务在 `E:\Code\demo` 写出 `CHEATSHEET.md`,其中 5 条命令逐字核对全部命中源文档(`README.md:71/72/85/94/95`);另一次运行由 `workspaceChanges` 抓到新增 `demo-workspace-change.md`(`addedCount: 1`); - ✅ 跟随 DSH 工作目录:同一会话在 E:\Code\acme-app 时省略 workspace → 落在 E:\Code\acme-app;把 DSH cwd 切到 E:\Code\dsh-workbuddy-kit 后再派发 → 落在 E:\Code\dsh-workbuddy-kit(projectSlug: e-Code-dsh-workbuddy-kit),没有写死路径; - ✅ 诚实的失败样本:跨 `--add-dir` 目录读取被拦(即使 `-y`),运行层仍 `succeeded` 但零改动 —— 证明「终态 ≠ 完成」,必须看 `workspaceChanges` / 文件; - ✅ 桌面调度器真实读取:6 条生效 + 3 条软删、累计 1000+ 次运行(成功率约 0.91)、失败码分布与桌面 App 界面一致; - ✅ `dsh plugin --profile web add .` 装进本机 profile,`dsh --profile web --dump-config` 出现 `dsh-workbuddy-kit` 行。 未验证:其他 WorkBuddy 版本 / 其他平台的路径、`--acp` 与 `--serve` 通道、`move` 归档对桌面端索引的影响。 ## 目录结构 ```text src/discover.mjs 安装/CLI/Node/数据目录定位,工程名映射 src/cli.mjs 组装无头调用、spawn、终止进程树 src/parse.mjs stream-json 与 session JSONL 解析 src/runs.mjs 派发台账、终态判定、恢复与取消 src/sessions.mjs 会话枚举/读取/精确归档 src/automations.mjs 桌面调度器只读(automations / runs / runtime_state) src/tools.mjs 唯一工具表(两个前门共用) plugin/index.mjs 原生 DSH 插件 mcp/server.mjs stdio MCP 服务(零依赖) bin/wb.mjs 手工 CLI cordis.patch.yml DSH bundle 层(只插一行) skills/delegate-to-workbuddy/SKILL.md docs/ 接入、架构、安全 tests/ 37 个 node:test 用例 ``` ## 文档 - [接入与故障定位](docs/setup.md) - [架构与状态机](docs/architecture.md) - [安全与边界](docs/security.md) - [DSH 交接清单](DSH-HANDOFF.md) ## 许可 MIT。WorkBuddy、CodeBuddy、DeepSeek Harness 不在本包内,各按其自身许可与条款。