dsh-auto-continue:DeepSeek Harness 轮次边界自动续行插件

前言

DeepSeek Harness(DSH)的 agent loop 把「一次模型请求失败」当作整个轮次的终点。当网关把长 SSE 掐断(MALFORMED_RESPONSE / STREAM_CLOSED),或重试预算耗尽时,轮次直接结束,会话停在 idle 等待手动输入。

@deepseek-ai/dsh-llm-retry 只负责轮次内同一步的重试,无法处理跨轮次的临时故障。本插件在轮次边界补上这一层。

插件定位

dsh-auto-continue 是一个为 DeepSeek Harness 设计的 Cordis 插件,维护者为 kemomimi12。它会在轮次结束后自动排队一条续行提示词,让会话在模型请求临时失败后继续执行。

插件发布在 npm 官方源,公开包,安装不需要任何 token。

核心功能

1. 轮次边界自动续行

当会话因为模型请求失败而结束轮次时,插件会自动排队一条续行提示词。续行提示词会告知模型上一次轮次结束的原因,并要求它重新核对工作区与工具结果,而不是相信之前的叙述。

2. 故障风暴合并

同一错误码在时间窗内反复出现视为一场故障,只买一次续行。避免网关整分钟不可用时,每次失败都像独立事件,各自买一次续行。

3. 四道闸门控制

一次失败要依次穿过四道闸门才续行:

  1. 永久错误:AUTH / QUOTA / INVALID_REQUEST / CONTEXT_WINDOW_EXCEEDED 直接放弃
  2. 连续失败上限:maxConsecutiveFailures(默认 3)
  3. 滚动预算:maxResumesPerWindow(默认 5,窗口 1 小时)
  4. 风暴预算:maxResumesPerStorm(默认 1)

实际续行次数取这几者的最小值。

4. 手动停止优先

手动停止永远优先。续行消息是立刻排进 inbox 的,不是藏在不可见的定时器里。你按停止 → cancel() 清空 inbox → 插件观察到消息消失并停手。

5. 人类输入优先

一旦你发出消息,插件会把自己还排在队里的续行提示撤掉。

安装

方式一:从 npm 安装(推荐)

让 dsh 里的模型调用 plugin_manager 工具:

install_bundle target: @xfufu/dsh-auto-continue

install_bundle 会自己处理 pnpm 安装、写 profile 的 package.json 与 bundle 列表,不要手工编辑。

方式二:从 GitHub clone

不需要 npm 也能装,适合想改源码的人:

git clone https://github.com/kemomimi12/dsh-auto-continue.git D:/plugins/dsh-auto-continue
install_bundle target: D:/plugins/dsh-auto-continue

方式三:手工写 profile

编辑你的 profile($DSH_HOME/profiles/<name>/):

  1. package.json 的 dependencies 加一行:
   "@xfufu/dsh-auto-continue": "^1.0.1"
  1. 同一个 package.json 的 dsh.profile.bundles 追加:
   "@xfufu/dsh-auto-continue"
  1. 在 profile 目录跑一次 pnpm install。

验证安装

插件行出现在 profile 里且状态为 active 即成功。可用 plugin_manager 的 list_plugins 查看,或直接查:

include:auto-continue enabled: true fiberPhase: active

再确认日志里有这一行(说明启动自检通过):

auto-continue: host interfaces verified, resuming enabled

配置

写在 cordis.patch.yml(或直接改 profile 里 auto-continue 那一行):

- id: auto-continue
  name: '@xfufu/dsh-auto-continue'
  config:
    enabled: true
    maxResumesPerWindow: 5        # 滚动窗口内最多自动续行几次
    windowMs: 3600000             # 窗口长度(1 小时)
    maxConsecutiveFailures: 3     # 连续失败几次后彻底停手
    includeSubagents: true        # 是否也管子代理会话
    stormWindowMs: 120000         # 同码失败间隔小于它就算同一场风暴
    maxResumesPerStorm: 1         # 一场风暴最多买几次续行

这一行是可选的。不写就用代码里的默认值(与上表一致)。

注意:本插件没有声明 Config schema,所以 plugin_manager 的配置页看不到字段,改配置直接编辑 cordis.patch.yml。

配套配置:retryPolicy

