dsh-plugin-auto-retry

DSH PLUGIN通知与集成

仓库 aqiu817/dsh-plugin-auto-retry

通知与集成 L5 · 运行验证通过 安全审查:低风险 可信度:金 可安装插件
安装这个插件

dsh-plugin-auto-retry 是 aqiu817 维护的 通知与集成 类 DSH 可安装插件, 仓库为 aqiu817/dsh-plugin-auto-retry,许可证 MIT。 下面按「装什么、怎么装、验没验过、跑起来要注意什么」逐项列出。

一句话选型结论

按资料字段整理

dsh-plugin-auto-retry 是通知与集成类的 DSH 可安装插件,验证等级 L5 · 运行验证通过,安全审查为低风险,有可直接复制的安装命令。验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么;涉及密钥或系统权限的,建议先在隔离环境试装。

分类通知与集成 收录类型可安装插件 验证等级L5 · 运行验证通过 安全审查低风险
验证等级 L5 L5 · 运行验证通过
GitHub 星标 1 复刻 0 · 未关闭 issue 0
最近更新 2026-10-21 创建于 2026-10-14
README 26.7 KB 正文体量,越大说明说明书写得越细

安装

可安装插件
在 DSH 里执行,装完用 dsh plugin list 确认已挂上;标了 L4 / L5 的命令已在沙盒里真实跑通
dsh plugin --profile web add github:aqiu817/dsh-plugin-auto-retry
  1. 1
    确认版本与运行环境

    仓库没有标注兼容版本与 Node.js 要求,先确认你的 DSH 版本能装,再继续。

  2. 2
    执行安装命令

    在 DSH 里执行上面的命令;多包仓库要注意区分具体子包名,别装错包。

  3. 3
    确认已挂载

    用 dsh plugin list 查看列表里是否出现这个插件,出现即挂载成功。

  4. 4
    试跑并检查权限

    加载后跑一个最小用例,确认它能正常工作;同时看它申请了哪些文件、命令或网络权限。

兼容 DSH 版本未标注版本不匹配时安装会报错
Node.js 要求未标注来自仓库 package.json 的 engines 字段
前置依赖无需要同时安装的同级依赖
npm 包名未发布到 npm可用 npm view 查看包信息

能做什么

通知与集成
字段来自项目仓库与公开索引,中文说明由本站标注

适用场景

通知

上游标签

cordisdeepseek-harnessdshdsh-pluginllmretry

验证等级与安全审查

L5 · 运行验证通过
自动化流程结论,不是人工审核
验证等级L5 · 运行验证通过在 dsh 沙盒中完成安装与运行冒烟——装得上、跑得起来
安全审查低风险静态分析结论,未标注不代表已通过审查
仓库健康度活跃按仓库活跃度与最近更新时间给出
可信度金综合仓库活跃度与验证结果

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 验证等级 L5:安装和运行都在沙盒里跑通过,装完能直接用的概率高。
  • 静态安全审查为低风险,没有发现明显可疑行为。
  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有声明 Node.js 版本要求,版本不匹配可能装不上。
  • 验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么。

更适合

  • 需要在通知与集成场景里给 DSH 补能力的人。
  • 想装完就能用、不想自己排依赖的人。
  • 能接受在本地装依赖、愿意自己确认权限的人。

不太适合

  • 只使用 DSH 官方内置能力、不接受第三方插件的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
插件验证等级安全审查星标兼容版本安装命令
dsh-plugin-auto-retry当前 L5 · 运行验证通过 低风险 1 未标注 有
treg L3 · 安装规范通过 低风险 4,976 未标注 无
dsh-purge 未做沙盒验证 未标注 3,412 未标注 有
dsh-im L5 · 运行验证通过 低风险 1,837 0.1.7-rc.2 等 10 个 有
dsh-dafeiyu L5 · 运行验证通过 低风险 425 0.1.7-rc.2 等 10 个 有
dsh-trading L3 · 安装规范通过 低风险 235 未标注 无

仓库档案

通知与集成
字段来自项目仓库与公开索引,中文说明由本站标注
作者 / 维护方aqiu817仓库 dsh-plugin-auto-retry · 默认分支 main
分类通知与集成notify
收录类型可安装插件声明了 dsh.bundle、可用 dsh plugin add 安装的条目
开发语言未标注仓库主语言
许可证MIT商用或二次分发前请确认条款原文
星标 / 复刻 / issue1 / 0 / 0收录时的数值,非实时
创建 / 更新2026-10-14 · 2026-10-21最后推送 2026-10-14
本站收录2026-10-30首次进入本站目录的时间

README 全文

26.7 KB
作者原文,中文版优先;只做安全转义,不改内容

dsh-plugin-auto-retry

官方重试放手的那些失败,我们接住。

简体中文 | English

DeepSeek Harness 宿主侧插件。它挂在官方 @deepseek-ai/dsh-llm-retry 主动放弃的那个位置,接住它让出去的模型请求失败,按「接续它的退避曲线」再试几次;同时在轮次尾部给一个默认折叠的面板,把原生转录里被丢掉或替换掉的完整报错还给读者。

没有客户端注入的伪造消息,不替换也不改动任何官方插件。




目录

  • 它解决什么
  • 与官方重试的关系
  • 官方不会自动重试的部分
  • 安装
  • 配置
  • 工作原理
  • 不冲突保证
  • 客户端半部
  • 安全设计
  • 测试
  • 已知限制
  • 故障排查
  • 开发
  • License

它解决什么

dsh 自带重试:@deepseek-ai/dsh-base 里挂了

- id: llm-retry
name: '@deepseek-ai/dsh-llm-retry'

它在 agent/request-error 这个 waterfall 上工作,按 provider 路由的 retryPolicy 重跑失败的模型请求。但它只在一个很窄的口子上工作:

  • 只认固定的几个失败 code
  • 预算有界(normal 模式默认 5 次)
  • 适配器没声明策略时用一套默认值

于是有大量失败一次都不重试。这个插件补的就是这一段。

它不替代官方执行器,而是挂在它主动放弃的那个位置——官方在决定「不重试」时一定会调用 next(),而 waterfall 按注册顺序外层优先、dsh-base 又先于任何 profile bundle 加载,所以官方永远是外层,我们永远是下游。官方让出去的每一次失败,都会精确流到我们手上。

与官方重试的关系

模型请求失败
│
├─ dsh-llm-retry(外层,dsh-base 挂载)
│     ├─ 可重试且预算未耗尽 → backoff() → { kind: 'retry' }   ← 我们不参与
│     └─ 否则 → next()  ──────────────────────────────┐
│                                                      │
└─ auto-retry(下游,本插件) ←─────────────────────────┘
├─ 命中硬拒绝集合 → 交回终态
├─ 不在白名单     → 交回终态
├─ 超出额外预算   → 交回终态
└─ 否则:先持久 → 可取消等待 → 追加 started → { kind: 'retry' }

官方默认 normal 策略(dsh-llm/lib/types/retry-policy.js):

retryableCodes: EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT
maxRetries:     5
backoff:        500ms → 10000ms,抖动 0.1

官方不会自动重试的部分

# 缺口 官方为何跳过 本插件
G1 code 不在 retryableCodes 显式集合判断 ✅ 接手
G2 预算耗尽 有界预算 ✅ 接手
G3 retryPolicy === undefined(最终适配器选定前的失败) 无策略 ✅ 接手(限白名单)
G4 UNKNOWN——非 HarnessError 穿过适配器边界 不在集合内 ⚠️ 需手动开启
G5 工具/middleware/扩展失败 根本不进 agent/request-error ❌ 不做
G6 直接 ctx.llm.stream() 消费方 无 agent 轮次 ❌ 不做
G7 aborted 用户主动取消 ❌ 永不重试
G8 CONTEXT_WINDOW_EXCEEDED / IMAGE_OFFLOAD_REQUIRED 压缩/图片卸载拥有 ❌ 永不重试

落到常见路由上的具体缺口:

适配器 产生但不被重试的 code
@deepseek-ai/dsh-llm-pi-ai PI_AI_ERROR(它的兜底)、QUOTA、INVALID_REQUEST、AUTH、UNKNOWN
@deepseek-ai/dsh-plugin-kilo-gateway PROVIDER_ERROR、INVALID_MODEL、INVALID_REQUEST、QUOTA、AUTH

两处代码里已经写下的证词:

  • dsh-plugin-kilo-gateway/lib/index.js:229:「把一切都归成一个 code……会静默地完全禁用重试,因为默认可重试集合不包含那个 code。」
  • dsh-llm-agentrouter/lib/index.js:144:「INVALID_REQUEST 不在 harness 默认可重试集合里,所以一个 400 就结束整个 turn。」

安装

标准 dsh bundle。推荐走 plugin_manager 的 install_bundle,由它代写 profile 的依赖与 patch:

plugin_manager(action: "install_bundle", target: "dsh-plugin-auto-retry")

手工安装则是在 profile 的 package.json(~/.dsh/profiles//package.json)里加依赖并加进 bundles:

{
"dependencies": {
"dsh-plugin-auto-retry": "^0.1.0"
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
"@deepseek-ai/dsh-web-app",
"dsh-plugin-auto-retry"
]
}
}
}

本地改代码时把版本号换成目录链接,省掉每次 npm install:

"dsh-plugin-auto-retry": "link:/absolute/path/to/dsh-plugin-auto-retry"

cordis.patch.yml 会通过 dsh.bundle.patch 自动挂进 patch 列表,不需要手写那条 - insert:。

装好之后先跑一次 npm test:contract.test.mjs 会逐个检查 package.json 里 files 指向的每个文件、依赖目录及其 package.json 是否真实可读,并重复 import 25 次确认稳定。

卸载

删掉依赖和 bundles 里那一行,再装一次依赖。没有残留状态。

> 本插件不是 required bundle:组合失败不会写启动日志。装完请确认 Plugins 页面能看到 auto-retry 这一项。

配置

六个字段全部是 .volatile():在 Plugins 页面改了不重载 fiber,enabled: false 中途就能掐断一条正在跑的重试链。

在哪里改:侧栏 → 插件 → 找到 dsh-plugin-auto-retry 这个组合包 → 打开它,配置表单就在这一页上。表单由本插件的浏览器半部通过 plugins.bundle.config slot 提供,key 是包名本身。

