返回目录
Agent 与会话 插件

dsh-subagent-codebuddy

flg1217/dsh-subagent-codebuddy

CodeBuddy CLI ACP 子代理插件:dsh subagent provider + subagent_codebuddy 工具

Stars
1
Forks
0
Issues
0
更新
1 天前

PROJECT TOPICS

项目标签

INSTALL REFERENCE

安装参考

未验证
dsh plugin --profile web add github:flg1217/dsh-subagent-codebuddy

该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。

PROJECT README

README

dsh-subagent-codebuddy

CodeBuddy Code CLI 模型提供方 — DeepSeek Harness (dsh) 插件 把腾讯 CodeBuddy CLI 以 ACP (Agent Client Protocol) 接入 dsh,注册为 codebuddy LLM provider: 主代理可直接在模型选择器选用 CodeBuddy 模型,通用 subagent 工具也可委派 CodeBuddy 子代理。 完全遵循 dsh 官方扩展机制,零源码改动。

一、这是什么

codebuddy 是一位完整的模型供应商

能力 说明
主代理模型 模型选择器出现 CodeBuddy 分组(模型目录 = codebuddy --help 解析 ∪ 配置默认模型),选中后该轮由 CodeBuddy CLI 全权驱动
子代理 provider 通用 dsh subagent 工具可传 provider: codebuddy 委派(配合 subagent-model-selection 设置);进程内 child agent、可并行、send_message 续聊
模型目录 adapter 实现 listModels()(带缓存、永不抛错),主选择器与 list_subagent_models 共用
推理强度 CLI --effort 档位(low / medium / high / xhigh / max / ultracode)暴露到 dsh 模型选择器;子代理可用 reasoning_effort 参数指定
图片输入/读取 输入:图片块读字节后以 ACP 原生 image 内容块(base64)随 prompt 发送(promptCapabilities.image,不落盘);读取:模型用 dsh 的 read_image,图片以 MCP image 内容块回到 CLI 侧模型、同时进 dsh 会话出图片卡片;历史种子与子代理转录里的图片经 CodeBuddy blob(内容寻址)双向原生转换,两侧均可预览
子代理可视化 codebuddy 轮里的 Agent 委派镜像为 dsh 子会话parentSession 血缘 + subagent/descriptor),侧边栏可点开完整转录(消息/工具/思考/任务),运行中近实时跟随
任务/todo 走 dsh 原生 todo_write(工具桥一等公民,主轮与子代理会话都复用 dsh 的 TodoPanel 渲染);CLI 侧 TaskCreate/TaskUpdate 已随内置工具禁用,todo-bridge 保留作旧会话重放兼容
工具桥(MCP / delegate) 把本会话可见的 dsh 工具暴露给 CodeBuddy,使它的文件/命令类能力走 dsh 管线(审批/沙箱/审计/后台面板)。默认 mcp:dsh 起 HTTP MCP server,工具以 mcp__dsh__<名> 一等公民呈现(完整 JSON Schema 强约束);delegate 为旧通道(dsh_<名> 合成 DelegateTool),保留作回退
opt-in 工具 subagent_codebuddy + list_codebuddy_modelsregisterSubagentTools: true 开启;默认关闭,推荐通用工具)
压缩归属 codebuddy 会话的压缩由 CLI 负责(dsh 的自动压缩被插件接管,不压镜像);CLI 压完之后镜像成 dsh 的标准压缩卡,零 token(见 §三「压缩归属」)

语义说明(主代理轮,重要):ACP CLI 自带 agent 循环,但它的内置工具已全部禁用 (spawn 传 --tools "";delegate 模式只放行委托工具合成器 DelegateTool,见 §三「工具桥」)。 因此:

  • 模型的全部工具面 = dsh 侧工具(MCP 模式下是 mcp__dsh__bash / mcp__dsh__edit / mcp__dsh__read_image …,delegate 模式下是 dsh_bash / dsh_edit …)。这些调用 在 dsh 侧执行,受 dsh 的沙箱与审批约束,并进会话日志、审计与后台任务面板。
  • 读图片走 dsh 的 read_image:图片既作为图片进入 CLI 侧模型的上下文(MCP image 内容块), 也进 dsh 会话出图片卡片(read_image 是 dsh 原生工具名,UI 按名字渲染图片卡)。
  • 文本/思考/工具卡片仍以会话事件实时回传 dsh 界面。

