Shuffle-1992/dsh-dispatch

zcode-dispatch

DSH 插件:ZCode 派发台 —— 在 DeepSeek Harness 面板里派发/监视 ZCode 无头进程(agent 工具 + 悬浮面板:单写者互斥 / 用量聚合 / 套餐通道 / 降级链)

待分类cordis-plugindeepseekdeepseek-harness

DSH 派发台(dispatch)

一个 DeepSeek Harness(DSH)插件:
把任务派发给本机 四个 AI 编程 CLI(ZCode / OpenCode / Qoder / WorkBuddy)的无头进程,
并在 DSH 页面右下角的悬浮面板里监视它们。

DSH 自带的 subagent 跑的是 DSH 自己的 agent;本插件跑的是你自己的 CLI —— 独立进程、 独立额度、独立会话,任务行出现在「DSH 派发台」面板里,可查输出、可终止、可续跑、可换通道重跑。

四个引擎

全部走官方 CLI:不反代、不模拟 OpenAI/Anthropic 兼容接口、不读写任何凭据内容 —— 插件只负责"起进程 / 传参数 / 读 stdout"。"通道即执行目标"是 core 的模型 (zcode / opencode/ / qoder/ / codebuddy/)。

引擎 · 官方 CLI · 登录 · 额度 / 积分 · 备注 ZCode · zcode.cjs(桌面客户端自带) · 读 ~/.zcode/v2/credentials.json · ✅ 能查(客户端日志里的 balance 行) · 支持「套餐」与「免费额度 Start Plan」两条路由 OpenCode · opencode(PATH) · 免费模型无需凭据 · 免费(无额度概念) · 十几个免费模型;并发上限默认 2(实测依据) Qoder · qoderclicn(PATH) · ⚠️ 需单独登录 · ⚠️ 积分制,无余额查询接口;单次消耗可查(credits) · 与桌面应用登录不共享;并发默认 3(实测) WorkBuddy · codebuddy(WorkBuddy 客户端内置) · ⚠️ 需单独登录 · ⚠️ 积分制,CLI 完全不暴露 · 见下节「积分可得性」;并发默认 2(保守默认,未实测)

引擎自动发现:四引擎都走 collab-kit/engine-discover.mjs 的五级探测 (env → 配置 → PATH → 已知安装位置 → 跨盘符扫描),不需要手填路径; 面板「引擎」行显示形态 / 版本 / 登录态,点悬浮看探测依据。

积分可得性(真机实测结论,不猜)

引擎 · 余额 · 单次消耗 · 依据 ZCode · ✅ giftQuota · token · 读客户端日志(只读,零网络) OpenCode · — · token · 免费模型 Qoder · ❌ 官方无接口 · ✅ credits · result.total_credits 恒 0,真值在每条 assistant 的 message.usage.credits(需按 message.id 去重累加) WorkBuddy · ❌ 官方无接口 · ❌ · stream-json 只有 token;total_cost_usd 恒 0;/cost /stats 无头不支持;--serve REST 无 usage 端点;本地存储无数值

⇒ 面板与 agent 工具只显示真拿得到的数:拿不到就不显示(不编数字、不猜汇率折算)。 「哪个引擎查不到」这种元数据仍完整保留在 action=channels 的 creditSummary 里给 agent 读。

为什么走官方 CLI,而不是反代

社区有把 WorkBuddy / Qoder 的账号登录态反代成 OpenAI/Anthropic 兼容接口的做法。 本项目刻意不做:

  1. 合规:反代本质是伪造客户端身份调官方接口,官方一收紧就会被封(WorkBuddy 已经开始限制)。

走官方 CLI 是产品自身入口,不受这类限制影响。

  1. 不碰凭据:插件只读做登录态探测(凭据文件是否存在 / 大小 / 刷新时间),

从不读内容、不写、不删。修复动作一律交给用户或官方 /login。

  1. 能力完整:CLI 自带完整工具链(读写文件、跑命令、MCP);反代只能拿到"对话",

DSH 的工具层反而用不上。

代价:只能拿到 CLI 愿意暴露的数据 —— 比如 WorkBuddy 的积分就查不到(见上表)。 本项目的态度是如实标注"查不到",不编数字。

长任务在看守进程那侧还会被温和终止(3 秒后才兜底强杀)—— 因为真机发现强杀 WorkBuddy CLI
会留下 .logged-out 标记,导致之后一直判"未登录"(详见 pitfalls.md)。