本插件只处理跨轮次的恢复。让内置重试先接住可重试的协议错误,才能少花上下文。在 cordis.patch.yml 里给提供方适配器加上:

- id: llm-deepseek
  name: "@deepseek-ai/dsh-llm-deepseek-api-key"
  config:
    # ... 你原有的 baseURL / models / apiKeyEnv ...
    retryPolicy:
      mode: normal
      maxRetries: 5
      retryableCodes:
        - EMPTY_RESPONSE
        - RATE_LIMIT
        - SERVER
        - TIMEOUT
        - TRANSPORT
        - MALFORMED_RESPONSE   # tool 参数 JSON 被截断
        - STREAM_CLOSED        # 流在 message_stop 之前结束
      backoff:
        initialDelayMs: 1000
        maxDelayMs: 120000     # 必须 ≥ 网关的 Retry-After,否则会放弃重试
        jitterRatio: 0.2

maxDelayMs 小于提供方返回的 Retry-After 时,normal 模式会直接放弃重试。若你的网关会返回 Retry-After: 120000,这里就得是 120000。

续行提示词示例

进入历史的是普通 user 角色消息,source.kind === 'auto-continue':

<auto_continue>
The previous turn ended because a model request failed: TRANSPORT: DeepSeek Messages transport failed
This is automatic resume attempt 2. The session state above is authoritative.
Re-check the current workspace and tool results instead of assuming earlier narration is still current, then continue the task from where it stopped.
Do not repeat tool calls whose results are already in the transcript.
</auto_continue>

提示词刻意让模型重新核对工作区而不是相信之前的叙述,这正好对上「网关断流后可能有半截工具调用」的场景。

实现约束

本模块只 import node:crypto,不 import 任何包。原因:bundle 以 link: 方式安装后,ESM 从真实路径解析裸说明符(realpath 之后在 profile 之外),@deepseek-ai/* 会解析失败。所以续行消息在本地构造,config 也在本地校验,不依赖 @deepseek-ai/schemastery。

代价是没有 Config schema:plugin_manager 的配置页看不到字段,改配置直接编辑 cordis.patch.yml。

测试

npm run verify       # 全部:28 项行为 + 风暴 + 回放 + 打包校验
npm test             # 仅 28 项行为测试(含 7 项启动自检用例)
npm run test:storm   # 风暴合并效果演示
npm run test:replay  # 真实故障序列回放
npm run verify:pack  # 检查 npm 包内容(必需文件齐全、无测试文件泄漏)

test.mjs 覆盖:临时失败续行、永久错误不续行、用户中止不续行、人类输入撤回续行、连续失败上限、模型输出不算进展、正常结束才算进展、失败续行不重复购买、滚动预算、单风暴只买一次、不同码各自成风暴、超窗口算新风暴、子代理开关、busy 时不打扰、卸载后不再触发、7 项启动自检。

已知限制

  • 只在轮次边界工作。它不能修复一次失败的请求本身,只能在该轮次结束后重开一轮。
  • 续行会多花一次完整上下文。这是它排在 retryPolicy 之后的原因。
  • 没有持久化状态。插件状态在内存里,dsh 重启后预算与风暴记录归零。
  • 未在多个 dsh 版本上验证。依赖的公开接口见下方启动自检;自检能挡住接口缺失,但挡不住语义变化。
  • 子代理会话默认也管。若子代理失败后你想让它直接结束,把 includeSubagents 设为 false。

兼容性

项 要求
Node ≥ 22(用了 randomUUID、setImmediate)
dsh 提供 agents / sessions 服务与 session/event 事件(当前 desktop 版本满足)
权限 无特殊要求,纯 Host 插件,不碰文件系统

启动自检

插件激活时会结构性检查它依赖的每个接口是否存在,不依赖版本号字符串。任一缺失就拒绝激活,并输出具体缺少的接口。

适用场景

适合在以下场景使用:

  • 模型请求经常因网络波动、网关超时等临时故障中断
  • 需要减少手动干预,让会话尽可能自动恢复
  • 对资源消耗敏感,希望复用已有的 retryPolicy 而不是多花一次完整上下文

插件以当前 dsh 进程权限运行,安装前应检查源码与许可证。

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye