dsh-sub-cli
English ·
安装 ·
工作原理 ·
更新日志 ·
问题反馈
在 DeepSeek Harness(DSH)中统一管理外部 Agent CLI 的插件。
- 把 Codex 与 Claude Code 两个 CLI 放到一个统一目录(默认
~/dsh-clis),不混入系统 PATH(Qwen Code 支持已于 2026-09 移除);
- 每个 CLI 用相互隔离的配置目录(
config-<cli>/),通过该 CLI 自身的环境变量指向,完全不碰你系统里已装的 CLI 配置;
- Web 插件配置卡片可配置统一目录 + 每个 CLI 的三层模型路由(Provider → 模型 → 推理强度);
- 注册 18 个模型工具,按"CLI × 模式"覆盖(只保留带后缀的入口):
- 持续会话(每个 CLI):
cli_codex_direct / cli_claude_direct(首轮;返回 sessionId)+ cli_<cli>_followup / cli_<cli>_status / cli_<cli>_sessions / cli_<cli>_interrupt;
- Relay 子代理(每个 CLI):
cli_codex_subagent / cli_claude_subagent(创建 DSH continuable 子代理,通过 managed_cli_submit 转发到真实 thread);
- 无头派发:
cli_dispatch;
- 生命周期:
cli_check / cli_install / cli_test / cli_remove(安装/检测/协议验证/移除);
- 18 = 2 direct + 2 subagent + 8 session 工具(followup/status/sessions/interrupt 各 2)+ 1 dispatch + 4 生命周期 + 1
managed_cli_submit Relay 内部工具;
cli_codex 别名已移除;无后缀的一次性工具 cli_claude_code / cli_qwen 及其 managed-<cli> provider 已删除——它们没有会话能力,且与 cli_dispatch 重复;
并发调度多个 CLI 请用 cli_<cli>_subagent:它返回子代理 id 后立刻继续,多个 CLI 天然并行跑,不需要后台任务(jobs)插件。
- 每个 CLI 都有持续会话驱动(Codex: app-server,Claude: stream-json NDJSON),并提供
cli_<cli>_followup / cli_<cli>_status / cli_<cli>_sessions / cli_<cli>_interrupt 持续会话工具;
- 注册
cli_dispatch 模型工具,让 DSH 模型无头调用外部 CLI 并回传输出。
功能
- 统一目录:所有 CLI 二进制集中到
~/dsh-clis/bin/,配置集中在 ~/dsh-clis/config-<cli>/;
- 配置隔离:启动时设置
CODEX_HOME / CLAUDE_CONFIG_DIR 指向统一目录内配置,不触碰系统默认路径;
- 三层模型路由:每个 CLI 可独立选 Provider → 模型 → 推理强度;
- 无头派发:
cli_dispatch 工具用 argv 数组执行 CLI,不用 shell 字符串拼接,处理超时、输出上限、退出码和 stderr;
- 两个 CLI 持续会话:首轮
cli_<cli>_direct 返回稳定 sessionId;后续工具直接进入同一 thread——Codex 走 app-server 长连接,Claude 走 stream-json 单次进程 + --session-id/--resume 文件级持久化——不经过 relay 模型;
- 配置持久化:统一目录与模型路由通过 settings 服务的
installSection 写入 ~/.dsh/settings.yaml,重启后仍生效。
- 会话持久化:会话注册表(含远程 thread id)写入统一目录的
sessions.json,Host 重启后 cli_<cli>_followup 直接按 sessionId 恢复并 reattach 同一 thread,无需重新创建。
- 自动补全(auto-continue):回答看起来提前收尾(只描述计划、未交付结果)时,自动在同一会话内续接追问直到拿到完整结果,单次
cli_<cli>_direct 即返回完整报告。每个 CLI 在设置卡里配置续接次数(max,默认 3;设为 0 即关闭,没有独立开关)。该机制依赖"同一 thread 的 followup",对所有持续会话式调用生效(Codex/Claude),INTENT_TAIL 正则同时识别中英文意图句。
各 CLI 内置工具能力
本插件不动 CLI 的工具——CLI 自己有什么工具就用什么。两个 CLI 在 cli_<cli>_direct / cli_<cli>_subagent 持续会话路径下实际能调用的工具:
| 文件读写(Read / Write / Edit / Glob / Grep) | ✅ | ✅ |
| Shell 命令(exec / Bash) | ✅ | ✅ |
| 内部子代理(Agent / Task / spawn) | ✅ | ✅ |
Qwen Code 支持已于 2026-09 移除:其实测可靠性不足(stream-json 不发 tool_use 事件、权限模型依赖单一配置键且被 CLI 启动时重写、多次真机运行出现瞬态失败)。托管 CLI 为 Codex 与 Claude Code 两家。
分工原则:主控联网调研,CLI 离线执行。
- 联网搜索 / 调研 / 抓 URL:由主控用 DSH 自带的
advanced_search / web_fetch / platform_search 直接完成,两个 CLI 均明确不提供联网搜索功能(2026-09 产品决策,不再折腾)。技术依据:Codex 的 web_search 与 Claude 的 WebSearch 都是模型供应商的 server-side 工具,中转商路线下基本不被执行。主控的搜索工具链完整且不受这些限制,联网任务直接用它。
- 代码工作(读 / 写 / 改 / 跑命令):两个 CLI 完全对称,按习惯选。
- 多步复杂任务:用 Relay 子代理(
cli_<cli>_subagent),让子代理持续推进。
- 如需 CLI 处理调研相关材料:主控先搜好,把材料作为任务内容派给 CLI。
设置卡的权限是单一两档下拉:只读 / 可执行(2026-09 简化,中间的"可写"档已移除——它是三档里最含糊的:Codex 在该档实际写不了文件(写路径是 exec_command),Claude 的 acceptEdits 边界又比"仅写文件"宽(实测删除命令被静默执行,见 VERIFICATION-FLOW 第十二轮发现 6))。两档语义干净:只读 = 只能看;可执行 = 能跑命令、写/删文件、装依赖。读取在两档恒为允许;档位启动时定死,无弹窗、无运行中提权,未授权的能力被触发时确定拒绝并清晰报错。文件读写仅限当前工作区内(CLI 自身沙箱把工作区外视为需提权)。
安装
在 DSH 中通过插件目录添加:
dsh plugin --profile desktop add <path>/plugin
或作为 npm 包安装:
npm install dsh-sub-cli
使用
- 打开 设置 → 插件 → 外部 Agent CLI 管理器(dsh-sub-cli);
- 填写或浏览选择 CLI 统一目录(默认
~/dsh-clis);
- 把需要的 CLI 二进制放入
<目录>/bin/;
- 为每个 CLI 选择 Provider、模型、推理强度并保存;
- 在对话中让 DSH 使用对应 CLI:日常委派用
cli_<cli>_direct(持续会话,直连)或 cli_<cli>_subagent(DSH Relay 子代理,任务通过子代理转发);需要同时调度多个 CLI 时用 cli_<cli>_subagent(返回子代理 id 后立即继续,多个 CLI 并行跑完再回报);仅在你明确要求"无头跑一次"时才用 cli_dispatch。
持续会话(所有 CLI)
首轮:
cli_<cli>_direct(description, prompt) # codex / claude
→ { sessionId, status, output }
后续(cli = codex / claude):
cli_<cli>_followup(sessionId, prompt) # 同一真实 thread
cli_<cli>_status(sessionId) # 状态、cwd、模型与权限
cli_<cli>_sessions() # 当前 Host 的会话列表
cli_<cli>_interrupt(sessionId) # 中断当前 turn
Relay 子代理(所有 CLI)
每个 CLI 都有 Relay 子代理——DSH 原生 continuable 子代理,任务通过 managed_cli_submit 转发给真实 CLI:
cli_<cli>_subagent(description, prompt) # codex / claude
→ { kind: "continuable", subagentId }
之后用 send_message 继续、interrupt_agent 中断。这些是本插件的专用续接工具,不是 DSH 原生 send_message。每个会话同时只允许一个 active turn;重叠 follow-up 会明确返回 SESSION_BUSY。
配置
cliDir | 统一目录,默认 ~/dsh-clis |
models.<cli>.provider | 该 CLI 的推理 Provider |
models.<cli>.model | 该 CLI 的模型 |
models.<cli>.reasoningEffort | 该 CLI 的推理强度 |
autoContinue.<cli>.max | 自动补全续接次数(0–10,默认 3;0 = 关闭。旧配置的 enabled:false 会归一化为 0,字段本身已废弃) |
turnTimeoutMinutes.<cli> | 静默检测点(分钟,3/5/10 三档,默认 5)。到点先探测:进程已退出交由真实结果、仍在输出则继续等并每个活跃窗口自动续期、连续静默 60 秒才判卡死 |
<cli> 取值:codex / claude。
工作原理
插件在 DSH 启动时向 Host 注册 18 个模型工具,并将每个托管 CLI 的配置目录隔离指向统一目录内的 config-<cli>/ 子目录。
会话生命周期(以 cli_claude_direct 为例):
- 安装检测(
cli_install)通过 npm 把 CLI 装到统一目录的 bin/ 下;
- 配置隔离:每个 CLI 启动时,
CODEX_HOME / CLAUDE_CONFIG_DIR 环境变量指向插件自己的配置目录,不碰系统里的同名配置;
- 首轮调用(
cli_<cli>_direct):spawn CLI 进程,首次返回稳定 sessionId;
- 续接(
cli_<cli>_followup):后续调用传入同一 sessionId,CLI 按 --session-id / --resume 重新 attach 到同一线程;
- 持久化:
sessions.json 记录每个会话的远程 thread id,Host 重启后仍可 reattach;
- Relay 子代理(
cli_<cli>_subagent):通过 managed_cli_submit 把任务转发给真实 CLI,权限由插件的两档(只读 / 可执行)统一控制。
权限两档:## 配置 中的 permissions.<cli> 档位在进程启动时定死沙箱模式(Codex -s、Claude --permission-mode),运行中不变;未授权能力被触发时确定拒绝,不弹窗、不提权。
环境变量隔离
| Codex | CODEX_HOME |
| Claude Code | CLAUDE_CONFIG_DIR |
CLI 联网搜索的最终决策(2026-09)
两个托管 CLI 均不提供联网搜索功能,此事已关闭。 联网任务一律由主控自带的搜索工具(advanced_search / web_fetch / platform_search)完成。
决策依据(详见根目录 CLI-WEB-SEARCH-RESEARCH.md 的完整调研):
- Codex:web_search 是 Responses server-side 工具,执行权在中转商——多数 chat 型中转商不执行;且旧开关形式(
-c tools.web_search=true)已是 deprecated 别名,新语义默认 cached(查索引缓存,不真联网);
- Claude:WebSearch 同为 Anthropic server-side 工具(
web_search_20250305),中转商转换时实测损坏;WebFetch 虽是本地执行但它只是抓取器,不解决"搜索发现";
- 结论:三家的搜索在我们的中转商路线上要么不可用、要么需要额外付费前提,投入产出不成立,明确降级为"不提供"。
实现层面的对应处理:
registry.js 不再向 codex 传任何 web_search 参数;verify.js 的 qwenSettings() 不再渲染 webSearch 块(盘上残留块会触发重写以清除);
- permissions 的工具映射表保留 WebSearch/WebFetcher → exec 分类——那是权限分类不是功能授予:万一 CLI 侧触发这类工具,仍由 exec 能力开关裁决(未勾选 → 确定拒绝),删映射反而会让未知工具静默放行;
- 未来若要提供"CLI 联网检索"能力,路线是纳入搜索开箱即用的 CLI(如 Gemini CLI,见调研文档第三节),而非修补这三家。
本地开发
npm test
npm pack --dry-run
参考 DEVELOPMENT.md 与 PLUGIN_REQUIREMENTS.md。
内核版本兼容
本插件运行在 DSH 宿主里,依赖 @deepseek-ai/dsh-tools / dsh-settings / dsh-typert-protocol / dsh-subagent 等内核包。内核在快速迭代,公共接口会改名或移除,所以插件对内核的依赖面刻意收窄,并由测试守住。
已核验的内核线:0.1.5-rc.1(DSH Desktop 2.0.9 内捆绑的版本,亦即当时 npm latest)。核验方式不是读变更日志(上游仓库没有),而是把插件真实装载到该内核上跑一遍:apply() 注册出 18 个工具 / 3 个 provider / 1 个 guard,Relay 子代理的执行层 allowlist 在真实内核语义下依然只放行 managed_cli_submit 与 report。
插件实际触碰的内核接口只有几处,均已逐一核对:
defineTool(dsh-tools) | 注册全部模型工具 | 存在,签名未变 |
SettingsProvider#installSection(dsh-settings) | 设置卡持久化 | 存在,两种构建都有 |
TypertRemoteService / Remote(dsh-typert-protocol) | cli 远程服务 | 存在,签名未变 |
subagents.registerProvider / startContinuable | Relay 子代理 | 存在;request.persona / request.toolFilter 仍是普通请求字段 |
ctx.tools.guard(全局 guard) | Relay 执行层 allowlist | 存在;plain-context guard 仍全局生效 |
subagents.registerContinuableSetup | 旧版 guard 通道 | 已移除——插件早已改为三通道 fail-loud,走 0.1.2+ 的全局 guard |
⚠️ 同一个版本号,两种构建的导出面不同
这是本项目踩过的一个致命坑,务必先读:
@deepseek-ai/dsh-settings 的 npm registry 构建与 DSH Desktop 捆绑构建,在同一个版本号下导出面并不相同——捆绑构建多出 installSettingsSection / settingsNamespace 两个便利函数,而 npm 构建从 0.1.2-rc.1 起就不再有它们(0.1.2-rc.1 / 0.1.5-rc.1 / 0.1.5-rc.2 实测均缺)。
后果:如果插件对这两个符号做静态 import,在桌面上一切正常,从 npm 装到裸 DSH 就在模块求值阶段直接崩——apply() 都进不去,所有工具不注册。0.1.1 就带着这个缺陷发布过,因为当时的验证全在桌面内核上做,恰好掩盖了它。
因此本项目只依赖两种构建共有的服务方法:ctx.inject(["settings"], …) 拿服务,再调 settings.installSection(...)(两边实现逐行相同)。test/kernel-contract.test.mjs 有守卫禁止重新引入静态 import。
需要留意的两个上游变动(本插件不受影响,但同族的会话分析类插件会受影响):
- 会话持久化改为句柄化:
locate / readRaw / DSH_SESSION_JSONL 已移除,会话格式升至 v3。本插件从不直接读写会话日志(它只通过 startContinuable 交给内核),因此不受影响;test/kernel-contract.test.mjs 里有一条守卫,防止后续有人把"按路径直读日志"重新引进来。
- Persona 段改名:
PERSONA_SECTION 拆成 PERSONA_PREFIX_SECTION / PERSONA_SUFFIX_SECTION。本插件传的是请求字段 persona: "<文本>",由内核自己翻译成当前段名,所以不随段名变动而失效;守卫测试断言的是这一层的"形状"而非具体段名。
test/kernel-contract.test.mjs 就是这条线的机械守卫:它在当前安装的内核上直接断言上述符号、capability 标志与 persona 映射仍然成立,并校验 peerDependencies 覆盖当前发布线。内核升级后如果它红了,不要放宽断言——先读新内核的真实契约,再改插件。同时它也是 package.json 里"依赖不要写死补丁版本"的守卫(曾经 dsh-settings 被钉在 0.1.0-rc.6,比实际运行的内核落后三个小版本)。
致谢
本项目的实现建立在他人已公开的工作之上。以下内容如实标注来源与许可证,我们对此保持充分尊重:
CLI 管理 / Relay 子代理的主参照
- dingminhua/dsh-subagent-default-model(MIT,Copyright (c) 2026 LaoDing)— 本项目的主要参照。多 CLI 注册表、argv 模板、三层模型路由、隔离配置目录、
managed_cli_submit Relay 子代理形态、DSH Web 卡片样式与发布工程,均从该项目的能力形态中提炼并独立重写。本仓库的 reference/dsh-subagent-default-model/ 即为该项目的归档实现,仅在本地开发期作为对照,不随包发布。
外部 CLI 派发的可行性参考
- MJorgin/dsh-agent-conductor(MIT,Copyright (c) 2026 MJorgin)— 在 DSH 会话里把任务派给 11 种外部 Agent CLI 的
subprocess.spawn 无头执行范式;本插件从中提炼出 argv 数组派发、超时与错误回传、退出码处理的实现细节。
协议续接调研(不进入默认链路)
- wujfeng712-ui/codex-bridge(MIT)— Responses API ↔ Chat Completions 双向转换与
previous_response_id 续接的备选协议路径;本项目仅在调研期记录其设计,未在主链路中引用,亦未引入其源码或二进制依赖。
说明
以上项目的版权归各自作者所有。本项目采用借鉴设计思路 + 独立实现的方式,未整体复制任何参考项目的源码;关键模块均为独立编写,并在源文件头部注释中标注了所参考的具体项目与模式。若你发现本项目的标注有遗漏或不当之处,请提交 issue,我们会立即更正。
第三方开源依赖
本项目参考的开源项目、其许可证与合规说明,完整记录见根目录的 THIRD_PARTY_NOTICES.md。引入新的外部依赖或复用其他项目代码时,请同步更新该文件并遵守对应许可证要求。
更新日志
完整版本与变更记录见 CHANGELOG.md。最近三次发布:
- 0.1.2 (2026-09-10) — 紧急修复:0.1.1 在裸 DSH 上加载即崩(
dsh-settings 的 npm 构建缺 installSettingsSection,桌面捆绑构建才有)。改为走两种构建共有的服务方法 installSection;守卫测试增至 10 条并修掉「假绿」条件。
- 0.1.1 (2026-09-10) — 内核 0.1.5-rc.1 对接核验:确认本插件无需适配性改动(依赖面 6 处全数可用),新增内核契约守卫测试、修掉 devDependencies 落后三个小版本的钉版漂移。
- 0.1.0 (2026-09-05) — 首次发布:Codex + Claude Code 双 CLI 持续会话、Relay 子代理、无头派发、配置隔离、auto-continue;Qwen Code 支持移除;权限收敛为两档(只读 / 可执行)。
- 详见 CHANGELOG.md 内「Added / Changed / Fixed / Removed」各小节。
Licence
本项目采用 MIT 许可证,版权归属:Copyright (c) 2026 LaoDing。详见 LICENSE。