为什么全禁而不是白名单(2026-09-18):CLI 原生工具要进 dsh 会话,必须在会话 scope 注册 「回放镜像」承接;镜像注册一旦丢失,该会话的原生调用就永久报 unknown tool "cli_read" (偶发、不可复现)。全禁后这条路径不再被触发。 逃生口:extraArgs 排在 --tools 之后,可用 extraArgs: ['--tools', 'Read'] 覆盖本策略。 历史注记:更早的版本让 CLI 用自己的工具链、dsh 沙箱不参与——安全模型以本节为准

二、快速开始

系统要求

  • dsh >= 0.1.3-alpha.2(验证于 0.1.3-alpha.2)
  • CodeBuddy CLI 已安装并登录(子进程继承登录态)
  • 需运行在webServer 服务的 profile(如 web):bridgeMode: mcp 依赖它挂 MCP 端点;拿不到该服务时端点不注册,--mcp-config 不注入(不会报错,但工具桥不可用)

安装插件

node scripts/link-profile.mjs            # 默认装配进 web profile
# 或等价于:dsh plugin --profile web add <本仓库目录>

插件行由插件自带的 cordis.patch.yml(bundle patch)自动注入,默认配置开箱即用。 需要覆盖默认值时,在 profile 的 cordis.patch.yml 用同 id 覆盖:

- id: subagent-codebuddy
  config:
    model: <其他模型>          # 默认 deepseek-v4-flash
    registerSubagentTools: true # 需要 opt-in 工具时开启
    bridgeMode: delegate        # 工具桥回退到旧通道(默认 mcp)

设置面板(设置 → 插件 → CodeBuddy)也可改模型与工具桥开关,表单值优先于本文件

重启 dsh web 后:模型选择器出现 CodeBuddy 分组。

主代理使用

新会话 → 模型选择器 → 选中 CodeBuddy 的某个模型(如 deepseek-v4-flash)→ 正常对话。 该轮由 CodeBuddy 驱动;它的文件/命令类工具经工具桥回到 dsh 执行(见 §一「语义说明」), 步骤/文本实时回传 dsh 界面。

子代理委派(通用工具)

  1. 设置 → 子代理模型选择,把 codebuddy 路由加入允许列表(新顶层会话生效):
    subagent-model-selection:
      enabled: true
      allowedModels:
        - provider: codebuddy
          model: deepseek-v4-flash
  2. 主代理调 subagent 工具,传 provider: codebuddymodel: <id>(model 为精确 id, 目录见 list_subagent_models 或主模型选择器)。

动态模型选择

  • adapter listModels():从 codebuddy --help 实时解析支持列表(成功缓存 10 分钟), 配置默认模型始终并入目录,CLI 不可用时回退配置模型、provider 仍可选择。
  • opt-in 的 list_codebuddy_models 工具提供同样数据的文本视图。

配置项