> 这一点容易踩空:宿主侧的 Config schema 本身不会在 Plugins 页面产生任何配置控件。配置区只在某个插件注册了 slot 时出现——组合包级的 plugins.bundle.config(key = 包名),或行级的 plugins.row.config(key = <包名>#<行 id>,需要再点进那一行才会有「配置」入口)。这里选组合包级:本插件只声明一行,两者内容完全一样,但组合包级少一次点击。所以一个只有宿主半部的插件(例如 dsh-plugin-continue-on-limit)在 Plugins 页面上看不到配置——它的 Config 依然生效,只是没有编辑入口。
>
> 组合包级的 slot 不会把表单对象递给你(行级才会),所以浏览器半部自己从 configForms 服务取命名空间 auto-retry 的表单。这个服务属于设置包,因此它不写在浏览器半部的 inject 里、而是单独开一个 fiber 去要:这样在一个没装设置包的部署里,丢掉的只有配置页,重试面板照常工作。
>
> 命名空间 auto-retry 必须与 cordis.patch.yml 里的行 id 一致,否则配置页会一直是空的(契约测试交叉检查这两处)。

表单行为:改动暂存到按下保存;保存只提交真正变动的字段,全部挂在同一个 revision 栅栏下,所以并发写入会被拒绝而不是被静默覆盖。离开该页面会丢弃暂存改动。数字字段超出边界或无法解析时,保存会被拦下并指出是哪个字段——不会把宿主会拒绝的值发出去。

字段 类型 默认 边界 说明
enabled boolean true — 总开关
extraRetries integer 3 0–10 官方预算之外额外允许的尝试次数;0 = 不介入
initialDelayMs integer 1000 0–600000 官方没试过时的起步延迟
maxDelayMs integer 60000 0–600000 外层上限,也用于钳制 provider 延迟
jitterRatio number 0.2 0–1 对称抖动比例
retryableCodes string[] 见下 至少 1 个 我们愿意接手的 code

retryableCodes 在页面上是开关,不是文本框——没有哪个接口能列出「适配器可能返回的全部 code」,所以内置了一份目录,分三组渲染:

分组 呈现 code
默认开启 开关 EMPTY_RESPONSE RATE_LIMIT SERVER TIMEOUT TRANSPORT PROVIDER_ERROR PI_AI_ERROR
按需开启 开关 QUOTA ACCOUNT_QUOTA INVALID_REQUEST UNKNOWN
永不重试 开关但禁用 AUTH MISSING_CREDENTIAL INVALID_CREDENTIAL NO_ADAPTER INVALID_MODEL INVALID_CONFIG CONTEXT_WINDOW_EXCEEDED IMAGE_OFFLOAD_REQUIRED ABORTED

第三组照样列出来、但开关是禁用的:这样页面既说清了有哪些 code 存在,也说了它们勾不动——硬拒绝是策略常量,不是偏好。开关的初始状态来自已接受的值,不是目录默认值;目录外的 code(某个适配器自己发明的)落在「其他错误码」文本里,每行一个。

一个 code 都不留是不允许的,页面会在暂存阶段就说明,并在保存时拦下。原因在宿主侧:codesWithin() 把空列表当成笔误,回落到默认白名单(否则一个输错的配置会悄悄把插件关掉,而设置页还显示它开着)。所以存一个空列表会让页面显示「一个都不重试」、而插件实际还在重试 7 个 code。要么勾一个,要么用 enabled: false / extraRetries: 0 表达「不介入」——那两个是明确的,不会和页面打架。

> 手改 cordis.patch.yml 写 retryableCodes: [] 同样会回落到默认白名单。手上的配置能被页面表达,就不该在页面之外生效。

默认白名单:

EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT,   ← 官方那 5 个(接手 = 延长预算)
PROVIDER_ERROR, PI_AI_ERROR                                ← 两个适配器的兜底 code

硬拒绝集合(代码常量,不可配置,优先于白名单):

AUTH, MISSING_CREDENTIAL, INVALID_CREDENTIAL, NO_ADAPTER,
INVALID_MODEL, INVALID_CONFIG,
CONTEXT_WINDOW_EXCEEDED, IMAGE_OFFLOAD_REQUIRED, ABORTED

硬拒绝优先于白名单。这是安全底线:一个误输的白名单不该有权力去重试认证失败或上下文溢出。

QUOTA / ACCOUNT_QUOTA / INVALID_REQUEST / UNKNOWN 不在默认白名单,也不在硬拒绝集合——它们既可能是瞬时的,也可能是真正终态的,所以由部署自己决定:加进 retryableCodes 就生效。这样守住了「route on code, never parse message」这条平台原则,又给中继的额度池与 400 方言拒绝留了口子。

cordis.patch.yml 里的默认值:

- insert:
- id: auto-retry
name: 'dsh-plugin-auto-retry'
config:
enabled: true
extraRetries: 3
initialDelayMs: 1000
maxDelayMs: 60000
jitterRatio: 0.2
retryableCodes: [EMPTY_RESPONSE, RATE_LIMIT, SERVER, TIMEOUT, TRANSPORT, PROVIDER_ERROR, PI_AI_ERROR]

工作原理

1. 挂载点:只接官方让出去的失败

ctx.on('agent/request-error', async (payload, next) => {
const terminal = await next();                    // 先让下游/终态说话
if (terminal !== void 0) return terminal;
if (lifetime.signal.aborted || payload.signal.aborted) return terminal;
// …资格判定 → 计数 → 持久 → 可取消等待 → { kind: 'retry' }
});

官方在决定重试时不会调用 next(),所以我们物理上收不到那次失败——不存在双重试的可能。

失败发生在打开的 step 内部、任何工具结果被合成之前,因此:

  • 同一份持久历史,请求仍可从会话日志重建
  • 不产生合成的 TOOL_OUTCOME_UNKNOWN 结果,不会诱导模型重跑副作用工具
  • 尊重同一个 signal,取消语义天然正确
  • 不注入伪造的用户消息,转录干净
  • 不走 session/event 回调,没有 append 重入问题

harness 文档对此边界的原话:

> 最终适配器选择、分发与迭代失败以终止结束的形式到达并进入 agent/request-error;处理该失败的监听器返回 { kind: 'retry' } 且不调用 next(),未被处理的失败则是终态。Middleware、结果处理、工具及其他扩展失败仍会抛出并直接关闭轮次——插件失败结束的是轮次,不是循环。

2. 退避:接续官方曲线,不另起一段

先读官方在当前 step 内已排的重试次数(按 provider 过滤、排除我们自己的 key):

const state = ctx.sessionProjections.stateOf(agent.session, 'llmRetry');
const official = officialRetries(state, provider);

然后:

if (official <= 0) return localDelay(ourRetry, settings, random);   // 官方没试过 → 1s 起步
const curve = {                                                     // 官方试过 → 接续它的曲线
initialDelayMs: officialPolicy.initialDelayMs,
maxDelayMs: Math.min(officialPolicy.maxDelayMs, settings.maxDelayMs),
jitterRatio: officialPolicy.jitterRatio,
};
return localDelay(official + ourRetry, curve, random);              // 在它最长的那次之后再推一格

localDelay 与官方逐字同构:

const exponential = Math.min(initialDelayMs * 2 ** (retry - 1), maxDelayMs);
const jitter = 1 - jitterRatio + 2 * jitterRatio * random();
return Math.min(exponential * jitter, maxDelayMs);

效果对照:

场景 官方已试 我们的第 1/2/3 次延迟
PI_AI_ERROR(官方完全不管) 0 1s / 2s / 4s
RATE_LIMIT 预算耗尽 5(base 500, cap 10000) 10s / 10s / 10s(封顶)

为什么接续而不是重来:官方放弃时,它的曲线已经推到很深了。从 1 秒重新开始等于对着刚刚让我们慢一点的 provider 猛敲。

providerRetryAfterMs 有效时优先用它,钳到 maxDelayMs。

> 与官方的一处有意差异:官方 normal 模式在 providerRetryAfterMs > maxDelayMs 时直接放弃重试;我们改为钳制后仍然重试。理由:provider 是在告诉我们「什么时候再来」,直接放弃等于白丢一次机会。

3. 计数:复用官方投影,不自己造

const key = policyKey(settings);                                    // '["auto-retry",…]'
const entry = state?.[JSON.stringify([provider, key])];
const ourRetry = (entry?.retry ?? 0) + 1;
const retryId = entry?.retryId ?? randomUUID();

官方投影的 apply 在 step/start 与 turn/end 时清空——正是我们要的「每 step 一套预算」,且随日志折叠,重启后依然正确。

4. 先持久,后等待

等待前追加 llm/retry,返回前追加 llm/retry-started,复用官方的事件词汇,所以官方的 llmRetry 投影与(若挂载)invariant 伴生插件都认得。追加失败即放弃重试(写日志、返回终态)——宁可少重试一次,也不留一个不可见的待处理重试。

不冲突保证

# 保证 依据
1 粒度不同:官方重跑打开的 step 内的请求;我们只在它 next() 后介入 官方决定重试时不调用 next(),我们收不到
2 policyKey 命名空间隔离:我们是 JSON.stringify(['auto-retry', …]),官方是 JSON.stringify([mode, …]) 首元素不同,永不相等;官方投影把两条链分开计数
3 只扩展不替换:不禁用、不覆盖、不注销官方插件,不写它的 config 只多注册一个 listener
4 预算独立:extraRetries 按我们自己的 policyKey 计数 同 #2
5 取消语义一致:AbortSignal.any([payload.signal, lifetime.signal]) 与官方 recover() 同构
6 先持久后等待 复用已知事件类型
7 满足 invariant 全部规则 retry 为正且 ≤ maxRetries、在打开的 turn/step 内、provider 匹配、retryId 同链保持
8 always 模式不打架:官方 settleDownstream(next) 先问下游 我们返回 { kind: 'retry' } 它照办,不返回它自己重试
9 不动转录:不调用 followup() 与 dsh-plugin-continue-on-limit 零交集(触发 reason 不同)
10 永不重试取消:硬拒绝含 ABORTED,信号中断即放弃 —

客户端半部

原生已经把 llm/retry 渲染成默认折叠的 `(ModelRetryItem`),所以我们的重试会自动出现在转录里,无需额外工作。