安装

本项目不发布 npm(不提供裸包名安装)。唯一安装路径 = 从 GitHub 仓库安装;
改代码的场景用本地绝对路径挂载。

从 GitHub 仓库安装(推荐)——git 规格 + #path: 子目录定位:

dsh plugin --profile web add "github:Shuffle-1992/dsh-dispatch#path:dispatch"
把 web 换成你的 profile 名(dsh plugin list 可查)。装完重启 DSH 生效。
`#path:dispatch` 不能省:本仓库是 monorepo,插件本体在 dispatch/ 子目录;
DSH 实际执行 pnpm add ,而 pnpm 对 git 仓库默认取仓库根 —— 根目录没有插件
package.json,装出来只会得到占位包(实测 {"_pnpmPlaceholder":"...did not contain a package.json."})。

本地开发挂载(源码就在本机时):profile 的 dependencies 加 "dsh-dispatch": "link:/dispatch"、bundles 加 "dsh-dispatch", 并在 profile 的 node_modules/dsh-dispatch 建 junction 指向该目录。

包名 = dsh-dispatch;bundle 资格由仓库根 package.json 的 dsh.bundle.patch
(→ dispatch/cordis.patch.yml)声明;插件本体清单在 dispatch/package.json。

它做什么

能力 · 说明 派发(四引擎) · 把 prompt / 任务文件 / 目标描述交给 ZCode / OpenCode / Qoder / WorkBuddy 跑;build / edit / plan / yolo 四种模式仅 ZCode 支持(其它引擎按各自 CLI 能力剔除) 单写者互斥 · repo / 文件锁 + 进程内 FIFO 队列:同锁任务串行,冲突只排队不报错(多会话并发也不会互相踩) 实时监视 · 每个 job 的状态、耗时、Token 用量、上下文占用、积分/费用、退出码、锁持有者;可拉最近输出(tail) 续跑 / 重跑 · 同会话 --resume 重发原提示词,或在同一会话里发一条新指令(仅 ZCode 支持 resume) 通道与降级链 · 切换通道 / 模型 / 思考强度;额度耗尽或未开通时按降级链自动交接重跑到下一个可用通道 分流模式 · 主通道名额满时把新任务自动改派到降级通道(两条通道并发名额叠加);显式指定 provider 时不生效 思考强度核对 · 记录请求档位 vs 实际生效档位;Qoder 用 thinking 内容块有无判定,不符时给出向上改档建议 引擎登录助手 · WorkBuddy 登录态常显(●已登录 / ◐凭据在 / ○未登录 / ⚠标记残留)+ 一键登录(临时 --serve);登录服务温和终止、残留标记自动诊断 免费额度通道 · ZCode Start Plan:托管官方 app-server 走逐请求签名;account:-start-plan;与付费套餐复用同一条面板 / 台账 / 暂停 / 降级流水线 积分/额度视图 · 按引擎汇总(creditSummary:有余额 / 取不到 / 官方无接口)+ 本进程内消耗聚合(creditConsumption);拿不到的就不显示 用量聚合 · 本地台账 5 小时滚动 / 本周 / 今日,外加套餐剩余额度(数据源不可用时如实标注,不猜) 派发总开关 · 一个跨会话真值文件:false = 任何会话都不得派发(runner / 插件 / 桥接三处强制生效) agent 工具 · 注册 dispatch,与面板同一套动作实现(一操作两调用方) 落地自动唤醒 · 任务落地(done/failed/killed/paused)时唤醒发起会话,与 DSH 后台任务同款

目录结构