默认 含义
command codebuddy 可执行文件(Windows 自动解析 npm cmd-shim → node <真实CLI>
model deepseek-v4-flash 默认 CodeBuddy 模型 ID(子代理委派缺省值;主代理选择器另选)
permissionMode bypassPermissions --permission-mode:CodeBuddy 工具调用自动放行
extraArgs [] 追加的 CodeBuddy 参数
providerName codebuddy LLM provider 路由名
toolName subagent_codebuddy opt-in 工具名
registerSubagentTools false 是否注册 opt-in 委派工具(推荐用通用 subagent
bridgeMode mcp 工具桥模式:mcp = dsh 起 HTTP MCP server、工具以 mcp__dsh__<名> 呈现(完整 schema,推荐);delegate = 旧 DelegateTool 通道(dsh_<名>,回退用)。设置面板有同名开关,切换对新回合生效
longToolCapMinutes 30 静默长工具硬顶(分钟,0 = 关闭):只影响发起后零事件的工具段(任何中间进展都会重置计时);超顶中止本次调用并自动续跑——防止 CLI 卡死时进程泄漏、子会话回合悬空
tailQuietSeconds 5 尾巴窗口静默阈值(秒,0 = 关闭):干净收尾后继续抽流,等 CLI 后台任务完成后的自发续跑
tailBgQuietMinutes 10 起了后台任务的回合的静默阈值(分钟)
tailCapMinutes 30 尾巴窗口硬顶(分钟):后台任务最长可拖着回合不闭合的时长

三、工作原理

主代理轮:  会话模型选择器 → codebuddy/<id> → CodebuddyLlmAdapter
              └─ spawn codebuddy --acp --tools "" [--mcp-config <会话专属配置>]
                   → initialize → session/new|load → session/prompt
                   └─ session/update(思考/文本/工具)→ 写入调用方已打开的 step
              └─ 工具调用回流:CLI 调 mcp__dsh__<名> ──HTTP JSON-RPC──▶ dsh MCP 端点
                    └─ 伪装成工具调用块灌进 dsh loop → 原生执行(审批/沙箱/事件/UI 卡片)
                         └─ tool/result 回填 MCP 响应 ──▶ CLI 拿到结果继续
               子代理轮:  通用 subagent(provider: spawn) → child agent → 同一 adapter

工具桥(MCP / delegate)

CLI 内置工具已全部禁用(--tools "",见 §一「语义说明」),模型的一切能力必须由 dsh 提供。 插件把当前会话可见的 dsh 工具暴露给 CLI,两条通道:

mcp(默认) delegate(回退)
呈现 mcp__dsh__bash / mcp__dsh__edit dsh_bash / dsh_edit …(合成 DelegateTool)
参数约束 各工具完整 JSON Schema(协议下发,强约束) 无结构 input: object(模型端几乎零约束)
传输 dsh 起 HTTP MCP server,CLI 每回合以 --mcp-config 连接 ACP session/request 反向调用
工具集 tools/list 每次现取会话可见工具(不缓存) 回合开始前批量注册

MCP 端点的安全约束:仅 loopback 来源 + URL 携带每进程随机 key(双重校验),挂在 dsh 自带 webserver 上(与 Web UI 同端口),不额外开监听;tools/call 另按 tools/list 的同一份 工具面校验可见性。会话专属配置写在系统临时目录,权限 0600,超龄自动清扫。

客户端放弃后的结果补投(交互式工具的关键路径):CLI 侧一旦按超时/断连放弃一次 tools/call,它的模型就永远看不到结果,而 dsh 侧不会因此中止已经注入 loop 的调用。 对交互式工具这是致命的:用户在 ask_user_question / exit_plan_mode 上作答之后,回答送达的是一个已经消失的调用——界面上"提问结束就完了", 不会有任何后续。所以端点把这类结果补投进会话:客户端确实断连过(响应未写完就 close)且调用最终完成时,把结果作为一条 notice 投进会话;会话空闲就唤起一轮(CLI 被 重新 prompt,模型据此继续),忙则排队等下一步。只在客户端确实断连时才投递——那时 CLI 一定没收到响应,所以这不是重复上报。同理,转发兜底超时那句"不要盲目重跑同一命令" 也会补投:它正是防止同一副作用跑两遍的关键,丢在一个已经消失的响应里等于没说。

诊断落盘:桥的关键诊断(客户端等待时长、兜底超时、回落直执、补投失败)除 console.error 外还会追加到 ~/.dsh/codebuddy/mcp-bridge.log。控制台日志会随终端滚掉, 而"CLI 到底有没有放弃这次调用"必须可事后核对——其中打印的客户端等待时长就是 CLI 侧工具超时 T_client。pump.tsMCP_CALL_TIMEOUT_MS 若要收紧必须以这个值为据, 不要拿尾注里那句未经验证的 30s 反推(曾这样推成 25s 并造成回归,有回归用例钉住)。

  • 事件写入:adapter 检测调用方(agent-loop)已打开的 turn/step,把 ACP 的 思考/文本/工具事件直写进该 step(tool/call 前先以 assistant/message 广告, 严格满足 dsh 会话格式 v2 关系校验);辅助调用(压缩/标题,带 purpose) 或没有打开 step 时退化为纯 chunk 流,不写会话。
  • 历史原生转换:已有对话切换到 codebuddy 时,折叠后的 dsh 历史由转换器写成 CodeBuddy 原生会话文件(~/.codebuddy/projects/<slug>/<sessionId>.jsonl: user/assistant 消息 + function_call/result 记录),再 session/load 载入—— 历史以原生消息进入 CodeBuddy,而不是压成一段提示词文本。载入失败自动回退 "新会话 + 全量提示词"。单条消息的新会话(如子代理首次委派)直接走 session/new
  • 会话续跑:同一 dsh 会话映射到同一 ACP sessionId(session/load 回放复用, 映射持久化在 ~/.dsh/codebuddy/conversations.json,服务重启可恢复); 可重试失败(静默空跑/进程退出/超时)自动恢复续跑,用尽才显式报错; 切换其他模型期间的缺失轮次在续聊时自动补发。
  • 假死防御:进展性 update 重置动态空闲阈值;工具在途期间暂停计时 (ACP 工具无心跳,完成即重新起算);静默超阈值先 session/cancel、5s 后 kill。
  • CodeBuddy 的模型、网络与配额由 CodeBuddy 侧负责,插件只做桥接。

压缩归属(重要)

codebuddy 会话的压缩由 CLI 负责,dsh 不参与。 这不是可选项,是架构约束:dsh 只是渲染层, 真实上下文在 CLI 自己的会话文件里;而 dsh 的压力测量以 CLI 上报的 usage 为基线,它唯一能压的 却是 dsh 侧的镜像消息面 —— 压完压力不降 → 下一个 step 再触发;compaction-basic 的收缩闸门 (summary is not smaller than the shadowed content)在镜像面只剩旧摘要时必然拒绝,于是变成 压缩风暴(短时间内连打 20+ 次 compaction/startcompaction/end(error)), 偶发成功的那几次还会把镜像面替换成摘要、把模型带偏。

实现方式(零 dsh 源码改动):

路径 行为
自动压缩(agent/pre-step 的 pressure / agent/request-error 的 context-overflow) 插件在 ctx.compaction 服务实例上接管 compactIfNeeded;codebuddy 路由的会话一律返回 null
手动 /compact per-agent 命令覆盖 → 转发给 CLI(跑在 runMaintenance 相位里,压缩期间消息按原生行为排队)
compactNow 兜底 per-agent 覆盖没挂上时全局命令会走到它 → 同样接管,不压镜像
CLI 自己压完之后 compact-mirror 把这次压缩镜像成 dsh 的一次压缩事务 → 界面出现标准压缩卡。零 token:照抄 CLI 的摘要原文,不调用任何模型,只有本地文件读取 + 日志写入

镜像的两个时机与一条去重规则:

  • 时机:挂在 agent/pre-step(CLI 在 step 之间压的) agent/turn-stopping(CLI 在 回合收尾压的,最常见)。只挂前者的话,回合收尾那一次本轮再没有下一个 step,压缩卡与 上下文容量都要等到下一个大轮才出现。两个时机都在 turn/end 之前,turn 归属天然正确。
  • 去重:dsh 自己发起的那次压缩(手动 /compact 转发)不再镜像——命令回执已经报过它, 再补一张压缩卡就是同一次压缩的两条消息。判定按时间线而不是"第几条摘要":CLI 的摘要是 惰性落盘的(手动压缩的摘要可能十几分钟后才写出),期间镜像层会先看到一条更早的、与本次 无关的摘要,而它恰恰会落在手动压缩回执的旁边——用户看到的两条正是这一对。30 分钟 TTL 兜底, 转发没有产出时不会永久静音镜像。

判据是会话最新一次请求的路由 provider(不是持久化的会话映射)——把 codebuddy 会话切回别的 provider 后,dsh 会恢复正常压缩。

已知限制:镜像时机与容量刷新

  • 压缩卡必然出现在一轮对话的尾部。ACP 协议不报压缩事件(session/update 只有 agent_message_chunk / agent_thought_chunk / tool_call / usage_update 等类型, 没有 compactsummary)。CLI 的压缩发生在 ACP run 内部(收到 prompt → 检查压力 → 压缩 → 落盘 → 处理 → 流式返回),对 dsh 完全不可见。镜像层只能 tail CLI 的会话文件 (~/.codebuddy/projects/<slug>/<acpId>.jsonl 里的 {"type":"summary","providerData":{"source":"periodic"}} 记录),而该记录的落盘是惰性的。所以卡片最早只能在 turn-stopping(turn 收尾)弹出—— 这是 dsh 能读到新摘要的最早时机。除非 CLI 在 ACP 协议里增加压缩通知,否则无法更早。
  • 上下文容量环(ContextMeter)不会在压缩时立即下降。容量环显示的是 contextPressure 投影的 projectedTokens,而 pressureTokens 只在 CLI 上报 usage chunk 时更新。压缩镜像是纯逻辑写(零模型调用、零 usage 事件),不产生 usage → 容量环停在 最后一次请求的值,直到下一轮请求上报 usage 才降到新值。这是设计后果,不是 bug。

压缩卡不会隐藏或删除任何历史消息:被遮蔽的消息照常渲染,dsh 的历史接口不做过滤。 压缩改的是 dsh 的 surface(上下文压力表,以及插件需要重建 CLI 上下文时的素材 buildPrompt / 原生 seed)。

启动自检与告警(不会再静默失效):插件装载时自检接管是否真的落到实例上,dsh 升级若改了 压缩入口,控制台会出现 [subagent-codebuddy/compact] …未生效/未安装 的 warn;正常接管时每个 codebuddy 会话播报一次 已接管 <sessionId> 的 dsh 压缩;镜像成功时播报 [subagent-codebuddy/mirror] 已把 CodeBuddy CLI 的压缩镜像到 dsh 会话 <id>…

四、参与开发

pnpm install        # 安装 typescript + vitest(PowerShell)
pnpm build          # tsc 编译 → lib/(产物随仓库提交,免构建安装)
pnpm test           # vitest 单元测试
pnpm typecheck      # tsc --noEmit

本仓库的 pnpm install 只在开发机上可用package.jsondevDependencies 用了 link:<绝对路径> 形式指向 dsh 源码树(本地联调需要)。 换机器前需把它们改成正常的版本号(如 >=0.1.3-alpha.2)或 workspace:*。 仅使用插件(不构建)的人不受影响——运行时 lib/ 只依赖相对路径与 peerDependencies。

分发与发布

分发走 git(不做 npm publish):使用者把本仓库目录加进 profile 即可,lib/ 已随仓库提交。

发布前检查:

  1. pnpm typecheck && pnpm test 全绿;
  2. pnpm buildlib/src/ 同步git statuslib/ 的改动应与 src/ 一致—— 否则使用者拿到的是旧产物);
  3. README 的配置项表/能力表与实际 Config 一致;
  4. git status 无遗留文件;
  5. git add -A && git commit -m "..." && git push origin master

五、许可证

MIT

CLASSIFICATION EVIDENCE

分类依据

项目类型插件
功能分类Agent 与会话
规则置信度

系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: subagent。