原生丢掉的是内容:

位置 现状
failureMessage()(dsh-client-ui-chat) 对 AUTH / QUOTA / ACCOUNT_QUOTA / 账户类 code,把原文整段替换成一句本地化短语——真实报错被丢弃
TurnErrorItem 终态报错行内联渲染,不可折叠,长 errorChain 会占满屏幕
两者 都不暴露 status / requestId / provider 路由 / turn·step / 等待时长

本插件在轮次尾部(conversation.chat.turnTail)加一个默认折叠的面板,只在「本轮有重试或有终态错误」时出现:

  • 摘要一行:本轮重试 3 次 · 最终失败 RATE_LIMIT
  • 展开后逐条列出,每条给全:来源(插件/官方)、第 N/M 次、错误码、HTTP、提供方、第 N 轮 · 第 M 步、等待毫秒、请求 id、完整报错原文

数据流:

llm/retry 事件 ─┐
├─→ auto-retry definition(target: chat)
turn/end(error) ┘        └─→ 每轮一个 hidden view node
└─→ turnDataSource(turn, 'auto-retry')
└─→ 轮次尾部面板

折叠面板只用主题 token(--dsw-alias-* / --dsw-static-* / --dsw-radius-*),不 import @deepseek-ai/dsh-client-ui-primitives,样式随 fiber 卸载一起移除。