dispatch/
├─ dispatch/          ★ 插件本体(cordis bundle:Host 半边 + Client 半边)
│  ├─ index.js              Host 入口:apply(ctx, config)、dispatcher 单例、注册 agent 工具、激活信标
│  ├─ wire.host.mjs         Host 接线 + 动作唯一实现 createActionHandler + 派发总开关读写
│  ├─ wire.client.mjs       Client 接线 + 轮询 / demo 降级
│  ├─ client.js             UI 半边:悬浮面板(React.createElement,无构建步骤)
│  ├─ core/                 派发核心(零 npm 依赖):dispatch-core / quota / gift-quota / appserver-rpc …
│  ├─ bin/dispatch.mjs           独立 CLI(与插件同 core,用来对照排查)
│  ├─ locale/{zh,en}.json   中英文案(key 一一对应,有测试锁)
│  ├─ test/                 69 个可复跑自测文件
│  └─ cordis.patch.yml      bundle 层 patch(**只放 demo/maxConcurrent**,机器路径见「配置」)
├─ collab-kit/              ★ 四个执行器(与插件 core 共用行协议,也可单独当 CLI 用)
│  ├─ zcode-run.mjs         ZCode 执行器
│  ├─ opencode-run.mjs      OpenCode 执行器
│  ├─ qoder-run.mjs         Qoder 执行器(credits 计量)
│  ├─ codebuddy-run.mjs     WorkBuddy / CodeBuddy 执行器(官方 CLI + 登录助手)
│  ├─ engine-discover.mjs   四引擎自动发现(env → 配置 → PATH → 已知安装位置 → 跨盘符扫描)
│  └─ appserver-gift*.mjs   ZCode Start Plan 免费额度托管
├─ tools/                   常驻证据脚本(见「复跑证据」,17 个)
├─ bridge/                  客户端自动化桥(inbox/outbox 收发任务文件)
├─ tasks/                   开发留档:每轮任务书与交付记录(含原始输出与未决项)
├─ refs/                    参考资料(**含第三方材料,见文末声明**)
├─ profile-backup/          DSH profile 配置的三态留档与恢复说明
├─ pitfalls.md              ★ 踩坑总账(每个真机坑的现场、根因、修法)
└─ dispatch/teaching.md     ★ 教学文档(引擎细节 / 登录 / 额度 / 验收清单)

四引擎的差异不会被"抹平":每个引擎在 validateSpec 里剔除自己不支持的参数 (如 Qoder/WorkBuddy 不支持 mode / memoryBench),并在面板"引擎提示"行如实说明, 而不是静默忽略。

安装

要求:DSH 桌面版(在 0.2.1-rc.2 上开发验证)+ 至少一个本机 AI CLI (ZCode / OpenCode / Qoder / WorkBuddy 任一;四个都在则全部自动发现)。

  1. 让插件目录能被 profile 解析(开发期常用 junction / link:):
# 例:把本包挂进 profile 的 node_modules
   New-Item -ItemType Junction `
     -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\@local\dispatch" `
     -Target "<本仓库>\dispatch"
  1. 在 创造模式会话里用 plugin_manager 的 install_bundle 安装(target = 插件绝对路径),

并读返回的 `application` 与 `warnings`(applied 才算生效)。

  1. 在 profile patch 里补上机器专有路径(见下一节),然后完全退出 DSH 再启动。
