Vai al contenuto principale
F

dsh-target-achieved

firenzeclaw/dsh-target-achieved

DeepSeek-Harness 插件:轮次收尾时若模型未声明目标状态,注入续行提醒而非放行。处理思考泄露、工具调用畸形、上游断流导致的任务静默中断;上游中断类型可重试错误耗尽后自动续行,默认上限 3 次,跨进程共享计数。含设置界面与判定留痕

Installazione

dsh plugin --profile web add github:firenzeclaw/dsh-target-achieved

README

DSH-Target-achieved

为 DeepSeek-Harness 提供的收尾门插件:当轮次即将关闭而模型未声明目标状态时,注入一条续行提醒,避免轮次静默关闭。

License Node DSH Tests Version

目录

背景

本项目处理任务未完成而轮次已结束的情况:用户看到的是完成汇报或半截输出,任务实际未继续。

中断来源与处理方式如下:

中断来源实际占比表现本项目
思考泄露 / 工具调用畸形主要模型把推理过程或残缺的工具调用写入正文,随后结束轮次收尾处拦截:未声明目标状态不放行
任务中途收尾(模型自认完成)极低模型未声明即直接结束同一规则处理
输出被长度上限截断偶发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 后放行。

配置

字段类型默认设置页说明
enabledbooleantrue是总开关;关闭时判定直接放行,工具拒绝声明
reminderThreshold正整数3是累计几次遗忘后复述一次声明规范
reactionWindowMs正整数30000是注入后多长时间内的声明视为由提醒触发
stallNoticeThreshold正整数5是本轮第几次未声明时向用户发“可能卡住”提示
resumeOnUpstreamErrorbooleantrue是上游可重试错误后是否自动续行
maxResumes非负整数3是单轮内上游异常的最大续行次数;0 等价于关闭自动续行。只约束上游异常续行,收尾提醒不受此限
crossProcessbooleantrue是是否使用跨进程守护;关闭后强制进程内判定(启动期生效)
declarationSectionbooleantrue是是否在会话开始时注入声明规范章节;关闭只停章节,工具、收尾门与上游续行不受影响(启动期生效)

标记为“设置页”的字段声明为 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。

工作原理

  1. 插件注册一个系统提示词章节,要求模型在收尾前声明目标状态(先声明,再汇报或提问)。
  2. 提供 target_status 工具作为声明通道;插件不解析自然语言。
  3. 宿主在轮次即将关闭时派发 agent/turn-stopping,插件据此判断:
    • 已声明 → 放行;
    • 未声明 → 注入提醒并将提醒置于队列首位,轮次继续。
  4. 提醒显式标注其非用户消息、不构成任何授权,并为等待用户确认的场景保留出口。
  5. 同一轮第 5 次仍未声明 → 向用户发一条“可能卡住”提示,每轮一次。
  6. 跨轮累计:注入后 30 秒内声明成功记一次“遗忘”;累计 3 次后,下一次提醒追加复述并清零。
  7. 上游错误由独立路径处理:宿主在错误路径不派发 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)
sessionagent 标识的 8 位十六进制短哈希
turn轮次号;未知为 -1
outcomeinjected 或 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 次;不可重试错误与取消不续行

上游断流 / 限流:分两层

处理分为两层:

  1. 单次请求失败由宿主 agent/request-error 接管重试(waterfall 返回 { kind: 'retry' })。这一层本项目不重复实现。
  2. 重试耗尽后,轮次以 error 结束。宿主在此路径派发 agent/error,不派发 agent/turn-stopping(dsh-agent-loop/lib/index.js:1001 的派发位于正常收尾路径内),收尾门不介入。
  3. 插件在 agent/error 上补充轮次级恢复:对可重试错误码(RATE_LIMIT / SERVER / TIMEOUT / TRANSPORT / EMPTY_RESPONSE,与宿主默认白名单一致)在轮次 idle 后投递一条提醒,并自动开启新一轮。默认连续上限 3 次,正常收尾后清零。
  4. AUTH / QUOTA / CONTEXT_WINDOW_EXCEEDED 不续行。对这类错误续行只会消耗额度且无法成功。第 5 次“可能卡住”提示仅由收尾门计数触发,与上游错误路径无关。

续行的代价是一整轮上下文重发,收益是无需用户手动催促。

不在范围内

项归属
子 agent 的轮次ctx.agents.roots() 之外一律不介入
任务是否完成模型的判断,插件只读声明
重试策略本身宿主 agent/request-error
流式渲染与工具参数校验流式层与工具层

能力声明

以下七条构成本项目的能力边界:

  1. 声明是模型自述。 插件读取并记录声明,据此放行;它不验证任务是否完成。模型判断失误而在未完成时声明 done,插件照常放行。
  2. 注入仅为提醒。 模型可以忽略提醒继续收尾;插件在同一轮内会再次注入,不设硬上限(第 5 次向用户发一次提示,由用户决定是否中断)。
  3. 判定依据是宿主事件。 插件订阅 agent/turn-stopping 与 agent/assistant-stream,按 0.2.1-rc.5 的契约实现。宿主升级后这些契约可能变化,需要重新核对,依赖项记录在 AGENTS.md 的「宿主事实速查」。
  4. 取消即刻生效。 用户取消轮次后插件不再注入,也不复活该轮;下一次用户输入自动恢复门控。
  5. 留痕是旁路。 磁盘写入失败时插件继续判定与注入,只告警一次。
  6. 上游断流按错误码区别对待。 可重试错误自动续行,上限 3 次;不可重试错误(鉴权、额度、上下文超限)不续行。单次请求重试属于宿主 agent/request-error 的职责,插件不重复实现,以免与其退避策略冲突。这个上限只约束上游异常续行,与第 2 条的收尾提醒分属两条预算线。
  7. 初始注入可关闭,且只关章节。 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频率
系统提示词章节32480–120每轮(同会话前缀稳定,可被 prompt cache 复用)
注入提醒409105–140每次未声明收尾
规范复述25165–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。

Plugin correlati