配置页

同一个浏览器半部还注册 plugins.bundle.config(key dsh-plugin-auto-retry),也就是组合包页面上的配置区。六个字段各一个控件:四个数字输入、一个总开关,加一组 code 开关。

它不做自己的校验逻辑——边界只是让输入框提示正确范围,真正的把关仍在 readSettings() 与 schema 那一层。保存时只提交变动过的字段:

{ op: 'set', path: ['retryableCodes'], value: ['RATE_LIMIT', 'TIMEOUT'] }
{ op: 'set', path: ['extraRetries'], value: 5 }

全部操作共用宿主读到的 revision,所以编辑期间发生的并发写入会让这次保存被拒绝,而不是被静默覆盖。

code 开关的列表顺序与「同一组 code」判断都不敏感:开关永远按目录顺序提交,所以仅仅重排不会产生一次写入。目录里被硬拒绝的 code 即使已经在配置里、也只会以「已勾选但禁用」的样子出现——页面如实报告配置,不会顺手把它删掉,因为读者没碰过的东西不该产生写操作。

组合包级的 slot 是常驻的:只要组合包在,配置区就在,无论宿主是否真的提供了 auto-retry 这个命名空间。所以页面区分三种状态——正在读取(loading)、不可写(unavailable,部署没提供这个命名空间,或本页只读)、就绪——前两种只说状态,不画表单:一个 Save 注定失败的输入框比一句说明更糟。

安全设计

三道闸

  1. extraRetries(默认 3)——主闸。这是官方预算之外的额度,不是总数。
  2. 接续退避——重试间隔逐次拉长,给上游恢复时间。
  3. enabled——随时可以掐断正在跑的重试链。

隐式保护

  • 只重试白名单里的 code,且硬拒绝优先
  • 尊重 failure.providerRetryAfterMs
  • 同一个失败不会被我们调度两次(waterfall 是逐个 await 的)
  • 非 error 的 turn end 一律不动
  • 追加失败即放弃,不写日志以外的东西
  • 等待可取消,fiber 卸载会中断并 drain

配置值不会被敲错一个数就失控

.volatile() 字段是从 Plugins 页面直接写进 reference cell 的,不走加载器的 Config 校验。所以 readSettings() 对每个字段做类型检查 + 钳制,负数回落到默认值而不是 0——0 是「关掉这道闸」,一个误输的负数不该有这个权力。

