前言¶
在 DSH 里编排智能体时,经常需要“先确认条件再动手”:上一轮是否报错、用户是否确认了某个方案、测试是否通过。把判断写进提示词,判断过程会占用主上下文;判断服从规则、命令参数等还可能改写后续请求的缓存前缀。dsh-if 把这件事收成一条自定义 /if 命令:判断交给一个继承当前会话上下文的 fork 子代理,条件成立时才把 /if 下方的指令作为一条普通用户消息发给主 agent,不成立则什么都不做。
下面介绍它的能力、安装、用法与需要注意的约束。
这是什么¶
- 插件名:
dsh-if,仓库tlewa/dsh-if,npm 包名dsh-if。 - 维护者:TLEWA,许可证 MIT。
- 分类:工作流;社区目录页登记为 GitHub 1 stars。
- 定位:给 DeepSeek Harness 加一条
/if斜杠命令,用 LLM 判断一个条件,成立时才发送指令。
判断器是一个 fork 子代理,继承当前会话的工具、system prompt 与消息历史,只在末尾追加一条判断指令。判断请求与主会话下一次请求共享逐字节一致的 tools → system → messages 前缀,因此可以复用 provider 的 prompt cache / KV cache。
涉及英文原文的表述摘自该插件的 GitHub README;目录页与维护者信息来自其社区目录条目。
核心功能¶
下面按 README「功能」一节逐条列出,均为已核实能力。
- 上下文条件判断。判断器继承当前会话的工具、system prompt 与历史消息,按条件回答
YES或NO。 - 复用 KV 缓存。fork 子代理只追加一条尾部 user 消息,前缀与主会话一致,可命中 provider 前缀缓存。
- 只读判断。工具目录保持不变,执行期 guard 只放行
read/glob/grep/web_search/web_fetch/skill等只读工具,写入、修改、执行命令、子代理一律拒绝。 - 与普通消息同队列。
/if作为一条普通消息进入输入框上方的队列面板,可编辑、删除、↑ 插话;回合开始时才判断,成立就把这条消息原地换成指令。 - 两种排队模式。默认
message,即与普通消息同队列;background保留 0.2.x 行为,命令立即返回,判断在后台跑。 - 条件成立才发送。只有
YES才把指令作为用户消息发给主 agent;NO完全不触发主 agent。 - 判断不进上下文。判断在独立子会话完成,不写主会话事件,不调用
agent.inject。 - 命令不泄露参数。
recordInput: false,log-only 的command/run也不记录条件或指令。 - 多行条件。第一行是条件,其余行是指令;条件有多行时用单独一行
---分隔。 - 自动回退。host 没有可继承上下文的 fork provider 时,回退到一次性
ctx.llm.stream+ 约 24k 字符纯文本记录。 - 防止误发。条件为空、指令为空、判断输出不是
YES/NO、或判断超时,都返回错误且不发送。
安装与启用¶
环境要求:
- DSH
0.2.1-rc.5或更新版本。 - Node.js
20.3或更新版本(使用AbortSignal.any/AbortSignal.timeout)。
从 npm 安装(推荐)¶
- 打开侧边栏「插件」页 →「安装插件」。
- 输入包名并确认:
dsh-if
- 安装完成后重启 DSH(host 插件在 profile 启动时加载)。
固定版本:
dsh-if@0.3.4
命令行等价写法:
dsh plugin --profile desktop add dsh-if@0.3.4
从 GitHub 安装¶
lib/ 是可直接运行的源码,不需要本地构建:
https://github.com/TLEWA/dsh-if
固定到标签或 Release 附件:
dsh plugin --profile desktop add github:TLEWA/dsh-if#v0.3.4
https://github.com/TLEWA/dsh-if/releases/download/v0.3.4/dsh-if-0.3.4.tgz
从源码安装¶
git clone https://github.com/TLEWA/dsh-if.git
cd dsh-if
npm test
dsh plugin --profile desktop add .
重启 DSH 后,在输入框输入 /if,斜杠面板会出现该命令。
典型用法¶
基本形式:第一行是条件,其余行是指令。
/if 1+1=2
请只回复 OK
条件本身有多行时,用单独一行 --- 分隔:
/if 用户已经确认方案 A
并且测试已经通过
---
请开始执行方案 A
也可以只写一行条件和一个单行指令,/if 后面的第一个换行就是分隔点。
分隔符优先:只要出现过单独一行的 ---,它右边的部分一定全是指令。所以指令正文里不要单独成行写 ---(Markdown 水平线),否则它前面的内容会被当成条件的一部分。
判断语义¶
- 判断由当前会话配置的模型完成,不是 JavaScript 或数学求值器。
1+1=2只是最小示例,条件可以是“用户是否已经确认方案”“上一条回复是否包含错误”等。 - 判断器只被要求输出
YES或NO;输出里没有可识别结果时按判断失败处理,不发送指令。 - 判断器可以只读地查看文件、检索代码来确认事实,但不能做任何修改。
- 条件需要的信息在当前上下文里找不到、或条件含糊无法确定时,判为不成立:宁可不动,也不误发指令。
- UI 只显示通用结果,不回显条件、指令或判断原文。
工作方式¶
README 把实现拆成九步,摘要如下。
- 通过
ctx.effect(() => ctx.commands.register(...))注册/if,并设置recordInput: false;注册挂在插件 fiber 上,停用时一并注销。 - 入队。
message模式把原文/if <条件>\n<指令>作为普通 user 消息agent.followup()到会话队列,进的是宿主 durable inbox,客户端排队面板照常显示、可编辑、可删除、可插话。 - 轮次边界拦截。回合开始时宿主按
agent/pre-stepwaterfall 领取这批消息,插件把以/if开头的条目挑出来先在原地判断:成立就把 content 换成指令,不成立就从批次删除。判断失败时该条同样删除,并在会话里记一张「【/if 未执行】」错误命令卡片,可用卡片右侧「取回 /if」一键放回。 - 首选 fork 子代理判断。用
ctx.subagents.getProvider('fork')取 provider(要求inheritsParentContext === true),再以startActivation({ provider: 'fork', childId, request: { parent, agentOptions, prompt }, delivery: 'caller' })启动判断子会话。 - 丢掉继承来的待办。fork 的种子会带回父会话入队的 next-turn 消息和未领走的 next-step 插话,插件在拿到子 agent 时把这两条队列中除自己判断 prompt 以外的待办全部
remove,父会话队列不受影响。 - 执行期只读限制。
ctx.tools.guard仅对判断子会话生效,只读工具放行,写入、命令、子代理被拒;工具目录不裁剪,缓存前缀不变。 - 判断超时 60 秒;只有
YES时把指令作为带id和role: 'user'的已识别消息交给主 agent。 - 回退路径。宿主没有可继承上下文的 fork provider 时,
historyTranscript()读取agent.session.deriveMessages(),只保留 user / assistant / tool 消息,生成最多约 24k 字符纯文本记录(每条最多 3k 字符,保留头尾),再用ctx.llm.stream()发起一次性判断请求;该请求不带主会话sessionId,命中不了主缓存。 background模式跳过上面的入队与轮次拦截两步,命令立即返回“已排队”,判断由后台任务完成;多个/if按发送顺序串行判断。
配置¶
配置写在 profile 的 cordis.patch.yml:
# profile 的 cordis.patch.yml
- id: if-command
name: 'dsh-if'
config:
mode: message # 默认。background = 0.2.x 的老行为
mode: message(默认):/if先作为普通用户消息排队,轮次开始时再判断。条目内容是/if <条件>+ 指令原文,出现在队列面板里,编辑它即等同于改条件与指令。mode: background:0.2.x 行为。命令立即返回“已排队”,判断交给后台任务,不占队列位置。判断失败时会在会话里记一张if命令卡片说明原因。
也可以不改文件:插件 → dsh-if → 配置 里有「判断方式」下拉框,切换即时生效(mode 声明为 volatile 字段,设置页直接改写运行中的配置)。插件用 Config(@deepseek-ai/schemastery)描述该字段,mode 缺省即为 message,未知字段被忽略。
KV 缓存与上下文隔离¶
这部分是刻意的设计约束,不是可选项。
/if命令、条件、指令、判断 prompt、判断结果都不写入主会话;command/run和command/done都是 log-only 事件。- 判断在 fork 子代理里完成,tools、system prompt、历史消息全部继承,只在末尾追加一条 user 消息,因此前缀与主会话下一步逐字节一致,provider 的 prefix cache / KV cache 可以直接复用。
- 判断子会话是独立会话,不追加主会话的 session 事件,只读历史;工具目录不裁剪,裁剪反而会改变前缀、破坏缓存。
- 代价是透明的:每次判断都会把整段上下文重发一次,token 计数按全量算,命中缓存的部分按 provider 的缓存价计费;会话越长,单次判断的绝对 token 数越大。
- 条件不成立时主会话没有任何变化;条件成立时唯一改动是在历史末尾追加一条普通用户消息,已有前缀不变,不会调用
agent.inject,也不会编辑、替换或删除已有 session 事件。
适用场景与注意¶
适合需要在主流程里插入条件门控的场景:方案确认后执行、测试通过后再继续、上一轮有错时再补一条指令。判断会消耗一次上下文重发,长会话下绝对 token 数不小,适合低频、条件明确的门控,不适合把每个判断都铺到高频路径。
注意两点:
- 插件以当前 dsh 进程的权限运行,相关判断逻辑在
lib/中是可直接运行的源码,安装前建议检查源码与许可证;该插件为 MIT 许可。 - 判断依赖宿主提供的 fork provider 与
ctx.tools.guard,缺失时自动回退到 transcript 判断,该退路命中不了主缓存。其他 host 能力边界请对照 README 的「兼容性」一节。
结尾¶
dsh-if 的价值在于把“判断条件”和“执行指令”分开:判断发生在独立子会话,不污染主上下文,成立时只追加一条普通消息,从而保住主请求的可复用前缀。它定位明确、约束写在明面上,适合需要条件门控的 DSH 工作流。
- 目录页:https://www.skillhub.cn/plugins/TLEWA/dsh-if
- GitHub:https://github.com/TLEWA/dsh-if
社区目录是独立站点,与 DeepSeek、幻方无官方从属关系。