本包无构建步骤、无 npm 依赖;Host 半边(index.js / wire.host.mjs / core/*)改动
需要重启 DSH 才生效,Client 半边(client.js)改动刷新页面即可。

配置

插件字段

字段 · 默认 · 说明 demo · false · UI 演示模式:用内置假数据渲染面板,不触达 dispatcher maxConcurrent · 3 · 全局同时运行上限(各引擎实际并发 = min(全局, 该引擎上限)) maxConcurrentOpenCode · 2 · OpenCode 独立并发(实测:并发 opencode 进程会零输出 exit 1,故默认 2 不建议调高) maxConcurrentQoder · 3 · Qoder 独立并发(实测:3/5 个 qoderclicn 并发全部 exit=0 且结果正确) maxConcurrentCodebuddy · 2 · WorkBuddy 独立并发(保守默认,未实测;该 CLI 是 Electron 内置 bundle,比纯 CLI 重) runnerPath · '' · ZCode 执行器绝对路径(collab-kit/zcode-run.mjs);留空则不创建 dispatcher opencodeRunnerPath · '' · OpenCode 执行器(留空自动推断:env DSP_OPENCODE_RUNNER → 仓库内) codebuddyRunnerPath · '' · WorkBuddy 执行器(留空自动推断:env DSP_CODEBUDDY_RUNNER → 仓库内) ledgerPath · '' · 台账 dispatch-runs.jsonl 绝对路径;留空则跳过用量聚合 workRoot · '' · 派发器工作根目录(locks/、state/jobs.json、logs/);留空则不创建 dispatcher runnerCwd · '' · runner 子进程工作目录(通常设为宿主项目根);留空 = DSH 进程 cwd switchPath · 内置默认 · 派发总开关真值文件;留空则开关不可写 notifyOnSettle · true · 任务落地时自动唤醒发起会话(与 DSH 后台任务同款) maxConsecutiveWakes · 0 · 连续唤醒次数上限;0 = 不限 systemPromptHint · true · 向 system prompt 注入「派发台优先于 subagent」提示段

Qoder 执行器路径通过 options.qoderRunnerPath / env DSP_QODER_RUNNER 注入,默认推断到仓库内。

runnerPath 与 workRoot 任一为空则不创建 dispatcher:面板走 demo 降级,agent 工具返回可读错误。 这是刻意设计——不猜一个位置静默跑错。

「引擎自动发现」在 dispatcher 存在时默认开启,不需要额外配置;
它只读取环境/文件系统/CLI 版本,不消耗任何额度。

机器专有路径放 profile patch(不进本仓库)

上面的路径都指向某个具体宿主项目,属部署配置。请在 ~/.dsh/profiles//cordis.patch.yml 里按 id 覆盖:

- id: dispatch
  name: "dsh-dispatch"
  config:
    demo: false
    maxConcurrent: 3
    runnerPath: '<宿主项目>\scripts\collab\zcode-run.mjs'
    ledgerPath: '<宿主项目>\collab\logs\dispatch-runs.jsonl'
    workRoot:   '<本插件目录>\.data'
    switchPath: '<宿主项目>\collab\dispatch.switch.json'

(cordis.patch.yml 的 patch 层按 id 覆盖是 DSH 的标准机制——同文件里 ui-theme / ui-chat 等条目就是这么覆盖 bundle 内置条目的。)

⚠️ 报「身份验证失败 / 401」:多半是 ZCode 凭据失配

本插件不自己存凭据,而是读 ZCode 自己的 ~/.zcode/v2/config.json (provider["builtin:bigmodel-coding-plan"].options.apiKey)—— 与 ZCode CLI 同源。

已知上游缺陷:ZCode 在 OAuth 重新登录后,把新 Key **只写进加密凭据库
credentials.json、不回写 config.json,导致 `config.json` 里留着失效的旧 Key**
⇒ 一切读它的程序集体 401(连 `zcode.cjs -p …` 也一样)。

💡 处理办法:把有效 Key 保存(写回)到 `config.json` —— 一次修好所有工具:

  1. 从 credentials.json 解出有效 Key(enc:v1 = AES-256-GCM,密钥 sha256(secret));
  2. 先验活(⚠️ 网关对失效 Key 也返回 HTTP 200,body 却是

{"code":1000,"msg":"身份验证失败。","success":false} —— 不能只看状态码);

  1. 备份 config.json,把有效 Key 填回 options.apiKey;回读校验。
详细步骤、解密参考实现、验活判定代码与完整踩坑记录见
`dispatch/README.md` 的「凭据从哪来 + 报 401 怎么办」一节。
现成恢复工具见姊妹项目 dsh-connect-zcode
的 scripts/sync-key-to-config.mjs(自动备份 + 验活 + 原子替换 + 回读校验)。

免费额度(Start Plan)通道

派发台支持 ZCode Start Plan(活动赠送额度):通道 id account:-start-plan (本机实测 account:bigmodel-start-plan),面板下拉里显示为「免费额度(Start Plan)」, 也可直接当 --provider 用:

zcd dispatch --kind prompt --prompt "只回复 OK" --provider start-plan
zcd dispatch --kind task   --task <任务包> --provider account:bigmodel-start-plan --mode yolo

原理:该端点要求逐请求的官方客户端证明(直连 HTTP 会被 405 / code 3012 拦), 所以由 runner 托管 zcode.cjs app-server、自己注入套餐账户并把 token 递给它签名。 实现在 collab-kit/appserver-gift.mjs(回合驱动)+ collab-kit/appserver-gift-job.mjs (产物 / 输出行 / 台账,与既有 print 模式同形)—— 因此面板、暂停分类、重试/交接、降级链、 台账聚合全部复用,台账按 billing=zcode-plan-gift 与付费套餐分账。

和姊妹项目 `dsh-connect-zcode` 的 provider 通道怎么选?
派发台的语义是「独立 ZCode 进程 + 它自己的工具 + 独立会话/额度」—— 所以在这里
「工具由 ZCode 自己执行」是设计,不是缺陷;而那边把同一个免费额度接成 DSH 的模型对话通道,