输入 结果
extraRetries: -1 / 99 / 2.5 / 'lots' 回落到默认 3
extraRetries: 0 保留 0(显式不介入)
initialDelayMs: -100 回落到默认 1000
jitterRatio: -0.5 / 7 回落到默认 0.2
retryableCodes: [] / 'RATE_LIMIT' / [1,2] 回落到默认列表
enabled: 'nope' 回落到默认 true

retryableCodes 的类型检查里 Array.isArray 是承重的:一个裸字符串会走到 String.prototype.includes,任何子串都会匹配,一个白名单笔误就能悄悄授权没人列过的 code。

测试

npm test

五层:

文件 覆盖
test/policy.test.mjs 纯决策逻辑,直接喂普通对象,不 import 任何 Cordis。资格矩阵、硬拒绝优先、接续退避、provider 延迟钳制、policyKey 命名空间、官方进度读取
test/index.test.mjs 用 mock ctx 驱动真实的 apply(),断言 waterfall 契约(下游优先、终态让路)、持久事件顺序与字段、预算与接续、retryId 连续性、跳过分支、异常边界、生命周期 drain
test/client.test.mjs stub __ModuleLoader__ 捕获 factory,断言事件折叠、hidden 节点、摘要文案、字段映射、来源标注、AUTH/QUOTA 原文不被替换、配置页的开关与暂存提交
test/contract.test.mjs 加载契约,不测行为,只测「装上会不会炸」。复刻加载器 jc() 的逐字逻辑,检查 dsh.client / ./client / files 全指向真实文件,并交叉核对配置命名空间与 patch 行 id
test/slots.test.mjs 对着应用里真实的 SlotCore 注册一次,断言它接受这份 options、生成出的 entry 形状(options.key / locale / inject)与渲染器读到的字段一致。应用没装时自动跳过

> test/slots.test.mjs 直接读安装目录里的 app.asar,所以它在别的机器上会跳过(DSH_APP_ASAR 可以指向别的安装)。这么做是因为「mock 接受」和「真实注册表接受」是两个问题:一个只有 mock 通过、注册表拒绝的形状,在浏览器里表现为配置区根本不出现。

> npm test 就是 node --test "test/**/*.test.mjs",和 CI、prepublishOnly 用的是同一条命令。--test-isolation=none(npm run test:in-process)是给禁止子进程的沙箱准备的逃生口——node --test 默认要给每个测试文件起子进程,而 DSH 自己的文件沙箱不允许;这个 flag 需要 Node 24+,所以它不在默认命令里,否则声明 engines: >=22 的包会在 Node 22 上直接报 bad option。

已知限制

  • 适配器已经砍掉的原文无法还原。 kilo-gateway 做 detail.slice(0, 300),pi-ai 也各有截断。面板保证的是「宿主记录下来的部分完整呈现」,不是「提供方原始响应完整呈现」。
  • 工具/middleware/扩展失败不在范围内。 它们根本不进 agent/request-error。harness 刻意不自动重试结果不明的操作,且在关闭失败步骤前会合成 TOOL_OUTCOME_UNKNOWN 结果,从轮次层补重试会有副作用重复的风险。
  • 直接 ctx.llm.stream() 的消费方不在范围内。 会话标题生成、压缩摘要器没有 agent 轮次,没有可挂的边界。
  • 计数器随日志折叠,但进程重启会重开一个 step。 一个 turn 不会跨重启继续,所以这不是问题。
  • 不感知子代理。 agent 作用域 dispatch 覆盖所有 agent,子代理的失败也会被接手——与官方 dsh-llm-retry 一致。
  • QUOTA / INVALID_REQUEST / UNKNOWN 默认不重试。 它们既可能瞬时也可能终态,需要你按部署显式开启。

故障排查

装完没反应。 本插件不是 required bundle,组合失败不会写启动日志。先在 Plugins 页面确认 auto-retry 出现,再看 ctx.logger 有没有 auto-retry: 开头的行。

失败了但没有重试。 依次检查:enabled 是否为 true;extraRetries 是否已到顶(到顶会写一条 warn);失败 code 是否在白名单里(UNKNOWN / QUOTA / INVALID_REQUEST 默认不在);是否是 AUTH / CONTEXT_WINDOW_EXCEEDED / ABORTED(硬拒绝,永远不重试)。

