dsh-auto-plan-executor
DSH 自动方案执行插件:四条件触发、方案冻结、任务图与步骤账本、门禁预算、完整度闸门与八部分报告。
- Stars
- 0
- Language
- JavaScript
- Created
- Oct 22, 2026
- Updated
- Oct 22, 2026
Introduction
dsh-plugin-auto-plan-executor
DSH(DeepSeek Harness)的自动方案执行宿主插件。
当真实用户提交一份方案时,该插件把当前会话切换到方案执行模式:冻结原方案、建立任务图、 受控连续轮次执行、门禁与预算守卫、独立验收,最后产出一次性完整报告。普通聊天保持原样。
目标宿主:0.2.0-rc.2。包名:dsh-plugin-auto-plan-executor。宿主行 id:auto-plan-executor。
现状(2026-10-19):源码、单元测试、装配验证与双插件共载联调均已完成;插件已装入日常
webprofile——bundles=14、dependencies为link:。本轮新增的步骤账本 / 派生问题 / 边界复核 / OpenViking 实时镜像 / 收尾退出方案模式属于宿主插件代码改动,必须在重启 DSH 后才会生效 (ESM 模块缓存按文件 URL 记,禁用→启用只会重放旧模块)。仍未接线的能力见下文「已知缺口」。
它做什么
| 能力 | 实现位置 | 运行时是否接线 |
|---|---|---|
| 四条件触发 + 明确否定优先 + 去重 | lib/trigger.js | ✅ 已接线(agent/pre-step) |
| 原方案冻结(哈希、修订、不可变核心) | lib/plan-snapshot.js | ✅ 已接线(激活时冻结一次) |
| 任务图(环检测、缺依赖、同目标并发写) | lib/task-graph.js | ✅ 已接线(auto_plan_task,含 start / list 的就绪判定) |
| 状态机与持久化(原子写、脱敏在 store 边界) | lib/plan-state.js | ✅ 已接线(schema v3:步骤账本字段) |
| 方案步骤清单(原文提取 + 逐字 declare + 受限降级) | lib/plan-steps.js | ✅ 已接线(激活时冻结;需求/交付/验收段也提取) |
| 逐步处置(DONE / OPEN / EXEMPT,收尾重验) | lib/step-coverage.js | ✅ 已接线(收尾时重算,不信持久化 verified;未关闭问题阻塞 DONE) |
| 步骤账本(派生问题 / 生命周期 / 边界复核 / 回退) | lib/step-gate.js | ✅ 已接线(auto_plan_step 的 begin / issue / close_issue / review) |
| OpenViking 实时镜像(写/读/清理,失败不阻塞) | lib/ov-client.js | ✅ 已接线(激活时写清单,每个步骤事件同步一次) |
| 完整度闸门(缺口拒绝收尾 + 缺口任务 + 收敛上限) | lib/completeness.js | ✅ 已接线(auto_plan_finish) |
| 门禁分级 G0–G4 + 决策落盘 | lib/policy.js | ✅ 已接线(auto_plan_gate) |
| 费用守卫(单次/累计 2 元,不可核价即拒绝) | lib/budget.js | ✅ 已接线 |
| 附件读取(白名单、字节/字符/时间上限) | lib/attachment-reader.js | ✅ 已接线 |
Goal 能力发现(按可观测的 goal 工具 + goals 服务) | lib/goal-adapter.js | ✅ 已接线(并如实报告在 auto_plan_status) |
| 独立验收(只认观测证据) | lib/verifier.js | ✅ 已接线(auto_plan_task action="verify") |
Cordis 装配、Prompt 段落、工具、tools.guard | lib/index.js | ✅ 已接线 |
| 与待确认插件的只读观察 | lib/confirmation-adapter.js | ⚠️ 部分:能力发现已接线并在状态里报告;镜像(confirmationMirror)当前没有运行时写入方,所以 confirmations 恒为 []。见下方"已知缺口" |
| 最终统一报告(八部分) | lib/reporter.js | ✅ 已接线(auto_plan_finish 收尾:验收 → 终态 → 报告落盘) |
| Goal 目标文本 | lib/goal-adapter.js | ✅ 已接线并在收尾时产出(auto_plan_finish 返回 goalHandoff) |
触发条件(四选一)
- 用户明确说「按照方案执行」「按这个方案实施」等;
- 用户明确说明当前提交的是方案(「这是我的实施方案」);
- 本轮真正上传的附件文件名包含「方案」;
- 本轮消息首部或可读取附件正文的标题为「××方案」。
优先级例外:同轮出现「不要执行/仅审阅/只是讨论」等明确否定,或本会话仍有未关闭的 待确认事项时,一律不触发。助手输出、工具结果、Goal 合成轮次不会触发。
与 dsh-confirmation-resolution 的边界
- 本插件拥有方案生命周期与自己的状态文件;
- 待确认插件独占确认项账本;
- 本插件没有任何
register/decide/complete/approve路径,也不导入其私有ledger.js; - 不伪造用户回复、不制造 C 项、不假设 V2 已启用。
安装
dsh plugin --profile web add C:\Users\a1941\Desktop\DeepSeek\dsh-auto-plan-executor
dsh --profile web --dump-config
--dump-config 需要写入 $DSH_HOME/profiles/web/cordis.yml,因此必须在不被沙箱限制的终端执行。
配置
行配置(cordis.patch.yml 已给出默认值):
| 字段 | 默认 | 含义 |
|---|---|---|
enabled | true | 总开关;false 时完全不介入普通对话 |
sectionName / order | auto-plan:policy / 118 | Prompt 段落名与顺序 |
budgetLimitCny / perOperationCny | 2 / 2 | 累计与单次费用上限(人民币) |
maxConsecutiveFailures | 3 | 同一任务连续失败上限 |
stateDirName | .dsh-auto-plan | 状态目录名 |
workspaceRoot | ""(进程 cwd) | 状态目录根 |
enforceWriteTools | true | 方案激活时,write/edit/ops_replace/apply_patch 等文件改写工具必须落在 RUNNING 任务的 allowed_scope / writes 内;没有 RUNNING 任务直接拒绝。shell/pwsh 无法按路径匹配,仍可能绕过 |
enforceScope | false | 额外开启后,所有带路径参数的工具(含读)都受 allowed_scope 限制;默认关闭以免影响普通任务 |
costArgName | "" | 声明费用数值的工具参数名(供预算守卫) |
maxRounds | 12 | 交给原生 Goal 的轮次上限 |
maxCompletenessRounds | 6 | 完整度闸门允许的补做轮次;同一缺口指纹重复且无进展 → STALLED,按 BLOCKED 如实收尾 |
maxSteps | 400 | 步骤提取上限;超出判 TRUNCATED 并在 extraction 中如实报告(当前不会因此自动拒绝收尾,见已知缺口 9) |
openvikingEnabled | true | 是否把步骤账本实时镜像到 OpenViking;写失败只记录,不阻塞方案 |
openvikingBaseUrl | http://127.0.0.1:1933 | 本机 OpenViking HTTP 端点 |
openvikingHome | viking://~/plans/auto-plan | 镜像根目录(~ 展开为当前用户根;不写进 memories/,避免污染召回) |
openvikingTimeoutMs | 1000 | 单次镜像请求超时;短超时避免拖住回合 |
serialSteps | false | false:保留独立分支并行,只禁止关闭仍被问题阻塞的步骤;true:严格串行,下一步开始前强制复核上一步 |
cleanupOnFinish | true | 通过收尾并退出方案模式后,删除该方案在 OpenViking 的镜像目录;失败如实写进报告 |
方案完整度闸门(2026-10-18)
它修的是什么:收尾过去只看"模型自己登记的任务图",于是 20 步方案登记 3 步、3 步过了就是 "全部完成并验证"。现在收尾按冻结的方案步骤判。
- 步骤清单冻结在原文上:激活时从
snapshot.sourceText确定性提取(编号列表、- [ ]、步骤N、P0/P1/P2与标题分段;代码块跳过;需求 / 目标 / 交付 / 验收 / 范围 / 功能 / 任务 段落里的编号、项目符号、表格行与带职责动词的散文句也算步骤;表头含「任务/待办/步骤/交付」 的表格在任意段落都算;「已知问题 / 现状」表格不算工作)。模型只能用auto_plan_step action="declare"追加,且每条必须source_quote逐字引用原文(只忽略标点差异);已提取步骤不能删除。 - 降级可选要引用原文:
classify把某步降为 OPTIONAL,必须引用原文里真实存在的可选措辞 (可选 / 如需 / 视情况 / 可随后 …);P2段默认可选,出现「必须 / 强制」则重新必需。 - 收尾重验:
auto_plan_finish逐项判定DONE / OPEN / EXEMPT,DONE 由当场重跑 verifier 决定——持久化的verified: true不算数。 - 缺口即拒绝:存在阻塞缺口时不写报告、不推终态,幂等登记
GAP-PS-xx缺口任务、记录一轮 完整度、返回续跑指令(下一次agent/pre-step注入一次,不新起续轮循环)。 - 两类用户侧豁免:
USER_AUTHORIZATION必须引用本方案真实的DENIED G3/G4记录;USER_ACTION必须写明需要用户提供什么。无证据的豁免判OPEN:EXEMPTION_UNEVIDENCED。 - 收敛:同一缺口集合 + 相同原因 + 完成数不变,连续两次 →
STALLED;轮次用尽 →ROUNDS_EXHAUSTED;两者都写成阻塞,绝不写成完成。 - 终态:全部完成且无用户侧待办 →
COMPLETED_VERIFIED(全部完成并验证); 仅剩有证据的用户侧待办 →COMPLETED_USER_PENDING(完成,不含用户侧待办),报告单列「待用户处理」。 - 未覆盖段落阻塞收尾:「必须 / 交付物 / 验收标准 / 验收条件」段落如果提取不出任何步骤,
会作为
SEC:<section>:UNCOVERED_SECTION阻塞收尾;必须用auto_plan_step action="declare"补齐, 或用action="dismiss_uncovered"提交该段落正文的逐字引用 + 理由,才能放行(报告会列出)。
步骤账本与服务器镜像(2026-10-19)
它修的是什么:完整度闸门只保证"清单里的每一步都被覆盖",但执行中派生的问题、以及"某一步 到底算不算完"仍由模型口头决定。现在每一步有独立账本,问题归属该步,未关闭就不能算完成。
- 激活即写清单:检测到方案、进入执行前,把冻结步骤写成
viking://~/plans/auto-plan/<plan_id>/ledger.json(机器账本)与plan.md(人读清单)。 写入失败只记录lastSync,不阻塞方案(镜像不是权威源)。 - 每个步骤事件同步一次:
task-start/task-verified/step-begin/issue-added/issue-closed/review-*/finish-refused都会递增revision,并同时重写ledger.json与plan.md(人读清单不再是激活快照)。 - 派生问题归属步骤:
auto_plan_step action="issue"把问题记到step_id名下; 该步还有OPEN问题 → 步骤状态为RUNNING,完整度判ISSUES_OPEN,不能关闭。 - 复核闭环:
action="review"要求为每条未关闭问题给出resolved判定——resolved=true必须附evidence_ref,resolved=false必须写reason。只要有一条未解决, 复核判REVIEW_REJECTED,方案回退到该步(rollbacks留痕),不会推进。 - 两种执行模型:
- 默认
serialSteps=false(本轮确认):独立分支可并行开始,只禁止关闭被阻塞的步骤; serialSteps=true:严格串行,开始下一步前必须对上一步提交APPROVED复核; 缺复核时auto_plan_task action="start"直接拒绝,并返回复核指令。串行模式下 一个任务不得同时覆盖多个必需步骤(否则会跳过上一步复核)。
- 默认
- 边界读服务器:
start开始步骤、提交复核、以及auto_plan_finish收尾前都会先读一次 镜像账本,把服务器上本地没有的问题并入本地(本地状态优先;server-only 问题一律按OPEN入账,必须本地凭证据关闭),并作废受影响步骤的复核;读不到时按本地账本继续,并标degraded。 - 收尾退出方案模式:完整度通过 → 推终态 →(
cleanupOnFinish)删除该方案镜像目录 → 写报告 → 从会话 runtime 表移除。只有**报告写成功且清理成功(或已跳过)**才退出;报告写失败或清理失败时 保留 runtime,auto_plan_status仍可见、可重试auto_plan_finish,不会留下没人管的孤儿。 清理失败会在报告与SERVER_CLEANUP里如实写FAILED,不写成已清理。 - 重启恢复:宿主重启后 runtime 表为空;第一次
agent/pre-step会扫描本地state.json, 找到同会话、未终态的 plan 后重建 runtime(任务图、步骤、问题、复核、stepBegins、evidenceByTask、预算记录与已拒绝的 operation key 全部从磁盘恢复),注入一次 「已恢复未完成方案」指令,不要求用户重新提交方案。重启不会把已用预算归零,恢复后的 剩余额度仍受同一 2 元上限约束。 只有服务器镜像、没有本地 state 的方案不会被自动恢复——这是有意的(无法证明它的任务与证据); 可以用auto_plan_status的include_mirrors: true查看 orphan 镜像,再人工核对/清理。 - 证据引用绑定:关闭问题或 review 的
resolved=true时,evidence_ref必须精确匹配 本方案已记录的evidenceRef,或是file:<工作区相对路径>且文件存在;随手编一个字符串会被EVIDENCE_NOT_FOUND拒绝。它仍然只是"这条引用指向的观测"的声明,不能证明命令真的执行过 (与 verifier 的evidenceRef同一局限)。 - 写入工具默认门禁:方案激活期间,
write/edit/ops_replace/apply_patch等文件改写工具 必须落在当前 RUNNING 任务的allowed_scope / writes内;没有 RUNNING 任务时直接拒绝。 这是方案内的轻量门禁,不影响普通会话;shell/pwsh无法按路径可靠匹配,仍是已知绕过口。
镜像不是强制执行器:OpenViking 只是被读写的账本,不会自己驱动 AI。真正的"强制"由插件在
auto_plan_task start/auto_plan_step review/auto_plan_finish三处执行;默认并行模式下 只禁止关闭被阻塞的步骤,需要"第二步前强制复核上一步"时必须显式serialSteps: true。 模型若绕过这些工具直接改文件,只有enforceScope=true的路径门禁能拦住(默认关闭)。 这是已知边界,不是隐性保证。
验证状态(实测)
| 层 | 命令 | 结果 |
|---|---|---|
| 语法 | node --check lib/index.js(含 step-gate / ov-client) | PASS |
| 单元(212 用例) | node --import ./test/host-hooks.mjs --test ...(显式文件清单) | 212 pass / 0 fail |
| 装配(57 项) | node --import ./test/host-hooks.mjs verify/wiring.mjs | 57/57 PASS(含 STEP-REVIEW / 并行模型 / 镜像脱敏 / 收尾清理成功与失败 / 重启恢复+预算 / 未覆盖段落 / 写入工具门禁 / orphan 列表 / 退出方案模式) |
| 双插件共载(21 项) | node --import ./test/host-hooks.mjs verify/dual-plugin.mjs | 21/21 PASS |
| 完整门禁 | 上述三段串行执行(等价 npm run check) | exit 0 |
| 真实 OpenViking 往返 | content/write → content/read → fs/ls → fs DELETE | 200/200/200/200,删后读 404(2026-10-19 实测) |
| 安装到 web profile | plugin_manager install_bundle | 包已装入(bundles=14、link:)+ dsh --profile web --dump-config exit 0 |
| 运行时激活 | Inspector Tool.listTools | auto_plan_status / auto_plan_task / auto_plan_gate 已注册 |
已知缺口(不隐瞒)
confirmationMirror没有运行时写入方:lib/confirmation-adapter.js具备能力发现与只读镜像, 但index.js目前没有任何地方调用mirror.observe()。因此:auto_plan_status里的"已观测确认项"恒为空,state.confirmations恒为[];- §4.1「正在回复已发布 C 项时不触发」这一条,在生产里只靠正文里的
C1/C2文本模式生效, 「本会话仍有未关闭 C 项」那条判据目前不生效。 - 这不是安全绕过:镜像只是"更早地不再触发",缺了它仍然不会误建方案(文本模式仍在)。
- 要关闭它需要挂一个只读的
tools/post-execute观察器;我没有擅自加,因为它会改变 最敏感的触发路径语义(一条被观察为 open 的 C 项会一直阻塞后续触发,直到观察到它关闭)。
auto_plan_finish是唯一的收尾闸门:它先跑完整度检查,缺口未清时拒绝收尾 (不写报告、不推终态),通过后才用同一套 verifier 重新验收、推进终态 (COMPLETED_VERIFIED/COMPLETED_USER_PENDING/BLOCKED/PARTIAL)、写八部分报告, 并返回交给原生 Goal 的目标文本。它不会自己创建 Goal——创建 Goal 是有副作用的主机动作, 所以这里只交文本(方案 §6.1)。 「失败」这一终态不会由本工具推导:任务状态FAILED是可重试态,把它映射成"方案失败" 会在还有重试额度时报成失败;失败只保留给显式中止。snapshot.requiredDeliverables仍恒为空(激活时按字面量[]创建,模型无法自行声明), 因此validateRequiredCoverage仍是"空集真空通过";覆盖改由步骤清单承担:snapshot.planSteps是新的权威来源,任务用plan_steps声明覆盖哪一步。goalObjective描述的轮次上限(maxRounds)没有被消费,因为没有任何路径创建 Goal。- 复核判定仍由模型提交:插件不能读取对话原文,
review的resolved是模型基于上下文给出的 判断;插件能强制的是"每条问题必须有判定、resolved 必须带证据、未解决就回退",不能替模型判断 "这段对话是否真的解决了它"。 - 镜像不是强制执行器:OpenViking 不会自己驱动 AI;
serialSteps=false(默认)下步骤可并行开始, 插件只保证"被问题阻塞的步骤不能关闭"。需要严格"第二步前强制复核上一步"时必须显式serialSteps: true,否则那个边界不会发生。 - 绕过插件工具仍可能发生(已收窄):
write/edit/ops_replace/apply_patch等文件改写工具 在方案激活期间默认受allowed_scope / writes约束;但shell/pwsh命令无法按路径可靠匹配, 仍可能直接写文件。要硬隔离需要在宿主沙箱/OS 层限制,而不是本插件。 - 清理只删服务器镜像:
cleanupOnFinish删除的是viking://~/plans/auto-plan/<plan_id>/; 本地.dsh-auto-plan/<plan_id>/的state.json与报告仍保留,作为本地审计痕迹。 TRUNCATED不会自动阻塞收尾:提取超过maxSteps时会在extraction里如实标记truncated/ok=false并给出告警,但当前闸门仍按已提取的前缀判定;需要把未提取部分用auto_plan_step action="declare"补齐,插件不会替模型猜剩余步骤。- 恢复依赖本地 state/evidence:重启恢复只扫描本地
state.json+ evidence 文件; 只有服务器镜像、没有本地 state 的方案不会自动恢复,也不会被自动删除(可能是另一台机器/会话的)。 可用auto_plan_status(include_mirrors: true)查看 orphan 镜像,人工核对后再清理。 evidence_ref已绑定,但仍是声明:现在必须精确匹配本方案已记录的evidenceRef,或file:<工作区相对路径>且文件存在;但一条已记录的 ref 仍只说明"模型声称执行过这个观测", 不能证明命令真的执行了(与 verifier 的evidenceRef同一局限)。
验收证据契约(2026-10-15 收紧,直接对应审查发现的阻塞项)
一条证据只有携带它那个 kind 应有的字段才算"观测",否则只能是"断言":
| kind | 必须携带 | 说明 |
|---|---|---|
command / test | 整数 exitCode + 非空 evidenceRef | 退出码非 0 → 任务判 FAIL(不是 UNVERIFIED) |
diff / probe | 非空 evidenceRef | 指向真实产物 |
manual | 非空 attributedTo | 人工断言必须写明由谁作出,否则不算 |
self-report / model-claim / tool-called / generated-code / planned-command | —— | 永远只能是断言,不能使任务通过 |
为什么:收紧前,{kind:"command", criterion:"<验收原文>", ok:true}(无退出码、无引用)
就能把任务判成 verified=true 并让 COMPLETION: COMPLETE,进而支持"全部完成并验证"的假结论。
现在这种输入一律 UNVERIFIED,并在理由里点名缺了哪些字段。
node_modules/是必需的,不是可选项。lib/用裸说明符 import 宿主包,装进 profile 后由 Node 从本目录向上解析,所以这里必须有指向 DSH 安装的 Junction:node_modules/@deepseek-ai/{cordis,dsh-tools,dsh-llm,schemastery,dsh-attachment}。2026-10-15 实测事故:最初因沙箱拒绝建 Junction 而改成"零
node_modules+ 开发期 resolve 钩子", 结果装进 profile 后failed to import(application: failed / 1 entry did not activate)。 而且首次解析失败会被长驻进程的解析缓存记住——禁用→启用只会重放失败,必须重启 DSH。 判据:同一个包用node -e "import('<包名>')"在新进程里成功、宿主仍报failed to import。
test/host-hooks.mjs保留为兜底(没有 Junction 时也能跑测试),它同样指向 profile 中同一份 宿主包实例,因此不会产生第二份模块身份。该文件不属于发布内容。
目录
dsh-auto-plan-executor/
├── package.json / cordis.patch.yml
├── lib/ 纯逻辑模块 + Cordis 装配
├── test/ 单元测试与开发期 resolve 钩子
├── verify/ 装配验证、双插件共载验证
└── artifacts/ 环境报告、基线、操作手册、回滚、测试矩阵
回滚
dsh plugin --profile web remove dsh-plugin-auto-plan-executor
详见 artifacts/rollback.md。