- Главная
- Плагины
- Процессы и автоматизация
- dsh-target-achieved
dsh-target-achieved
firenzeclaw/dsh-target-achieved
DeepSeek-Harness 插件:轮次收尾时若模型未声明目标状态,注入续行提醒而非放行。处理思考泄露、工具调用畸形、上游断流导致的任务静默中断;上游中断类型可重试错误耗尽后自动续行,默认上限 3 次,跨进程共享计数。含设置界面与判定留痕
Установка
dsh plugin --profile web add github:firenzeclaw/dsh-target-achievedREADME
DSH-Target-achieved
为 DeepSeek-Harness 提供的收尾门插件:当轮次即将关闭而模型未声明目标状态时,注入一条续行提醒,避免轮次静默关闭。
目录
背景
本项目处理任务未完成而轮次已结束的情况:用户看到的是完成汇报或半截输出,任务实际未继续。
中断来源与处理方式如下:
| 中断来源 | 实际占比 | 表现 | 本项目 |
|---|---|---|---|
| 思考泄露 / 工具调用畸形 | 主要 | 模型把推理过程或残缺的工具调用写入正文,随后结束轮次 | 收尾处拦截:未声明目标状态不放行 |
| 任务中途收尾(模型自认完成) | 极低 | 模型未声明即直接结束 | 同一规则处理 |
| 输出被长度上限截断 | 偶发 | max-tokens,截断后的工具调用未执行 | 识别截断并说明未执行的工具调用 |
| 上游断流 / 限流 | 偶发 | 请求失败,重试耗尽后轮次以错误结束 | 重试由宿主负责;耗尽后自动续行一轮(默认上限 3 次) |
前两类在插件中对应同一条件:轮次结束时没有目标状态声明。插件不判断结束原因,只在该条件成立时拦截,因此思考泄露与工具调用畸形导致的静默收尾同样会触发拦截。
特性
- 收尾拦截:轮次收尾时未声明目标状态则不放行,注入一条归属
target-monitor的提醒。判定不区分中断的上游原因。 - 两种结束原因:区分普通收尾与
max-tokens截断,后者额外说明工具调用未执行。 - 跨轮复述:累计遗忘达阈值后,下一次提醒末尾追加声明规范复述。
- 上游续行:可重试的上游错误(限流、超时、连接中断)在重试耗尽后自动续行一轮,默认上限 3 次;不可重试错误与用户取消不续行。
- 判定留痕:每次判定写入一条 JSONL 记录,放行同样记录;九种原因可区分(六种收尾门 + 三种上游续行)。
- 设置页可改:全部八个字段集中在「设置 → DSH中断续行」分区,写回带 revision 冲突检测;除
crossProcess/declarationSection外即时生效。 - 可关闭初始注入:关闭
declarationSection后只停系统提示词章节,target_status工具、收尾门与上游续行照常工作;设置页给出可复制的AGENTS.md片段作为替代承载方式。 - 跨进程共享:多个 DSH 入口(web / CLI)共用同一个守护进程,遗忘与续行计数跨入口累计;留痕由守护进程统一写入,不会交错。守护进程不可用时自动退回进程内判定。
- 三种安装方式:bundle、git 源、本地 link。
安装
# 1. 作为 bundle(安装器读取 dsh.bundle.patch 并写入 profile 补丁层)
dsh plugin --profile web add dsh-target-monitor
# 2. 从 git 源
dsh plugin --profile web add git+ssh://git@github.com/FirenzeClaw/dsh-Target-achieved.git
# 3. 本地开发
dsh plugin --profile web add /path/to/DSH-Target-achieved
也可以直接编辑 profile 的 cordis.patch.yml:
- insert:
- id: target-monitor
name: 'dsh-target-monitor'
config:
enabled: true
stallNoticeThreshold: 5
安装后重启 dsh web:系统提示词章节与工具 schema 均在进程启动时注册。
从源码或本地目录安装时,先执行 npm run build。宿主只加载已构建的客户端产物;缺少 dist/client.js 会导致插件激活失败,表现为设置分区无法打开、日志报缺 bundle。通过 npm 或 git 源安装无需手动构建,发布包已包含产物。
包已发布到 npm,可直接引用版本号安装:
dsh plugin --profile web add dsh-target-monitor@0.5.0
快速开始
安装完成后,模型在收尾前调用一次工具:
// 任务完成,准备汇报
{ "state": "done" }
// 需要用户决定,准备提问
{ "state": "awaiting_user" }
未调用即收尾时,插件会拦截并注入提醒;声明 done 或 awaiting_user 后放行。
配置
| 字段 | 类型 | 默认 | 设置页 | 说明 |
|---|---|---|---|---|
enabled | boolean | true | 是 | 总开关;关闭时判定直接放行,工具拒绝声明 |
reminderThreshold | 正整数 | 3 | 是 | 累计几次遗忘后复述一次声明规范 |
reactionWindowMs | 正整数 | 30000 | 是 | 注入后多长时间内的声明视为由提醒触发 |
stallNoticeThreshold | 正整数 | 5 | 是 | 本轮第几次未声明时向用户发“可能卡住”提示 |
resumeOnUpstreamError | boolean | true | 是 | 上游可重试错误后是否自动续行 |
maxResumes | 非负整数 | 3 | 是 | 单轮内上游异常的最大续行次数;0 等价于关闭自动续行。只约束上游异常续行,收尾提醒不受此限 |
crossProcess | boolean | true | 是 | 是否使用跨进程守护;关闭后强制进程内判定(启动期生效) |
declarationSection | boolean | true | 是 | 是否在会话开始时注入声明规范章节;关闭只停章节,工具、收尾门与上游续行不受影响(启动期生效) |
标记为“设置页”的字段声明为 volatile,修改后立即生效,无需重启。crossProcess 与 declarationSection 例外:它们决定注册内容(socket 连接、系统提示词章节),按启动时取值,修改需「重启 DSH」生效。
设置界面
八个字段均在「设置 → DSH中断续行」中,由插件自带的客户端分区渲染。该分区不使用宿主根据 volatile 字段生成的自动表单;自动表单只提供输入框,无法承载关闭初始注入后的说明与复制按钮。
分区的排版与控件沿用宿主设置页本身的口径:标题 18px/600、说明 13px、设置行上下 16px 且行间 1px 分隔线、行标签 14px、行说明 12px;开关与输入框直接用宿主 @deepseek-ai/dsh-client-ui-primitives 的 Switch / Input / Button,颜色全部取自宿主 --dsw-* 设计令牌。亮色与暗色主题因此跟随宿主,插件不维护第二套配色。
写回调用宿主的 settings.update 并携带 revision。写入返回冲突(配置已在别处修改)时,分区先重新读取拿到新 revision,再按本次操作重试一次;重试仍失败才报错并把该字段的控件回填为真实值。开关采用乐观显示:实测宿主从写入到界面拿到新值约 1.5 秒,按服务端结果渲染会让开关在点击后保持原样,用户容易再点一次。
数字字段用「本地草稿 + 回车/失焦提交」,不逐键写入——一次带 revision 的写入就是一次文档变更,逐键写会把 3、30、300 这类中间态落盘。校验按配置表的取值类型逐项镜像(正整数 / 非负整数,各有下界;maxResumes 允许 0),非法草稿不提交,提示改为错误色并按类型说明原因,失焦即还原。有效值旁附一句可达性说明——阈值填太大不会报错,只会静默地永不触发,这类事实必须在配置页讲清楚。分区只在下一次写入撞冲突时重新读取,不订阅宿主的文档变更事件,因此另一窗口的改写不会实时反映到本页。
字段顺序是先开关后阈值:开关决定「这条路开不开」,阈值是「开了之后怎么调」。
关闭 declarationSection 后,分区顶部显示一段可直接写入用户级 AGENTS.md(~/.dsh/AGENTS.md)的文本,并提供两个复制按钮(规范正文 / AGENTS.md 片段);提示块放在字段之前,避免设置项增多后需要滚动才看得到。文本在构建期由宿主侧常量注入客户端产物,设置页复制的内容与真实注入内容一致。stallNoticeThreshold 自 0.4.1 起为 volatile,可在分区中直接修改;热改能否改变告警节奏尚未在真实会话中验证。
非法值在保存或启动时抛出,并指明字段,不回退默认值。静默回退会使配置修改看似生效而实际未生效,排查成本较高。
跨进程守护
多个 DSH 进程(web、CLI、临时实例)共用同一个守护进程,判定状态与留痕写入均在其中完成,因此遗忘计数跨入口累计,留痕不会交错。
- socket 与 pid 文件在
$DSH_HOME/run/(target-monitor.sock/.pid),权限0600/0700。 - 第一个 DSH 启动时自动拉起;最后一个连接断开后等待 30 秒退出,并删除 socket 与 pid 文件。宽限期避免同一入口关闭、另一入口启动时反复重启守护进程。
- 守护进程无法启动、被强制终止或协议版本不匹配时,插件退回进程内判定;功能不变,计数不再跨进程共享(每个进程各告警一次)。
排查:
ls -l "$DSH_HOME/run" # 有无 socket/pid
ps -p "$(cat "$DSH_HOME/run/target-monitor.pid")" # 进程是否还在
如需强制单进程模式(例如受限环境不允许后台进程),在 profile 配置中设 crossProcess: false。
工作原理
- 插件注册一个系统提示词章节,要求模型在收尾前声明目标状态(先声明,再汇报或提问)。
- 提供
target_status工具作为声明通道;插件不解析自然语言。 - 宿主在轮次即将关闭时派发
agent/turn-stopping,插件据此判断:- 已声明 → 放行;
- 未声明 → 注入提醒并将提醒置于队列首位,轮次继续。
- 提醒显式标注其非用户消息、不构成任何授权,并为等待用户确认的场景保留出口。
- 同一轮第 5 次仍未声明 → 向用户发一条“可能卡住”提示,每轮一次。
- 跨轮累计:注入后 30 秒内声明成功记一次“遗忘”;累计 3 次后,下一次提醒追加复述并清零。
- 上游错误由独立路径处理:宿主在错误路径不派发
agent/turn-stopping,只派发agent/error。插件在此判断错误码:可重试错误在轮次 idle 后投递同一条提醒并开启新一轮;不可重试错误仅留痕。
状态只存内存,按「顶层 agent × 轮次」定位。子 agent 不设门。
留痕与排查
每次判定写入一条记录,放行同样记录。缺少放行记录时,无法区分“未注入”与“未运行”。
# 某一轮的全部判定
jq -c 'select(.turn == 7)' ~/.dsh/logs/target-monitor/gate-trace.jsonl
# 最近 20 条
tail -n 20 ~/.dsh/logs/target-monitor/gate-trace.jsonl | jq -c .
| 字段 | 说明 |
|---|---|
at | 判定时刻(epoch ms) |
session | agent 标识的 8 位十六进制短哈希 |
turn | 轮次号;未知为 -1 |
outcome | injected 或 released |
reason | 收尾门:undeclared / declared / disabled / not-root / aborted / error;上游续行:upstream-resumed / upstream-skipped / upstream-capped |
attempt | 本轮第几次注入;放行时为 0 |
degraded | 仅在降级路径本地写入时出现;守护进程不可用期间的记录包含该键 |
某轮查不到记录,表示该轮未被判定。 单文件超 2 MiB 轮转为 .1,保留两代。
排查重复提醒时,判定时刻以 at 字段为准;消息到达时间不反映判定时刻。注入投递到下一步,长轮次中可能滞留于收件箱,直到轮次结束才显示。实测出现过判定发生在 17:39、提醒直到 19:11 才显示的情况,按到达时间检索会得到错误结论。
injected 之后出现同一 turn 的 released/declared 属于正常回路(提醒 → 补声明 → 放行),不表示门异常。同一轮出现多条 injected 且 attempt 不递增时,才需要检查。
记录只写入文件与内存缓冲,不进入对话消息、系统提示词或工具返回值。工具结果必然进入模型上下文,因此本项目不提供查询工具。
覆盖边界
已覆盖
插件只检查一个条件:轮次收尾时是否调用过 target_status。该条件不区分中断的上游原因,下列情形均在覆盖范围内:
| 情形 | 处理 |
|---|---|
| 思考泄露后直接收尾 | 未声明 → 注入续行提醒 |
| 工具调用畸形、调用被丢弃后收尾 | 未声明 → 注入续行提醒 |
| 模型自认完成但没声明 | 未声明 → 注入续行提醒 |
输出被 max-tokens 截断 | 注入提醒并说明截断后的工具调用未执行 |
| 同一轮反复不声明 | 第 5 次向用户发“可能卡住”提示,由用户决定是否中断 |
| 用户取消轮次 | 不注入、不复活;下一次输入恢复门控 |
| 上游断流 / 限流(重试耗尽后) | 自动续行一轮,默认上限 3 次;不可重试错误与取消不续行 |
上游断流 / 限流:分两层
处理分为两层:
- 单次请求失败由宿主
agent/request-error接管重试(waterfall 返回{ kind: 'retry' })。这一层本项目不重复实现。 - 重试耗尽后,轮次以
error结束。宿主在此路径派发agent/error,不派发agent/turn-stopping(dsh-agent-loop/lib/index.js:1001的派发位于正常收尾路径内),收尾门不介入。 - 插件在
agent/error上补充轮次级恢复:对可重试错误码(RATE_LIMIT/SERVER/TIMEOUT/TRANSPORT/EMPTY_RESPONSE,与宿主默认白名单一致)在轮次 idle 后投递一条提醒,并自动开启新一轮。默认连续上限 3 次,正常收尾后清零。 AUTH/QUOTA/CONTEXT_WINDOW_EXCEEDED不续行。对这类错误续行只会消耗额度且无法成功。第 5 次“可能卡住”提示仅由收尾门计数触发,与上游错误路径无关。
续行的代价是一整轮上下文重发,收益是无需用户手动催促。
不在范围内
| 项 | 归属 |
|---|---|
| 子 agent 的轮次 | ctx.agents.roots() 之外一律不介入 |
| 任务是否完成 | 模型的判断,插件只读声明 |
| 重试策略本身 | 宿主 agent/request-error |
| 流式渲染与工具参数校验 | 流式层与工具层 |
能力声明
以下七条构成本项目的能力边界:
- 声明是模型自述。 插件读取并记录声明,据此放行;它不验证任务是否完成。模型判断失误而在未完成时声明
done,插件照常放行。 - 注入仅为提醒。 模型可以忽略提醒继续收尾;插件在同一轮内会再次注入,不设硬上限(第 5 次向用户发一次提示,由用户决定是否中断)。
- 判定依据是宿主事件。 插件订阅
agent/turn-stopping与agent/assistant-stream,按0.2.1-rc.5的契约实现。宿主升级后这些契约可能变化,需要重新核对,依赖项记录在AGENTS.md的「宿主事实速查」。 - 取消即刻生效。 用户取消轮次后插件不再注入,也不复活该轮;下一次用户输入自动恢复门控。
- 留痕是旁路。 磁盘写入失败时插件继续判定与注入,只告警一次。
- 上游断流按错误码区别对待。 可重试错误自动续行,上限 3 次;不可重试错误(鉴权、额度、上下文超限)不续行。单次请求重试属于宿主
agent/request-error的职责,插件不重复实现,以免与其退避策略冲突。这个上限只约束上游异常续行,与第 2 条的收尾提醒分属两条预算线。 - 初始注入可关闭,且只关章节。
declarationSection为false时,插件不注入系统提示词章节,但target_status工具、收尾门与上游续行仍在工作。该开关用于「改用 AGENTS.md 承载规范」的场景:模型仍有声明通道,门也就仍有可满足的条件。该开关按启动时取值,修改需「重启 DSH」生效。
版本兼容
| 项 | 范围 |
|---|---|
| Node.js | >=22 |
| DeepSeek-Harness | >=0.2.1-rc.5 <0.3.0 |
| 许可 | MIT |
宿主主版本变化(0.3.x)视为不兼容:事件契约与工具注册方式可能改变,需要重新验证后放宽范围。
开销
提示词侧有三处固定成本,数字为实测:
| 项 | 字符数 | 约合 token | 频率 |
|---|---|---|---|
| 系统提示词章节 | 324 | 80–120 | 每轮(同会话前缀稳定,可被 prompt cache 复用) |
| 注入提醒 | 409 | 105–140 | 每次未声明收尾 |
| 规范复述 | 251 | 65–85 | 每累计 3 次遗忘 |
字符成本之外,主要开销是可能增加一轮模型请求:模型收到提醒后需先声明再汇报,多数场景下会多一次请求。实测 8 次注入均在 8–19 秒内被响应,中位数约 9.6 秒。
开销通过三处限制控制:仅在未声明时注入;面向用户的提示每轮至多一次;复述按累计次数节流。
跨进程模式另有一项固定开销:一个常驻守护进程(实测空闲 51 MiB,2000 次请求后 58 MiB)与每次判定的一次本机 socket 往返(实测 0.13 毫秒/次,500 次平均)。该开销不产生 token 成本;守护进程不可用时降级为进程内判定,功能不变。
设置界面另有一份一次性成本:客户端产物约 31.8 KiB(32 553 字节),随页面启动时的组合脚本下发一次,不产生 token 成本,也不随会话增长。
短会话与一次性问答场景中,收尾门的收益可能低于固定开销;长任务、多步实现与需要收尾汇报的场景更适合使用。
开发
npm install
bash scripts/link-host.sh # 开发期链接宿主已安装的 @deepseek-ai 包
npm run typecheck
npm test # node --test,199 项
npm run build
目录结构、宿主事实与编码约定见 AGENTS.md,设计取舍见 docs/PLUGIN-DESIGN.md。
版本与发布
版本遵循 语义化版本,变更记录见 CHANGELOG.md。
0.x 阶段:事件契约调整按次版本发布;宿主主版本跃迁(如 DSH 0.3.x)或插件导出面变化按主版本考虑。
npm 与 git 两条通道同时发布,同一版本号在两处指向同一份代码:
npm version <patch|minor|major> # 同步 package.json 与 tag
npm publish # 需要带 bypass 2FA 的细粒度 token
git push --follow-tags origin master
发布凭据是细粒度 access token(Write 权限 + Bypass 2FA),写进 ~/.npmrc 的 //registry.npmjs.org/:_authToken。写权限 token 最长 90 天,到期需重建。npm login 只给两小时会话且发布时仍要求 2FA,不能用于自动发布。
git 源的 commit 取该版本 tag 指向的提交,不使用分支 HEAD。
贡献
欢迎提交 Issue 与 PR。提交前执行:
npm run typecheck && npm test && npm run build
提交信息遵循 Conventional Commits,格式 <type>(<scope>): <subject>。触碰提示词注入、轮次边界或重试策略的改动需附带回归测试。
许可
本项目以 MIT 许可发布,版权归 © 2026 FirenzeClaw 所有。
MIT 允许使用、修改、再分发与商业用途,条件是保留版权声明与许可声明;完整条款见 LICENSE。
本项目依赖 DeepSeek-Harness 的内部事件契约,用于生产前请在目标版本上验证。验证步骤见 specs/001-target-monitor/quickstart.md。
Похожие плагины
webnovel-writer
lingfengqaq/webnovel-writer
loopx (dsh-loopx-plugin)
loopx-project/loopx
ouroboros (dsh-plugin)
q00/ouroboros
loopx (dsh-loopx-plugin)
huangruiteng/loopx