拦截重复无效动作,核对工具执行真实结果
数据截至 2026/9/19 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
dsh plugin --profile web add goatliamia/dsh-runtime状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
安装兼容性检查需源码安装仓库根目录无 package.json,浅层子目录有 12 份 dsh 插件清单(plugins/dsh-notify/package.json、plugins/runtime-progress/package.json、experiments/harness/fixture/package.json 等),无法唯一定位安装名
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/3 15:20:13
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
查看扫描证据(共 58 处,展示前 57 条)
- 写入本地文件core/runtime-circuit/lib/index.js:130
writeFileSync( - 写入本地文件core/runtime-investigate/lib/index.js:138
writeFileSync( - 写入本地文件core/runtime-progress/lib/index.js:272
writeFileSync(join(resultsDir, `${runId}.projection.json`), `${JSON.stringify(payload, null, 2)}\n`); - 写入本地文件core/runtime-reconcile/lib/index.js:142
writeFileSync( - 发起网络请求core/runtime-seam/lib/client.js:97
fetch("/api/runtime-seam/activity") - 发起网络请求core/runtime-seam/lib/client.js:113
return fetch(path, { - 读取本地文件core/runtime-seam/lib/index.js:127
if (readConfig().capabilities.persistence && existsSync(STATE_FILE)) { - 读取本地文件core/runtime-seam/lib/index.js:129
const saved = JSON.parse(readFileSync(STATE_FILE, "utf8")); - 写入本地文件core/runtime-seam/lib/index.js:145
mkdirSync(STATE_DIR, { recursive: true }); - 写入本地文件core/runtime-seam/lib/index.js:146
writeFileSync( - 写入本地文件core/runtime-seam/lib/pre-continuation.mjs:215
mkdirSync(join(diagPath, ".."), { recursive: true }); - 写入本地文件core/runtime-seam/lib/pre-continuation.mjs:216
writeFileSync(diagPath, JSON.stringify({ ...diag, metrics: state.metrics }, null, 2)); - 读取本地文件experiments/harness/aggregate.mjs:18
if (!existsSync(path)) return null; - 读取本地文件experiments/harness/aggregate.mjs:20
return JSON.parse(readFileSync(path, "utf8")); - 读取本地文件experiments/harness/aggregate.mjs:28
if (!existsSync(path)) return { found: false }; - 读取本地文件experiments/harness/aggregate.mjs:30
return { found: true, text: readFileSync(path, "utf8").trim() }; - 读取本地文件experiments/harness/aggregate.mjs:38
if (!existsSync(path)) return ""; - 读取本地文件experiments/harness/aggregate.mjs:40
const text = readFileSync(path, "utf8"); - 写入本地文件experiments/harness/aggregate.mjs:190
writeFileSync(join(RESULTS, "summary.json"), JSON.stringify(summary, null, 2)); - 写入本地文件experiments/harness/aggregate.mjs:191
writeFileSync(join(RESULTS, "summary.md"), md.join("\n")); - 读取本地文件experiments/harness/aggregate2.mjs:26
if (!existsSync(path)) return null; - 读取本地文件experiments/harness/aggregate2.mjs:28
return JSON.parse(readFileSync(path, "utf8")); - 读取本地文件experiments/harness/aggregate2.mjs:36
if (!existsSync(path)) return "(missing)"; - 读取本地文件experiments/harness/aggregate2.mjs:38
return readFileSync(path, "utf8").trim().replace(/ +/g, " ").slice(0, 80); - 读取本地文件experiments/harness/aggregate2.mjs:58
for (const file of readdirSync(RESULTS)) { - 写入本地文件experiments/harness/aggregate2.mjs:172
writeFileSync(join(RESULTS, "summary2.json"), JSON.stringify(summary, null, 2)); - 写入本地文件experiments/harness/aggregate2.mjs:173
writeFileSync(join(RESULTS, "summary2.md"), md.join("\n")); - 读取本地文件experiments/harness/aggregate4.mjs:18
if (!existsSync(path)) return null; - 读取本地文件experiments/harness/aggregate4.mjs:20
return JSON.parse(readFileSync(path, "utf8")); - 读取本地文件experiments/harness/aggregate4.mjs:28
if (!existsSync(path)) return "(missing)"; - 读取本地文件experiments/harness/aggregate4.mjs:30
return readFileSync(path, "utf8").trim().replace(/ +/g, " ").slice(0, 60); - 读取本地文件experiments/harness/aggregate4.mjs:44
for (const file of readdirSync(RESULTS)) { - 写入本地文件experiments/harness/aggregate4.mjs:109
writeFileSync(join(RESULTS, "summary4.json"), JSON.stringify({ generatedAt: new Date().toISOString(), byArm, rows }, null, 2)); - 写入本地文件experiments/harness/aggregate4.mjs:110
writeFileSync(join(RESULTS, "summary4.md"), md.join("\n")); - 读取本地文件experiments/harness/aggregate4b.mjs:17
if (!existsSync(path)) return null; - 读取本地文件experiments/harness/aggregate4b.mjs:19
return JSON.parse(readFileSync(path, "utf8")); - 读取本地文件experiments/harness/aggregate4b.mjs:27
if (!existsSync(path)) return "(missing)"; - 读取本地文件experiments/harness/aggregate4b.mjs:29
return readFileSync(path, "utf8").trim().replace(/ +/g, " ").slice(0, 60); - 读取本地文件experiments/harness/aggregate4b.mjs:43
for (const file of readdirSync(RESULTS)) { - 写入本地文件experiments/harness/aggregate4b.mjs:107
writeFileSync(join(RESULTS, "summary4b.json"), JSON.stringify({ generatedAt: new Date().toISOString(), byArm, rows }, null, 2)); - 写入本地文件experiments/harness/aggregate4b.mjs:108
writeFileSync(join(RESULTS, "summary4b.md"), md.join("\n")); - 读取本地文件experiments/harness/aggregate56.mjs:11
if (!existsSync(path)) return null; - 读取本地文件experiments/harness/aggregate56.mjs:13
return JSON.parse(readFileSync(path, "utf8")); - 读取本地文件experiments/harness/aggregate56.mjs:21
if (!existsSync(path)) return "(missing)"; - 读取本地文件experiments/harness/aggregate56.mjs:23
return readFileSync(path, "utf8").trim().replace(/ +/g, " ").slice(0, 140); - 读取本地文件experiments/harness/aggregate56.mjs:37
for (const file of readdirSync(RESULTS)) { - 写入本地文件experiments/harness/aggregate56.mjs:116
writeFileSync(join(RESULTS, "summary56.json"), JSON.stringify({ generatedAt: new Date().toISOString(), e5, e6 }, null, 2)); - 写入本地文件experiments/harness/aggregate56.mjs:117
writeFileSync(join(RESULTS, "summary56.md"), md.join("\n")); - 读取本地文件experiments/harness/aggregate7.mjs:12
if (!existsSync(path)) return null; - 读取本地文件experiments/harness/aggregate7.mjs:14
return JSON.parse(readFileSync(path, "utf8")); - 读取本地文件experiments/harness/aggregate7.mjs:28
if (!existsSync(path)) return "(missing)"; - 读取本地文件experiments/harness/aggregate7.mjs:30
return readFileSync(path, "utf8").trim().replace(/ +/g, " ").slice(0, 100); - 读取本地文件experiments/harness/aggregate7.mjs:38
for (const file of readdirSync(RESULTS)) { - 写入本地文件experiments/harness/aggregate7.mjs:166
writeFileSync(join(RESULTS, "summary7.md"), md.join("\n")); - 执行外部命令固定命令plugins/dsh-notify/lib/notify.js:120
return spawn("osascript", ["-e", expr], { stdio: "ignore" }); - 执行外部命令固定命令plugins/dsh-notify/lib/notify.js:149
child = spawn( - 执行外部命令固定命令plugins/dsh-notify/lib/notify.js:165
child = spawn("notify-send", [opts.title, opts.body], { stdio: "ignore" });
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
功能亮点
- 拦截重复无效动作
- 核对工具执行结果
不要让 Model 为确定性问题反复思考,也不要让 Harness 为不确定的问题假装知道答案
README
由 DeepSeek 最新模型翻译生成展开完整文档(README 剩余 1 节)
DSH Runtime
让模型负责思考,让 Harness 处理那些可以确定的事情。
dsh-runtime 是一组面向 DeepSeek Harness 的小型 Runtime 能力。
它不试图做一个新的 Agent,也不试图用更多 Prompt、Skill 或规则去“教会”模型所有事情。
它只接住一类问题:
那些模型不应该反复猜,Harness 本来就可以确定的问题。
例如:
* 这件事现在能不能做?
* 刚才这一步到底有没有产生变化?
* 工具虽然报错了,实际操作有没有已经发生?
* 工具说成功了,目标状态有没有真的生效?
* 一个动作已经重复很多次,但事情有没有继续往前走?
安装
一条命令装完(所有能力都在一个包里;装完去设置页选模式 / 场景 / 自定义即可):
从 GitHub 页面直接下载 release/dsh-runtime-0.1.0.tgz,或先 clone 仓库
dsh plugin --profile add dsh-runtime-0.1.0.tgz
装完 dsh --profile --dump-config 能看到 runtime-progress / circuit / reconcile / investigate / seam 五行,即成功。
- 用户不需要选包:模式(Off / Minimal / Balanced / Strict)、场景预设(Creative / Coding / External / Safe)和自定义勾选都在 Runtime 设置页里完成;
- 开发者按需:release/ 里另有五个单独包(progress + circuit + reconcile + investigate + seam),只想装某几个能力时才用;
- 开发者重建:改完代码跑 node scripts/pack-release.mjs 重新生成 release/。
它是怎么工作的?
一次工具调用会发生四件事,Runtime 只做后面两件:
模型调用工具 value !== "locked", predicateText: "pwsh requires runtime-progress.shell-lock != locked", })
事实是 locked 时,模型调用 pwsh 会拿到一条 [action-rejected] 加那句 predicateText,调用不会进入执行。
Progress
执行之后看有没有真的往前走。
const p = foldProjection(events)
p.verdict.execution // "success" | "failed" | "none"
p.verdict.effect // { 工具名: "success" | "failed" | "unknown" }
"命令跑成了"和"事情往前走了"是两条轴,分开报。它不停止、不重试、不修,只提供判断。
Circuit
同一件事反复失败,而且一直没有进展时,说一次。
registerCircuitContract({ id: "exp_flaky", match: { tool: "exp_flaky" }, threshold: 2 })
判定的单位是工具加失败形态加目标,不是"工具"。到阈值时模型收到一条观测:
[runtime-observation circuit-open]
observed: "read" failed 2 times with the same failure on c:\repo\gone.mjs (threshold 2).
failure: Error: cannot read "": not found
fact: capabilities.read.state = "stalled" (authority: runtime, revision: 1, fingerprint: ...)
它只报观测,不下命令。也不做拒绝:文件工具里读、写、改互为解法,拒绝会把会话锁死。
Reconcile
非原子动作已经跑过一次时,不许重放。
registerNonAtomicContract({ id: "deploy", match: { tool: "pwsh", pattern: /deploy\.ps1/ } })
第二次调用被拦下,理由是:"was already invoked once and its confirmation was lost; the effect may already be applied, so a retry can duplicate the side effect."
Delta
只有值得关注的变化才告诉模型。
{ role: "user",
source: { kind: "plugin", plugin: "dsh-runtime-seam" },
content: [{ type: "text", text: "[runtime-observation ...]" }] }
变化以一条 user-role 消息进入下一步。Runtime 不播报"现在还是这样"。
子代编排(Child orchestration)
父 agent 把活派给后台子代,编排层等的是终局事实,不是状态。
const fact = await ctx.childOrchestration.wait(childId, 30000)
// { outcome: "settled", stopReason: "completed", lastAssistantMessage: "..." }
idle 不等于结束:子代静默但还持有活孙代时是 waiting,不是完成。实测里这个等待在"看起来已经不跑了"的 30.5 秒内一次都没有提前返回。语义冻结在 docs/20-child-orchestration-semantic-contract.md,实测见 experiments/async-lifecycle/。
Continuation(事前)
当事实与契约把下一步压缩到唯一确定的动作时,Runtime 直接执行,模型只消化结果。
首发合同是 post-write-syntax-check:模型写完或改完工作区 JS 模块后,Runtime 自己跑 node --check,然后交给模型一条 digest:
[runtime-continuation] deterministic step already executed by the runtime: post-write-syntax-check.
Do not re-run it; digest the outcome and continue from the current world state.
它走正常的工具管线(权限、守卫、取消照旧),所以模型看到的是一个已经发生的结果,不是一句要求。没有把握时一律不接管。由 settings 里的 continuation 位驱动,详见 docs/status/pre-productized-2026-09-03.md。
一个简单的例子
Agent 执行:
“把插件切到 fast 模式。”
它修改了配置,构建也成功了。
但运行中的插件实际上还在旧模式。
普通流程很容易得到:
修改成功 → build 成功 → 完成
Runtime-aware 的流程可以继续观察实际状态:
执行成功 → 没有看到预期变化 → investigate → 发现仍是旧状态 → reload → 再确认 → ready
这里 Runtime 没有替模型做创造。
它只是避免把:
“工具完成了”
误认为:
“事情完成了”。
Runtime 应该尽量安静
Runtime 不是一个新的“大管家”。
一个重要原则是:
模型自己看得清的地方,不需要 Runtime 出手。
正常 Coding 任务中,Runtime 应该可以完全不介入。
只有当 Harness 能明确看到一些模型不容易可靠判断的事情时,才值得介入。
因此 Runtime 更像是一层很薄的保护,而不是一套新的 Agent。
模式
设置页分为两根独立的轴:
| 轴 | 是什么 | 选择 |
| 事前 Pre | 一个开关——Continuation:事实 + 契约把下一步压缩到唯一时,替模型走这一步 | 开 / 关 |
| 事后 Post | 执行后纠偏与止损:guard、circuit、reconcile、verify & repair | Off / Minimal / Balanced / Strict / Custom |
两根轴不必落在同一个模式里:Post 模式选择事后职责组合,Pre 开关独立翻转。
也可以直接从场景开始(一个同时设定两轴的快捷键):
Creative · Coding · External Actions · Safe
模式只是选择不同的 Runtime 能力组合,不会改变模型本身。
为什么不是更多 Skill?
有些东西确实适合写进 Skill。
比如:
一种工作习惯、一种偏好、一种场景下更好的做法。
但有些东西不属于这个层面:
文件到底有没有写进去?
插件到底有没有加载?
一个操作到底有没有发生?
用户到底有没有真正批准?
这些事情,如果 Harness 能够确定,就不应该只靠模型记住一段文字。
一个简单的原则:
需要理解的事情交给模型。
可以确定的事情交给 Harness。
无法确认的事情,就承认不知道。
Evidence
这个项目不是只停留在设计上,目前已经完成了一组 DSH 实际实验。
在确定性的场景中:
* 重复无进展:真实执行次数下降 67%
* 非原子失败:重复副作用下降 75%
* 成功但未生效:世界正确率从 0/2 提升到 2/2
* 正常 Coding:0 次误介入
* 异步 polling:在当前场景和模型下没有明显优势
* 确定性 continuation(第 1-4 轮):Runtime 接管唯一确定的下一步(B 模型调用 15 vs A 19,−21%),stale/cancel/guard/多选一律零误执行,事实缺失或误导时克制,instruction continuity 保持——模型消化已发生的事实并把动作诚实归给 runtime
这些结果说明 Runtime 的价值不是“任何地方都更强”。
更接近:
模型自己看得清的地方,它不打扰;模型看不清现实的地方,它补上一点确定性。
完整实验过程、原始数据和限制条件见 docs/——尤其是 Runtime Continuation 线:docs/status/runtime-continuation-2026-09-02.md(命题)、runtime-continuation-boundaries-2026-09-02.md(边界)、runtime-continuation-instruction-2026-09-02.md(instruction continuity)、runtime-continuation-ownership-2026-09-03.md(所有权边界),以及四轮汇总 runtime-continuation-summary-2026-09-03.md。
后面三条线各有独立报告:
* 子代编排 —— docs/20-child-orchestration-semantic-contract.md(冻结的语义,含那个把 idle 从终局判定里排除掉的 30.5 秒负例)
* turn-stopping —— docs/status/turn-stopping-continuation-and-cost-2026-09-11.md(收尾点算不算合法的续跑点,在那里注入的代价是多少),机制写在 docs/21-turn-continuation-and-injection.md
* 熔断在文件工具上的误报 —— docs/status/circuit-fingerprint-vs-fs-errors-2026-09-11.md(两个不同文件塌成一条指纹,以及修法)
Project Status
当前项目仍然是一个实验驱动的小型 Runtime capability 集合。
它不会试图成为新的 Agent Framework,也不会自己重新实现 DSH 的底层 Runtime。
能力应该从真实问题中长出来:
真实问题 ↓ 找到可以确定的事实 ↓ 最小 Runtime 能力 ↓ 实际验证 ↓ 重复出现 ↓ 再考虑抽象
先解决一个具体的问题,而不是先设计一个完整系统。
Repository
这个仓库目前主要面向:
* DSH Runtime capability 实验
* Runtime / Event / Progress 研究
* 真实 Agent trajectory 验证
* DSH 插件组合与场景实验
安装和启用方式以各 capability 的文档为准;这个仓库本身不是一个需要单独启动的 Agent 应用。
License
见 LICENSE。
最后:为什么是 Plugin?
因为我们并不认为已经知道 Agent 的最终结构应该是什么。
这个项目只是观察到了一个很具体的事实:
真实 Agent 运行中,有些工作已经足够确定,不值得继续占用模型的推理空间。
如果这些工作可以被独立出来,那么最自然的方式不是把它们全部塞回 Core。
而是:
Real friction
↓
Deterministic part
↓
Capability
↓
Plugin
↓
Harness
今天可能是 Runtime。
明天可能是别的东西。
不需要预先知道。
一个暂时足够简单的原则
如果模型仍然需要判断,让模型判断。
如果程序已经知道答案,不要让模型重复发现。
如果现实已经确定不允许,不要让模型决定能不能执行。
如果现实没有变化,不要告诉模型。
如果一条路径已经没有进展,不要让它无限继续。
如果状态已经存在,不意味着必须重新注入。
如果用户的意图可能错,保护现实,但不要替用户重新定义意图。
Agent = Model + Harness
这个仓库只是在探索:
Harness 究竟应该接住哪一部分。预览分享卡片长什么样 ↓
拦截重复无效动作,核对工具执行真实结果
相关插件 · 其他
这是你的仓库?认领条目