dsh-client-auto-continue 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,7 +37,7 @@ For [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh we
37
37
 
38
38
  **Smart recovery** (all configurable):
39
39
 
40
- - **Error classification** — transient failures (network / timeout / 5xx / 429…) are auto-resumed; permanent ones are **skipped** and notified, because retrying them never helps. A failure counts as permanent when its HTTP status is 401/403 or its code/message matches auth, credential/API-key, balance/quota, unknown-model, or context-length/overflow keywords. Turn classification off to resume everything
40
+ - **Error classification** — transient failures (network / timeout / 5xx / 429…) are auto-resumed; permanent ones are **skipped** and notified, because retrying them never helps. A failure counts as permanent when its HTTP status is 401/403 or its code/message matches auth, credential/API-key, balance/quota, unknown-model, or context-length/overflow keywords. Provider-specific exceptions can be opted into with literal custom retryable patterns; turn classification off to resume everything
41
41
  - **Adaptive backoff** — consecutive failures wait longer each time (cooldown × factor: 20s → 40s → 80s…), capped at the max backoff, instead of hammering a broken upstream
42
42
  - **Templated continue text** — `continueText` supports `{code}` `{message}` `{status}` `{tool}` `{turn}` `{errorCount}` `{sessionTitle}` `{elapsed}` placeholders, so the resume message can carry the failure context ("继续 (git push failed: UPSTREAM)"); a **separate template** fires on `max-tokens` (e.g. "继续输出, 不要重复已生成的内容")
43
43
  - **Idempotency guard** — before resuming, the plugin inspects the last tool call: if its result is unconfirmed (the turn died mid-tool, e.g. a `git push` that may have gone through), the resume message tells the model to check state first and not to rerun; if the tool is confirmed done, it says so and asks not to repeat it; a failed tool gets no guard (retrying it is the point). Both guard texts are configurable (`{tool}` / `{result}` placeholders)
@@ -72,7 +72,7 @@ The browser half is a thin shell: the settings card, plus a status bridge that s
72
72
 
73
73
  DSH plugins install into a **profile** (`dsh web` → `web` profile). Install, restart `dsh web`, done.
74
74
 
75
- > **Requires DSH 0.1.0-rc.7** newer versions register the settings card through the keyed `settings.plugin.item` slot; on rc.6 and earlier the plugin fails to load (`list slot ... requires options.id`). Upgrade dsh first.
75
+ > **Use the latest DSH (recommended: 0.1.2-alpha.1 or newer).** Run `dsh --version` before installing. Plugin v0.8.2 supports the new 0.1.2 client-store module layout and retains a fallback for DSH 0.1.0-rc.7 through 0.1.1; rc.6 and earlier remain unsupported (`list slot ... requires options.id`). Preview releases may appear on the [official DSH releases page](https://github.com/deepseek-ai/deepseek-harness/releases) before the public npm tag catches up.
76
76
 
77
77
  ### From npm (recommended)
78
78
 
@@ -164,6 +164,7 @@ auto-continue:
164
164
  freshMs: 900000
165
165
  verbose: true
166
166
  classify: true
167
+ retryableErrorPatterns: ''
167
168
  backoffFactor: 2
168
169
  backoffMaxMs: 300000
169
170
  notify: false
@@ -210,10 +211,21 @@ auto-continue:
210
211
  | Scan window (ms) | `900000` | Scan only considers interruptions inside this window |
211
212
  | Verbose logs | `on` | `[auto-continue]` console logs |
212
213
  | Classify errors | `on` | Auto-resume transient failures only; auth / balance / model errors are skipped and notified |
214
+ | Custom retryable errors | empty | One case-insensitive literal per line; matching the error code, HTTP status, or message explicitly overrides the built-in classifier |
213
215
  | Backoff factor | `2` | Cooldown multiplier per consecutive failure (2 = 20s → 40s → 80s…) |
214
216
  | Max backoff (ms) | `300000` | Cap on the adaptive backoff interval |
215
217
  | Browser notifications | `off` | Notify when auto-continue fires, gives up, or hits a permanent error |
216
218
 
219
+ For a provider-specific error that is safe to resume (confirm first that manually sending "continue" recovers), add a narrow, stable fragment rather than disabling classification globally:
220
+
221
+ ```yaml
222
+ auto-continue:
223
+ retryableErrorPatterns: |-
224
+ Upstream rejected the request as invalid
225
+ ```
226
+
227
+ Patterns are literal substrings, not regular expressions. Blank lines are ignored; any matching line wins before the built-in permanent-error rules. Cooldown and consecutive-attempt limits still apply.
228
+
217
229
  `continueText` (and `continueTextMaxTokens`) accept the placeholders `{code}`, `{message}`, `{status}`, `{tool}` (last tool call before the failure), `{turn}`, `{errorCount}` (consecutive failures including this one), `{sessionTitle}` (from the session list) and `{elapsed}` (time since the failure, e.g. `1m5s`) — e.g. `继续 ({tool}: {code})` becomes `继续 (git push: UPSTREAM)`. The guard texts accept `{tool}` and `{result}` (a truncated excerpt of the last tool output).
218
230
 
219
231
  ---
@@ -240,7 +252,7 @@ npm run test # node tests/simulate-host.mjs — 15 host-side behavioral s
240
252
 
241
253
  While `npm run watch` runs, the profile's client-hmr row polls `lib/client.js` every 500 ms and hot-reloads the plugin in the browser — no server restart needed for code changes.
242
254
 
243
- CI runs [dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check) on every push to `main` and every pull request, and gates releases — the tag workflow refuses to publish while the check fails.
255
+ CI installs from the lockfile, typechecks, rebuilds and verifies committed artifacts, runs the host and dual-layout client simulations, then runs [dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check). The same health check gates releases.
244
256
 
245
257
  ---
246
258
 
package/README.zh.md CHANGED
@@ -37,7 +37,7 @@
37
37
 
38
38
  **智能恢复**(全部可配置):
39
39
 
40
- - **错误分类** — 临时性错误(网络 / 超时 / 5xx / 429 等)自动续跑; 永久性错误跳过并通知, 因为重试也没用。判定为永久性的条件: HTTP 状态码 401/403, 或 code/message 命中认证、凭据/API Key、余额/配额、模型不存在、上下文长度/超限等关键词。关闭分类后则全部自动继续
40
+ - **错误分类** — 临时性错误(网络 / 超时 / 5xx / 429 等)自动续跑; 永久性错误跳过并通知, 因为重试也没用。判定为永久性的条件: HTTP 状态码 401/403, 或 code/message 命中认证、凭据/API Key、余额/配额、模型不存在、上下文长度/超限等关键词。provider 专属例外可由用户显式填写普通文本匹配; 关闭分类后则全部自动继续
41
41
  - **自适应退避** — 连续失败时等待时间递增(冷却 × 系数: 20s → 40s → 80s…), 有上限, 不再对故障上游狂轰滥炸
42
42
  - **模板化继续文本** — `continueText` 支持 `{code}` `{message}` `{status}` `{tool}` `{turn}` `{errorCount}` `{sessionTitle}` `{elapsed}` 占位符, 续跑消息可携带失败上下文(如「继续 (git push 失败: UPSTREAM)」); 达到 `max-tokens` 时使用**另一套模板**(如「继续输出, 不要重复已生成的内容」)
43
43
  - **幂等护栏** — 续跑前检查上一步工具调用: 结果未确认(回合在工具执行中途夭折, 如 `git push` 可能已经推上去了)时, 续跑消息会提示模型先确认状态、不要重复执行; 工具已确认成功时说明已完成、请勿重复; 工具失败则不加护栏(重试本来就是目的)。两段护栏文本都可配置(支持 `{tool}` / `{result}` 占位符)
@@ -72,7 +72,7 @@
72
72
 
73
73
  DSH 插件安装进 **profile**(`dsh web` 对应 `web` profile)。安装后重启 `dsh web` 即可。
74
74
 
75
- > **要求 DSH 0.1.0-rc.7** 新版本通过 keyed `settings.plugin.item` 槽位注册设置卡片; rc.6 及更早版本上插件会加载失败(`list slot ... requires options.id`), 请先升级 dsh。
75
+ > **请使用最新 DSH(推荐 0.1.2-alpha.1 或更高版本)。** 安装前先运行 `dsh --version`。插件 v0.8.2 已支持 0.1.2 的新 client-store 模块布局, 同时为 DSH 0.1.0-rc.7 至 0.1.1 保留兼容回退; rc.6 及更早版本仍不支持(`list slot ... requires options.id`)。预览版可能先发布在 [DSH 官方 Releases](https://github.com/deepseek-ai/deepseek-harness/releases), 公开 npm 标签会稍后跟进。
76
76
 
77
77
  ### 从 npm 安装(推荐)
78
78
 
@@ -164,6 +164,7 @@ auto-continue:
164
164
  freshMs: 900000
165
165
  verbose: true
166
166
  classify: true
167
+ retryableErrorPatterns: ''
167
168
  backoffFactor: 2
168
169
  backoffMaxMs: 300000
169
170
  notify: false
@@ -210,10 +211,21 @@ auto-continue:
210
211
  | 扫描时间窗 (ms) | `900000` | 扫描只处理该时间窗内的中断 |
211
212
  | 详细日志 | 开 | 控制台输出 `[auto-continue]` 日志 |
212
213
  | 错误分类 | 开 | 仅自动恢复临时性错误; 认证 / 余额 / 模型等永久性错误跳过并通知 |
214
+ | 自定义可恢复错误 | 空 | 每行一个大小写不敏感的普通文本片段; 命中错误码、HTTP 状态或消息时显式覆盖内置分类 |
213
215
  | 退避系数 | `2` | 连续失败时冷却间隔的倍率(2 = 20s → 40s → 80s…) |
214
216
  | 最大退避间隔 (ms) | `300000` | 自适应退避的上限 |
215
217
  | 浏览器通知 | 关 | 自动继续成功 / 放弃 / 遇到永久性错误时弹通知 |
216
218
 
219
+ 遇到确认可以安全续跑的 provider 专属错误时(先确认手动发送「继续」确实能恢复), 应添加足够具体、稳定的片段, 而不是全局关闭错误分类:
220
+
221
+ ```yaml
222
+ auto-continue:
223
+ retryableErrorPatterns: |-
224
+ Upstream rejected the request as invalid
225
+ ```
226
+
227
+ 匹配项是普通子串, 不是正则表达式。空行会被忽略; 任一行命中后会优先于内置永久错误规则。冷却与最大连续次数仍然生效。
228
+
217
229
  `continueText`(以及 `continueTextMaxTokens`)支持占位符 `{code}`、`{message}`、`{status}`、`{tool}`(失败前最后一次工具调用)、`{turn}`、`{errorCount}`(连续失败次数, 含本次)、`{sessionTitle}`(来自会话列表)和 `{elapsed}`(距失败经过的时间, 如 `1m5s`)——例如 `继续 ({tool}: {code})` 会变成 `继续 (git push: UPSTREAM)`。护栏文本支持 `{tool}` 与 `{result}`(上一步工具输出的截断摘要)。
218
230
 
219
231
  ---
@@ -240,7 +252,7 @@ npm run test # node tests/simulate-host.mjs — 15 个 host 侧行为场
240
252
 
241
253
  `npm run watch` 运行时, profile 的 client-hmr 行每 500ms 轮询 `lib/client.js` 并在浏览器中热重载插件——改代码无需重启服务。
242
254
 
243
- CI 在每次推送到 `main` 及每个 PR 时跑 [dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check) 体检, 并作为发布的前置条件——体检失败时 tag 流程拒绝发布。
255
+ CI 会按锁文件安装依赖, 执行类型检查、重新构建并核对已提交产物, host 与双模块布局 client 模拟测试, 最后运行 [dsh-plugin-check](https://github.com/omdsh-dev/dsh-plugin-check); 发布也受同一体检门禁约束。
244
256
 
245
257
  ---
246
258
 
package/lib/client.js CHANGED
@@ -67,6 +67,8 @@ var zh = {
67
67
  "field.verboseHint": "在浏览器控制台输出 [auto-continue] 日志。",
68
68
  "field.classify": "错误分类",
69
69
  "field.classifyHint": "仅自动恢复临时性错误(网络/超时/5xx 等); 认证/余额/模型不存在等永久性错误跳过并通知。",
70
+ "field.retryableErrorPatterns": "自定义可恢复错误",
71
+ "field.retryableErrorPatternsHint": "每行一个大小写不敏感的普通文本片段; 命中错误码、HTTP 状态或消息时覆盖内置分类。请只填 provider 稳定且足够具体的文案, 过宽会重复请求。",
70
72
  "field.backoffFactor": "退避系数",
71
73
  "field.backoffFactorHint": "连续失败时冷却间隔的倍率(如 2 表示 20s→40s→80s 递增)。",
72
74
  "field.backoffMaxMs": "最大退避间隔 (ms)",
@@ -148,6 +150,8 @@ var en = {
148
150
  "field.verboseHint": "Log [auto-continue] lines to the browser console.",
149
151
  "field.classify": "Classify errors",
150
152
  "field.classifyHint": "Auto-resume transient failures only (network/timeout/5xx…); auth, balance and model errors are skipped and notified.",
153
+ "field.retryableErrorPatterns": "Custom retryable errors",
154
+ "field.retryableErrorPatternsHint": "One case-insensitive literal per line. A match in the error code, HTTP status, or message overrides built-in classification. Use only stable, provider-specific text; broad matches can repeat requests.",
151
155
  "field.backoffFactor": "Backoff factor",
152
156
  "field.backoffFactorHint": "Cooldown multiplier per consecutive failure (2 = 20s→40s→80s…).",
153
157
  "field.backoffMaxMs": "Max backoff (ms)",
@@ -201,7 +205,6 @@ var en = {
201
205
 
202
206
  // src/client/settings-card.tsx
203
207
  var import_react = require("react");
204
- var import_client = require("@deepseek-ai/dsh-client-runtime/client");
205
208
 
206
209
  // src/shared/core.ts
207
210
  var DEFAULT_CONFIG = {
@@ -218,6 +221,7 @@ var DEFAULT_CONFIG = {
218
221
  freshMs: 15 * 60 * 1e3,
219
222
  verbose: true,
220
223
  classify: true,
224
+ retryableErrorPatterns: "",
221
225
  backoffFactor: 2,
222
226
  backoffMaxMs: 3e5,
223
227
  notify: false,
@@ -233,6 +237,18 @@ var DEFAULT_CONFIG = {
233
237
  var RECOVERY_WINDOW_MS = 10 * 60 * 1e3;
234
238
  var ECHO_WINDOW_MS = 10 * 60 * 1e3;
235
239
 
240
+ // src/client/dsh-store-compat.ts
241
+ function resolveSnapshotStore() {
242
+ const current = ["@deepseek-ai/dsh-client", "-store"].join("");
243
+ const legacy = ["@deepseek-ai/dsh-client-runtime", "/client"].join("");
244
+ try {
245
+ return require(current);
246
+ } catch {
247
+ return require(legacy);
248
+ }
249
+ }
250
+ var { createSnapshotStore } = resolveSnapshotStore();
251
+
236
252
  // src/client/bridge.ts
237
253
  var EMPTY_STATS = {
238
254
  date: "",
@@ -659,6 +675,7 @@ var css = `
659
675
  .dshAcInput:focus-visible { border-color: var(--dsw-alias-brand-primary); outline: none; }
660
676
  .dshAcInput:disabled { color: var(--dsw-alias-label-tertiary); cursor: default; }
661
677
  .dshAcInputInvalid { border-color: var(--dsw-alias-label-error); }
678
+ .dshAcTextArea { box-sizing: border-box; height: auto; min-height: 84px; padding: 8px 12px; resize: vertical; }
662
679
  .dshAcSelect {
663
680
  border: 1px solid var(--dsw-alias-border-l2);
664
681
  background: var(--dsw-alias-bg-layer-3);
@@ -734,6 +751,7 @@ var AutoContinueSettingsCardController = class {
734
751
  numberField("freshMs", 0),
735
752
  booleanField("verbose"),
736
753
  booleanField("classify"),
754
+ textField("retryableErrorPatterns"),
737
755
  numberField("backoffFactor", 1),
738
756
  numberField("backoffMaxMs", 0),
739
757
  booleanField("notify"),
@@ -745,7 +763,7 @@ var AutoContinueSettingsCardController = class {
745
763
  numberField("loopToolRepeat", 2),
746
764
  textField("loopText")
747
765
  ]);
748
- this.store = this.form.bind(() => this.projection(), import_client.createSnapshotStore);
766
+ this.store = this.form.bind(() => this.projection(), createSnapshotStore);
749
767
  }
750
768
  projection() {
751
769
  return {
@@ -764,6 +782,7 @@ var AutoContinueSettingsCardController = class {
764
782
  freshMs: this.form.field("freshMs"),
765
783
  verbose: this.form.field("verbose"),
766
784
  classify: this.form.field("classify"),
785
+ retryableErrorPatterns: this.form.field("retryableErrorPatterns"),
767
786
  backoffFactor: this.form.field("backoffFactor"),
768
787
  backoffMaxMs: this.form.field("backoffMaxMs"),
769
788
  notify: this.form.field("notify"),
@@ -831,6 +850,7 @@ function SettingsCard(props) {
831
850
  ] });
832
851
  }
833
852
  function ValueField(props) {
853
+ const className = props.invalid ? "dshAcInput dshAcInputInvalid" : "dshAcInput";
834
854
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshAcField", children: [
835
855
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dshAcHead", children: [
836
856
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "dshAcLabel", htmlFor: props.id, children: props.label }),
@@ -839,11 +859,23 @@ function ValueField(props) {
839
859
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "dshAcReset", disabled: props.disabled, onClick: props.onReset, children: props.t("chrome.reset") })
840
860
  ] }) : null
841
861
  ] }),
842
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
862
+ props.multiline === true ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
863
+ "textarea",
864
+ {
865
+ id: props.id,
866
+ className: `${className} dshAcTextArea`,
867
+ "aria-invalid": props.invalid || void 0,
868
+ value: props.text,
869
+ placeholder: props.placeholder ?? "",
870
+ disabled: props.disabled,
871
+ rows: 4,
872
+ onChange: (event) => props.onEdit(event.target.value)
873
+ }
874
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
843
875
  "input",
844
876
  {
845
877
  id: props.id,
846
- className: props.invalid ? "dshAcInput dshAcInputInvalid" : "dshAcInput",
878
+ className,
847
879
  type: "text",
848
880
  inputMode: props.numeric === true ? "numeric" : void 0,
849
881
  "aria-invalid": props.invalid || void 0,
@@ -1189,6 +1221,20 @@ function AutoContinueSettingsCard(props) {
1189
1221
  onReset: () => props.resetField("classify")
1190
1222
  }
1191
1223
  ),
1224
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1225
+ ValueField,
1226
+ {
1227
+ id: "auto-continue-retryable-error-patterns",
1228
+ label: t("field.retryableErrorPatterns"),
1229
+ hint: t("field.retryableErrorPatternsHint"),
1230
+ multiline: true,
1231
+ ...shared,
1232
+ ...state2.retryableErrorPatterns,
1233
+ onEdit: (text) => props.edit("retryableErrorPatterns", text),
1234
+ placeholder: "Upstream rejected the request as invalid",
1235
+ onReset: () => props.resetField("retryableErrorPatterns")
1236
+ }
1237
+ ),
1192
1238
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
1193
1239
  ValueField,
1194
1240
  {