官方明明会重试,插件却没动静。 这是对的。官方决定重试时不会调用 next(),我们收不到那次失败——这正是「不冲突」的实现方式。

面板没出现。 面板只在「本轮有重试或有终态错误」时渲染。如果那一轮既没重试也成功了,尾部就是空的。另外确认客户端半部已注册:package.json 的 dsh.client.platform 为 web,且 profile 里装了 @deepseek-ai/dsh-client-ui-conversation。

装完 dsh 启动就崩,import 阶段报错。 有一个真实踩过的坑:npm 的 node_modules 内容和 npm 缓存之间存在引用关系,删掉缓存会把装好的包打成坏的重解析点——目录还在,stat 报 size=0,readdir 直接 ENOENT。所以装好之后不要删 npm 缓存;要清理就删掉 node_modules 重新装。

开发

npm install --ignore-scripts
npm test

代码结构:

lib/policy.js    纯决策逻辑,零框架依赖 —— 什么时候该重试、等多久
lib/index.js     宿主半部 —— 读 waterfall、找官方进度、持久事件、等待
client.js        客户端半部 —— 事件折叠、轮次尾部折叠面板、样式、字典
cordis.patch.yml 组合声明

把决策逻辑剥到无框架依赖的 policy.js 里,是为了让「什么时候该发」这条主链路能被裸 node 直接验证,不需要启动 harness。这是整个包最容易出错、也最难在集成环境里观察的一层。

client.js 是浏览器格式的产物:它通过 window.__ModuleLoader__.load 注册一个惰性工厂,而不是 export。test/client.test.mjs stub 掉那个全局来驱动它。

License

MIT © 2026 Rina

常见问题

6 问
答案由本站依据条目字段整理,不引用上游原文
dsh-plugin-auto-retry 怎么安装?

在 DSH 里执行 `dsh plugin --profile web add github:aqiu817/dsh-plugin-auto-retry` 即可。

dsh-plugin-auto-retry 的验证等级是什么意思?

L5 · 运行验证通过。在 dsh 沙盒中完成安装与运行冒烟——装得上、跑得起来

dsh-plugin-auto-retry 安全吗?

低风险。

dsh-plugin-auto-retry 装之前要先确认什么?

先看它会读写哪些文件、执行哪些命令、连接哪些网络地址;验证等级只说明装得上、跑得起来,不等于安全。涉及密钥或系统权限的,建议先在隔离环境试装。

dsh-plugin-auto-retry 用的是什么许可证?

上游标注为 MIT。商用或二次分发前请到仓库确认许可证原文。

这条记录的数据可靠吗?

本站按仓库名去重后合并整理,字段以作者仓库与 npm 元数据为准;安装命令、仓库地址与 README 保持原文,带 L4 / L5 标记的安装命令已在隔离沙盒里真实跑通。

通知与集成
按星标排序,可作替代方案

treg

treg

Treg 是面向 Agent 工具的 OpenRouter,聚合 60 个提供商的 2896 个端点,按调用计费,统一令牌访问,无需注册各供

L3 · 安装规范通过 4,976 星

dsh-purge

dsh-purge

dsh-purge Community · DeepSeek Harness 社区维护版:宿主兼容、状态诊断、提示词管理、更新与本机备份回滚

未标注验证等级 3,412 星

dsh-im

dsh-im

将IM机器人接入DeepSeek Harness,支持飞书、微信、钉钉等九种渠道,通过扫码或凭据配置,统一管理多机器人连接。

L5 · 运行验证通过 1,837 星

dsh-dafeiyu

dsh-dafeiyu

DSH 大肥鱼是一款由 DeepSeek Harness 真实工作状态驱动的桌面伴侣插件,以始终置顶的透明窗口实时展示 Agent 的思考、

L5 · 运行验证通过 425 星

dsh-trading

dsh-trading

L3 · 安装规范通过 235 星

aqiu817 的其它插件

2 个

插件的元信息来自作者仓库;分类、验证等级与安全结论的中文说明为中文整理,最近核验 —。