dsh-client-auto-continue 0.8.2 → 0.10.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/lib/index.js CHANGED
@@ -98,12 +98,26 @@ var RetryPolicySchema = z.union([normalPolicySchema, alwaysPolicySchema]);
98
98
  var { version } = createRequire(import.meta.url)("../package.json");
99
99
 
100
100
  // src/shared/core.ts
101
+ var LOCALIZED_TEXT_DEFAULTS = {
102
+ zh: {
103
+ continueText: "继续",
104
+ continueTextMaxTokens: "继续",
105
+ guardPendingText: "(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)",
106
+ guardDoneText: "(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)",
107
+ loopText: "(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)"
108
+ },
109
+ en: {
110
+ continueText: "Continue",
111
+ continueTextMaxTokens: "Continue",
112
+ guardPendingText: '(The previous tool "{tool}" may not have completed. Check its state before continuing and do not run it again.)',
113
+ guardDoneText: '(The previous tool "{tool}" completed successfully. Result: {result}; do not run it again. Continue from there.)',
114
+ loopText: "(You may be stuck in a loop. Stop repeating the last action and continue with a different approach.)"
115
+ }
116
+ };
101
117
  var DEFAULT_CONFIG = {
102
- continueText: "继续",
103
- continueTextMaxTokens: "继续",
118
+ locale: "zh",
119
+ ...LOCALIZED_TEXT_DEFAULTS.zh,
104
120
  guardTools: true,
105
- guardPendingText: "(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)",
106
- guardDoneText: "(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)",
107
121
  graceMs: 3e3,
108
122
  cooldownMs: 2e4,
109
123
  maxConsecutive: 3,
@@ -112,6 +126,7 @@ var DEFAULT_CONFIG = {
112
126
  freshMs: 15 * 60 * 1e3,
113
127
  verbose: true,
114
128
  classify: true,
129
+ retryableErrorPatterns: "",
115
130
  backoffFactor: 2,
116
131
  backoffMaxMs: 3e5,
117
132
  notify: false,
@@ -121,8 +136,7 @@ var DEFAULT_CONFIG = {
121
136
  loopWindowMs: 3e4,
122
137
  loopShortCount: 12,
123
138
  loopRepeatText: 4,
124
- loopToolRepeat: 5,
125
- loopText: "(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)"
139
+ loopToolRepeat: 5
126
140
  };
127
141
  function numberOr(value, fallback) {
128
142
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
@@ -132,11 +146,14 @@ function booleanOr(value, fallback) {
132
146
  }
133
147
  function resolveConfig(section) {
134
148
  const value = section ?? {};
135
- const text = typeof value.continueText === "string" && value.continueText.trim() !== "" ? value.continueText : DEFAULT_CONFIG.continueText;
136
- const maxTokensText = typeof value.continueTextMaxTokens === "string" && value.continueTextMaxTokens.trim() !== "" ? value.continueTextMaxTokens : DEFAULT_CONFIG.continueTextMaxTokens;
137
- const guardPendingText = typeof value.guardPendingText === "string" && value.guardPendingText.trim() !== "" ? value.guardPendingText : DEFAULT_CONFIG.guardPendingText;
138
- const guardDoneText = typeof value.guardDoneText === "string" && value.guardDoneText.trim() !== "" ? value.guardDoneText : DEFAULT_CONFIG.guardDoneText;
149
+ const locale = value.locale === "en" ? "en" : "zh";
150
+ const localized = LOCALIZED_TEXT_DEFAULTS[locale];
151
+ const text = typeof value.continueText === "string" && value.continueText.trim() !== "" ? value.continueText : localized.continueText;
152
+ const maxTokensText = typeof value.continueTextMaxTokens === "string" && value.continueTextMaxTokens.trim() !== "" ? value.continueTextMaxTokens : localized.continueTextMaxTokens;
153
+ const guardPendingText = typeof value.guardPendingText === "string" && value.guardPendingText.trim() !== "" ? value.guardPendingText : localized.guardPendingText;
154
+ const guardDoneText = typeof value.guardDoneText === "string" && value.guardDoneText.trim() !== "" ? value.guardDoneText : localized.guardDoneText;
139
155
  return {
156
+ locale,
140
157
  continueText: text,
141
158
  continueTextMaxTokens: maxTokensText,
142
159
  guardTools: booleanOr(value.guardTools, DEFAULT_CONFIG.guardTools),
@@ -150,6 +167,7 @@ function resolveConfig(section) {
150
167
  freshMs: numberOr(value.freshMs, DEFAULT_CONFIG.freshMs),
151
168
  verbose: booleanOr(value.verbose, DEFAULT_CONFIG.verbose),
152
169
  classify: booleanOr(value.classify, DEFAULT_CONFIG.classify),
170
+ retryableErrorPatterns: typeof value.retryableErrorPatterns === "string" ? value.retryableErrorPatterns.trim() : DEFAULT_CONFIG.retryableErrorPatterns,
153
171
  backoffFactor: Math.max(1, numberOr(value.backoffFactor, DEFAULT_CONFIG.backoffFactor)),
154
172
  backoffMaxMs: numberOr(value.backoffMaxMs, DEFAULT_CONFIG.backoffMaxMs),
155
173
  notify: booleanOr(value.notify, DEFAULT_CONFIG.notify),
@@ -160,14 +178,16 @@ function resolveConfig(section) {
160
178
  loopShortCount: Math.max(2, numberOr(value.loopShortCount, DEFAULT_CONFIG.loopShortCount)),
161
179
  loopRepeatText: Math.max(2, numberOr(value.loopRepeatText, DEFAULT_CONFIG.loopRepeatText)),
162
180
  loopToolRepeat: Math.max(2, numberOr(value.loopToolRepeat, DEFAULT_CONFIG.loopToolRepeat)),
163
- loopText: typeof value.loopText === "string" && value.loopText.trim() !== "" ? value.loopText : DEFAULT_CONFIG.loopText
181
+ loopText: typeof value.loopText === "string" && value.loopText.trim() !== "" ? value.loopText : localized.loopText
164
182
  };
165
183
  }
166
184
  function isNonHumanReason(kind) {
167
185
  return kind === "error" || kind === "interrupted" || kind === "max-tokens";
168
186
  }
169
- function isTransientFailure(failure) {
170
- const haystack = `${failure.code} ${failure.message}`.toLowerCase();
187
+ function isTransientFailure(failure, retryableErrorPatterns = "") {
188
+ const haystack = `${failure.code} ${failure.status ?? ""} ${failure.message}`.toLowerCase();
189
+ const explicitlyRetryable = retryableErrorPatterns.split(/\r?\n/).map((pattern) => pattern.trim().toLowerCase()).filter((pattern) => pattern !== "").some((pattern) => haystack.includes(pattern));
190
+ if (explicitlyRetryable) return true;
171
191
  const status = failure.status;
172
192
  if (status !== void 0 && (status === 401 || status === 403)) return false;
173
193
  const permanent = /auth|unauthor|forbidden|credential|api[_-]?key|permission/i.test(haystack) || /insufficient.*(balance|quota)|billing|payment|quota.*exceeded.*(?!retry)/i.test(haystack) || /model.*not[_-]?found|unknown[_-]?model|model[_-]?not[_-]?found|not.*support.*model/i.test(haystack) || /context.*(length|limit|overflow|exceed)|token.*limit|max.*context/i.test(haystack) || /invalid[_-]?request|bad[_-]?request/i.test(haystack);
@@ -260,6 +280,28 @@ function isOurEcho(state, event) {
260
280
  }
261
281
 
262
282
  // src/host/engine.ts
283
+ var NOTICE_COPY = {
284
+ zh: {
285
+ notContinuedTitle: "dsh-auto-continue: 未自动继续",
286
+ permanentErrorBody: (sessionId, summary) => `${sessionId}: 永久性错误 ${summary},需要人工处理`,
287
+ resumeAction: "立即续跑",
288
+ pauseAction: "暂停该会话 1 小时",
289
+ continuedTitle: "dsh-auto-continue: 已自动继续",
290
+ continuedBody: (sessionId, text, count) => `${sessionId}: 已发送「${text}」(第 ${count} 次连续)`,
291
+ stoppedTitle: "dsh-auto-continue: 已停止自动继续",
292
+ stoppedBody: (sessionId, count) => `${sessionId}: 连续失败 ${count} 次, 需要人工介入`
293
+ },
294
+ en: {
295
+ notContinuedTitle: "dsh-auto-continue: Not continued",
296
+ permanentErrorBody: (sessionId, summary) => `${sessionId}: Permanent error ${summary}; manual intervention required`,
297
+ resumeAction: "Resume now",
298
+ pauseAction: "Pause this session for 1 hour",
299
+ continuedTitle: "dsh-auto-continue: Continued automatically",
300
+ continuedBody: (sessionId, text, count) => `${sessionId}: Sent "${text}" (consecutive attempt ${count})`,
301
+ stoppedTitle: "dsh-auto-continue: Auto-continue stopped",
302
+ stoppedBody: (sessionId, count) => `${sessionId}: ${count} consecutive failures; manual intervention required`
303
+ }
304
+ };
263
305
  var AutoContinueRunner = class {
264
306
  /**
265
307
  * @param ctx - host plugin context (agents registry, session events, settings).
@@ -564,15 +606,16 @@ ${event.data.arguments}`;
564
606
  // ---------- host 帧 ----------
565
607
  onTurnFailure(sessionId, reason, failure) {
566
608
  const config = this.getConfig();
567
- if (config.classify && !isTransientFailure(failure)) {
609
+ if (config.classify && !isTransientFailure(failure, config.retryableErrorPatterns)) {
610
+ const copy = NOTICE_COPY[config.locale];
568
611
  const summary = `${failure.code}${failure.status !== void 0 ? ` (HTTP ${failure.status})` : ""}`;
569
612
  this.log(`跳过 ${sessionId}(${reason}): 永久性失败 ${summary} — ${failure.message}`);
570
613
  this.bumpStat({ skipped: 1, code: failure.code });
571
614
  if (config.notify) {
572
615
  this.notify(
573
- "dsh-auto-continue: 未自动继续",
574
- `${sessionId}: 永久性错误 ${summary},需要人工处理`,
575
- this.notifyOptions(sessionId)
616
+ copy.notContinuedTitle,
617
+ copy.permanentErrorBody(sessionId, summary),
618
+ this.notifyOptions(sessionId, config.locale)
576
619
  );
577
620
  }
578
621
  return;
@@ -580,11 +623,12 @@ ${event.data.arguments}`;
580
623
  this.schedule(sessionId, reason);
581
624
  }
582
625
  /** 通知操作按钮与回调(「立即续跑」/「暂停该会话 1 小时」)。 */
583
- notifyOptions(sessionId) {
626
+ notifyOptions(sessionId, locale) {
627
+ const copy = NOTICE_COPY[locale];
584
628
  return {
585
629
  actions: [
586
- { action: "resume", title: "立即续跑" },
587
- { action: "pause1h", title: "暂停该会话 1 小时" }
630
+ { action: "resume", title: copy.resumeAction },
631
+ { action: "pause1h", title: copy.pauseAction }
588
632
  ],
589
633
  onAction: (action) => this.onNotifyAction(sessionId, action)
590
634
  };
@@ -741,20 +785,22 @@ ${event.data.arguments}`;
741
785
  this.bumpStat({ sent: 1, ...state.lastFailure !== void 0 ? { code: state.lastFailure.code } : {} });
742
786
  this.log(`已自动发送「${text}」到 ${sessionId}(${reason}), 第 ${state.consecutive} 次连续`);
743
787
  if (config.notify) {
788
+ const copy = NOTICE_COPY[config.locale];
744
789
  this.notify(
745
- "dsh-auto-continue: 已自动继续",
746
- `${sessionId}: 已发送「${text}」(第 ${state.consecutive} 次连续)`,
747
- this.notifyOptions(sessionId)
790
+ copy.continuedTitle,
791
+ copy.continuedBody(sessionId, text, state.consecutive),
792
+ this.notifyOptions(sessionId, config.locale)
748
793
  );
749
794
  }
750
795
  if (state.consecutive >= config.maxConsecutive) {
751
796
  this.bumpStat({ gaveUp: 1 });
752
797
  this.log(`达到连续上限 ${config.maxConsecutive} 次, 停止自动继续 ${sessionId}`);
753
798
  if (config.notify) {
799
+ const copy = NOTICE_COPY[config.locale];
754
800
  this.notify(
755
- "dsh-auto-continue: 已停止自动继续",
756
- `${sessionId}: 连续失败 ${state.consecutive} 次, 需要人工介入`,
757
- this.notifyOptions(sessionId)
801
+ copy.stoppedTitle,
802
+ copy.stoppedBody(sessionId, state.consecutive),
803
+ this.notifyOptions(sessionId, config.locale)
758
804
  );
759
805
  }
760
806
  }
@@ -887,16 +933,18 @@ ${event.data.arguments}`;
887
933
  // src/index.ts
888
934
  var AUTO_CONTINUE_NS = "auto-continue";
889
935
  var AutoContinueSchema = z2.object({
936
+ /** Active browser/UI locale mirrored by the client. */
937
+ locale: z2.string().default("zh"),
890
938
  /** Text automatically sent after an interruption. */
891
- continueText: z2.string().default("继续"),
939
+ continueText: z2.string().default(""),
892
940
  /** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
893
- continueTextMaxTokens: z2.string().default("继续"),
941
+ continueTextMaxTokens: z2.string().default(""),
894
942
  /** Idempotency guard: inspect the last tool call before resuming and steer the model. */
895
943
  guardTools: z2.boolean().default(true),
896
944
  /** Guard text appended when the last tool call has no confirmed result (it may have partially executed). */
897
- guardPendingText: z2.string().default("(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)"),
945
+ guardPendingText: z2.string().default(""),
898
946
  /** Guard text appended when the last tool call completed successfully (don't rerun it). */
899
- guardDoneText: z2.string().default("(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)"),
947
+ guardDoneText: z2.string().default(""),
900
948
  /** Grace period after an interruption before auto-sending (ms). */
901
949
  graceMs: z2.natural().default(3e3),
902
950
  /** Minimum interval between two auto-continues per session (ms). */
@@ -913,6 +961,8 @@ var AutoContinueSchema = z2.object({
913
961
  verbose: z2.boolean().default(true),
914
962
  /** Classify failures: auto-continue transient errors only; permanent ones are skipped and notified. */
915
963
  classify: z2.boolean().default(true),
964
+ /** Provider-specific message/code/status fragments that explicitly count as retryable, one literal per line. */
965
+ retryableErrorPatterns: z2.string().default(""),
916
966
  /** Cooldown multiplier per consecutive failure (adaptive backoff). */
917
967
  backoffFactor: z2.natural().min(1).default(2),
918
968
  /** Cap on the effective backoff interval (ms). */
@@ -934,7 +984,7 @@ var AutoContinueSchema = z2.object({
934
984
  /** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
935
985
  loopToolRepeat: z2.natural().min(2).default(5),
936
986
  /** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
937
- loopText: z2.string().default("(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)")
987
+ loopText: z2.string().default("")
938
988
  });
939
989
  function apply(ctx) {
940
990
  ctx.inject(["settings"], (settingsCtx) => {
@@ -1,8 +1,3 @@
1
- /**
2
- * `auto-continue` namespace dictionaries: copy for the plugin settings card
3
- * registered into the `settings.plugin.item` seat of the plugin-configuration
4
- * section. Includes the card-chrome keys the card component reads.
5
- */
6
1
  /** 简体中文词典(键集的事实来源)。 */
7
2
  export declare const zh: {
8
3
  'card.title': string;
@@ -11,14 +6,18 @@ export declare const zh: {
11
6
  'field.pausedHint': string;
12
7
  'field.continueText': string;
13
8
  'field.continueTextHint': string;
9
+ 'default.continueText': "继续";
14
10
  'field.continueTextMaxTokens': string;
15
11
  'field.continueTextMaxTokensHint': string;
12
+ 'default.continueTextMaxTokens': "继续";
16
13
  'field.guardTools': string;
17
14
  'field.guardToolsHint': string;
18
15
  'field.guardPendingText': string;
19
16
  'field.guardPendingTextHint': string;
17
+ 'default.guardPendingText': "(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)";
20
18
  'field.guardDoneText': string;
21
19
  'field.guardDoneTextHint': string;
20
+ 'default.guardDoneText': "(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)";
22
21
  'field.graceMs': string;
23
22
  'field.graceMsHint': string;
24
23
  'field.cooldownMs': string;
@@ -35,6 +34,8 @@ export declare const zh: {
35
34
  'field.verboseHint': string;
36
35
  'field.classify': string;
37
36
  'field.classifyHint': string;
37
+ 'field.retryableErrorPatterns': string;
38
+ 'field.retryableErrorPatternsHint': string;
38
39
  'field.backoffFactor': string;
39
40
  'field.backoffFactorHint': string;
40
41
  'field.backoffMaxMs': string;
@@ -62,6 +63,7 @@ export declare const zh: {
62
63
  'field.loopToolRepeatHint': string;
63
64
  'field.loopText': string;
64
65
  'field.loopTextHint': string;
66
+ 'default.loopText': "(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)";
65
67
  'stats.byCode': string;
66
68
  'stats.empty': string;
67
69
  'stats.reset': string;
@@ -18,6 +18,7 @@ export interface AutoContinueSettingsCardState extends CardShell {
18
18
  freshMs: CardFieldState;
19
19
  verbose: CardFieldState;
20
20
  classify: CardFieldState;
21
+ retryableErrorPatterns: CardFieldState;
21
22
  backoffFactor: CardFieldState;
22
23
  backoffMaxMs: CardFieldState;
23
24
  notify: CardFieldState;
@@ -13,8 +13,10 @@ import type { Context } from '@deepseek-ai/cordis';
13
13
  import z from '@deepseek-ai/schemastery';
14
14
  /** Settings namespace of the auto-continue plugin (lowercase kebab-case). */
15
15
  export declare const AUTO_CONTINUE_NS = "auto-continue";
16
- /** Wire schema of the auto-continue section; defaults are the plugin's built-in values. */
16
+ /** Wire schema; blank localized text fields tell resolveConfig() to select the active locale's defaults. */
17
17
  export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
18
+ /** Active browser/UI locale mirrored by the client. */
19
+ locale: z<string, string>;
18
20
  /** Text automatically sent after an interruption. */
19
21
  continueText: z<string, string>;
20
22
  /** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
@@ -41,6 +43,8 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
41
43
  verbose: z<boolean, boolean>;
42
44
  /** Classify failures: auto-continue transient errors only; permanent ones are skipped and notified. */
43
45
  classify: z<boolean, boolean>;
46
+ /** Provider-specific message/code/status fragments that explicitly count as retryable, one literal per line. */
47
+ retryableErrorPatterns: z<string, string>;
44
48
  /** Cooldown multiplier per consecutive failure (adaptive backoff). */
45
49
  backoffFactor: z<number, number>;
46
50
  /** Cap on the effective backoff interval (ms). */
@@ -64,6 +68,8 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
64
68
  /** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
65
69
  loopText: z<string, string>;
66
70
  }>, Schemastery.ObjectT<{
71
+ /** Active browser/UI locale mirrored by the client. */
72
+ locale: z<string, string>;
67
73
  /** Text automatically sent after an interruption. */
68
74
  continueText: z<string, string>;
69
75
  /** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
@@ -90,6 +96,8 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
90
96
  verbose: z<boolean, boolean>;
91
97
  /** Classify failures: auto-continue transient errors only; permanent ones are skipped and notified. */
92
98
  classify: z<boolean, boolean>;
99
+ /** Provider-specific message/code/status fragments that explicitly count as retryable, one literal per line. */
100
+ retryableErrorPatterns: z<string, string>;
93
101
  /** Cooldown multiplier per consecutive failure (adaptive backoff). */
94
102
  backoffFactor: z<number, number>;
95
103
  /** Cap on the effective backoff interval (ms). */
@@ -5,8 +5,29 @@
5
5
  * 以及回显识别。引擎迁入 host 后(0.8.0), 浏览器半侧只 re-export 本模块。
6
6
  */
7
7
  import type { SessionEvent } from '@deepseek-ai/dsh-session/types';
8
+ /** Supported UI/config locales. Any unknown browser locale falls back to Chinese. */
9
+ export type AutoContinueLocale = 'en' | 'zh';
10
+ /** Locale-owned defaults for the user-editable text fields. */
11
+ export declare const LOCALIZED_TEXT_DEFAULTS: {
12
+ readonly zh: {
13
+ readonly continueText: "继续";
14
+ readonly continueTextMaxTokens: "继续";
15
+ readonly guardPendingText: "(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)";
16
+ readonly guardDoneText: "(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)";
17
+ readonly loopText: "(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)";
18
+ };
19
+ readonly en: {
20
+ readonly continueText: "Continue";
21
+ readonly continueTextMaxTokens: "Continue";
22
+ readonly guardPendingText: "(The previous tool \"{tool}\" may not have completed. Check its state before continuing and do not run it again.)";
23
+ readonly guardDoneText: "(The previous tool \"{tool}\" completed successfully. Result: {result}; do not run it again. Continue from there.)";
24
+ readonly loopText: "(You may be stuck in a loop. Stop repeating the last action and continue with a different approach.)";
25
+ };
26
+ };
8
27
  /** The `auto-continue` settings section (all fields optional on the wire; the host schema carries defaults). */
9
28
  export interface AutoContinueSettings {
29
+ /** Active browser/UI locale mirrored by the client. */
30
+ locale?: AutoContinueLocale;
10
31
  /** Text automatically sent after an interruption. */
11
32
  continueText?: string;
12
33
  /** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
@@ -33,6 +54,8 @@ export interface AutoContinueSettings {
33
54
  verbose?: boolean;
34
55
  /** Classify failures: auto-continue transient errors only; permanent ones (auth/balance/model) are skipped and notified. */
35
56
  classify?: boolean;
57
+ /** Provider-specific message/code/status fragments that explicitly count as retryable, one literal per line. */
58
+ retryableErrorPatterns?: string;
36
59
  /** Cooldown multiplier per consecutive failure (adaptive backoff). */
37
60
  backoffFactor?: number;
38
61
  /** Cap on the effective backoff interval (ms). */
@@ -58,7 +81,7 @@ export interface AutoContinueSettings {
58
81
  }
59
82
  /** Fully resolved configuration (built-in defaults + user overrides). */
60
83
  export type AutoContinueConfig = Required<AutoContinueSettings>;
61
- /** Built-in defaults must match the host schema defaults in src/index.ts. */
84
+ /** Effective built-in defaults; localized text fields use Chinese until a browser locale is mirrored. */
62
85
  export declare const DEFAULT_CONFIG: AutoContinueConfig;
63
86
  /** Resolve a (possibly partial / not-yet-loaded) settings section to a full config. */
64
87
  export declare function resolveConfig(section: AutoContinueSettings | undefined): AutoContinueConfig;
@@ -81,10 +104,11 @@ export interface FailureFacts {
81
104
  }
82
105
  /**
83
106
  * 错误分类: 该失败是否值得自动继续。
107
+ * 用户填写的 provider 专属文本片段优先覆盖内置结果; 未命中时,
84
108
  * 永久性失败(认证/余额/模型不存在/上下文超限等)重试也不会成功, 应跳过并通知用户;
85
109
  * 其余(网络、超时、5xx、429 等)视为临时性失败, 允许自动恢复。
86
110
  */
87
- export declare function isTransientFailure(failure: FailureFacts): boolean;
111
+ export declare function isTransientFailure(failure: FailureFacts, retryableErrorPatterns?: string): boolean;
88
112
  /**
89
113
  * host/agent-error 消息分类: 仅明确属于网络/传输类的临时错误才自动继续。
90
114
  * 其余(序列化失败、配置/宿主内部错误等)视为永久性——重试无益, 且用户停止导致的
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-client-auto-continue",
3
- "description": "DSH Web UI plugin: automatically sends \"继续\" (continue) when a request is interrupted by network errors or other non-human causes",
4
- "version": "0.8.2",
3
+ "description": "DSH Web UI plugin: automatically sends a localized continue prompt when a request is interrupted by network errors or other non-human causes",
4
+ "version": "0.10.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
@@ -56,6 +56,18 @@ export {
56
56
  export function apply(ctx: ClientContext): void {
57
57
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'auto-continue: dictionaries');
58
58
 
59
+ const scope = ctx.settingsScope.bind<AutoContinueSettings>({ namespace: SETTINGS_NS });
60
+ const syncLocale = (): void => {
61
+ const active = ctx.locale.getLocale().active;
62
+ const snapshot = scope.getSnapshot();
63
+ if (snapshot.status !== 'ready' || !snapshot.writable || snapshot.mode !== 'host') return;
64
+ if (snapshot.value?.locale === active) return;
65
+ void scope.set('locale', active);
66
+ };
67
+ ctx.effect(() => scope.subscribe(syncLocale), 'auto-continue: locale settings sync');
68
+ ctx.on('locale/change', syncLocale);
69
+ syncLocale();
70
+
59
71
  // 状态桥: 订阅 host 的通知与运行时状态, 弹浏览器通知并驱动卡片面板。
60
72
  ctx.effect(() => startBridge(), 'auto-continue: host bridge');
61
73
 
@@ -64,7 +76,6 @@ export function apply(ctx: ClientContext): void {
64
76
  // (Settings → Plugins). Since DSH 0.1.0-rc.7 `settings.plugin.item` is a
65
77
  // keyed slot dispatched by the settings namespace it edits, so the entry
66
78
  // registers with `key` (the namespace), like the official cards.
67
- const scope = ctx.settingsScope.bind<AutoContinueSettings>({ namespace: SETTINGS_NS });
68
79
  const controller = new AutoContinueSettingsCardController(scope);
69
80
  ctx.slots.inject('settings.plugin.item', () =>
70
81
  ctx.slots.register(
@@ -3,6 +3,7 @@
3
3
  * registered into the `settings.plugin.item` seat of the plugin-configuration
4
4
  * section. Includes the card-chrome keys the card component reads.
5
5
  */
6
+ import { LOCALIZED_TEXT_DEFAULTS } from '../shared/core.ts';
6
7
 
7
8
  /** 简体中文词典(键集的事实来源)。 */
8
9
  export const zh = {
@@ -12,14 +13,18 @@ export const zh = {
12
13
  'field.pausedHint': '全局暂停: 实时与扫描都不会再自动发送, 已排队的待发送也会取消。',
13
14
  'field.continueText': '继续文本',
14
15
  'field.continueTextHint': '中断后自动发送的消息内容。',
16
+ 'default.continueText': LOCALIZED_TEXT_DEFAULTS.zh.continueText,
15
17
  'field.continueTextMaxTokens': '超限时的继续文本',
16
18
  'field.continueTextMaxTokensHint': '达到输出 token 上限时自动发送的文本, 支持与继续文本相同的占位符。',
19
+ 'default.continueTextMaxTokens': LOCALIZED_TEXT_DEFAULTS.zh.continueTextMaxTokens,
17
20
  'field.guardTools': '幂等护栏',
18
21
  'field.guardToolsHint': '续跑前检查上一步工具调用: 结果未确认时提示先确认状态, 已成功时提示不要重复执行, 避免重复 commit/调 API。',
19
22
  'field.guardPendingText': '结果未确认时的护栏文本',
20
23
  'field.guardPendingTextHint': '上一步工具可能已部分执行时附加到继续文本之后, 支持 {tool} 占位符。',
24
+ 'default.guardPendingText': LOCALIZED_TEXT_DEFAULTS.zh.guardPendingText,
21
25
  'field.guardDoneText': '工具已成功时的护栏文本',
22
26
  'field.guardDoneTextHint': '上一步工具已确认成功时附加到继续文本之后, 支持 {tool} 与 {result}(结果摘要)占位符。',
27
+ 'default.guardDoneText': LOCALIZED_TEXT_DEFAULTS.zh.guardDoneText,
23
28
  'field.graceMs': '宽限期 (ms)',
24
29
  'field.graceMsHint': '检测到中断后等待的时长; 期间宿主自行恢复则取消。',
25
30
  'field.cooldownMs': '冷却时间 (ms)',
@@ -36,6 +41,8 @@ export const zh = {
36
41
  'field.verboseHint': '在浏览器控制台输出 [auto-continue] 日志。',
37
42
  'field.classify': '错误分类',
38
43
  'field.classifyHint': '仅自动恢复临时性错误(网络/超时/5xx 等); 认证/余额/模型不存在等永久性错误跳过并通知。',
44
+ 'field.retryableErrorPatterns': '自定义可恢复错误',
45
+ 'field.retryableErrorPatternsHint': '每行一个大小写不敏感的普通文本片段; 命中错误码、HTTP 状态或消息时覆盖内置分类。请只填 provider 稳定且足够具体的文案, 过宽会重复请求。',
39
46
  'field.backoffFactor': '退避系数',
40
47
  'field.backoffFactorHint': '连续失败时冷却间隔的倍率(如 2 表示 20s→40s→80s 递增)。',
41
48
  'field.backoffMaxMs': '最大退避间隔 (ms)',
@@ -63,6 +70,7 @@ export const zh = {
63
70
  'field.loopToolRepeatHint': '同工具+同参数+同结果的连续调用多少次时判定死循环; 参数或结果有变化视为有进展。',
64
71
  'field.loopText': '循环提示文本',
65
72
  'field.loopTextHint': '打断后重启回合时发送的文本, 支持 {tool} 占位符。',
73
+ 'default.loopText': LOCALIZED_TEXT_DEFAULTS.zh.loopText,
66
74
  'stats.byCode': '按错误码统计',
67
75
  'stats.empty': '今天还没有自动继续记录。',
68
76
  'stats.reset': '清零',
@@ -93,19 +101,23 @@ export type SettingsCardKey = keyof typeof zh;
93
101
  /** English dictionary, checked complete against the zh key set. */
94
102
  export const en: Record<SettingsCardKey, string> = {
95
103
  'card.title': 'Auto continue',
96
- 'card.description': 'When a request is interrupted by a non-human cause, automatically send 「继续」 to resume.',
104
+ 'card.description': 'When a request is interrupted by a non-human cause, automatically send "Continue" to resume.',
97
105
  'field.paused': 'Pause auto-continue',
98
106
  'field.pausedHint': 'Globally pause: no live or scan auto-send fires, and queued pending sends are cancelled.',
99
107
  'field.continueText': 'Continue text',
100
108
  'field.continueTextHint': 'Message automatically sent after an interruption.',
109
+ 'default.continueText': LOCALIZED_TEXT_DEFAULTS.en.continueText,
101
110
  'field.continueTextMaxTokens': 'Continue text (max tokens)',
102
111
  'field.continueTextMaxTokensHint': 'Text sent when the output token ceiling is reached; same placeholders as the continue text.',
112
+ 'default.continueTextMaxTokens': LOCALIZED_TEXT_DEFAULTS.en.continueTextMaxTokens,
103
113
  'field.guardTools': 'Idempotency guard',
104
114
  'field.guardToolsHint': 'Before resuming, inspect the last tool call: if its result is unconfirmed, tell the model to check state first; if it succeeded, tell it not to rerun — avoids duplicate commits / API calls.',
105
115
  'field.guardPendingText': 'Guard text (unconfirmed result)',
106
116
  'field.guardPendingTextHint': 'Appended when the last tool may have partially executed; supports the {tool} placeholder.',
117
+ 'default.guardPendingText': LOCALIZED_TEXT_DEFAULTS.en.guardPendingText,
107
118
  'field.guardDoneText': 'Guard text (tool succeeded)',
108
119
  'field.guardDoneTextHint': 'Appended when the last tool is confirmed done; supports {tool} and {result} (result excerpt).',
120
+ 'default.guardDoneText': LOCALIZED_TEXT_DEFAULTS.en.guardDoneText,
109
121
  'field.graceMs': 'Grace period (ms)',
110
122
  'field.graceMsHint': 'Wait after an interruption; cancelled if the host recovers on its own.',
111
123
  'field.cooldownMs': 'Cooldown (ms)',
@@ -122,6 +134,8 @@ export const en: Record<SettingsCardKey, string> = {
122
134
  'field.verboseHint': 'Log [auto-continue] lines to the browser console.',
123
135
  'field.classify': 'Classify errors',
124
136
  'field.classifyHint': 'Auto-resume transient failures only (network/timeout/5xx…); auth, balance and model errors are skipped and notified.',
137
+ 'field.retryableErrorPatterns': 'Custom retryable errors',
138
+ '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.',
125
139
  'field.backoffFactor': 'Backoff factor',
126
140
  'field.backoffFactorHint': 'Cooldown multiplier per consecutive failure (2 = 20s→40s→80s…).',
127
141
  'field.backoffMaxMs': 'Max backoff (ms)',
@@ -149,6 +163,7 @@ export const en: Record<SettingsCardKey, string> = {
149
163
  'field.loopToolRepeatHint': 'How many consecutive calls of the same tool with identical arguments and results trip the loop guard; a changed argument or result counts as progress.',
150
164
  'field.loopText': 'Loop text',
151
165
  'field.loopTextHint': 'Text sent after the loop guard restarts a turn; supports the {tool} placeholder.',
166
+ 'default.loopText': LOCALIZED_TEXT_DEFAULTS.en.loopText,
152
167
  'stats.byCode': 'By error code',
153
168
  'stats.empty': 'No auto-continue activity today.',
154
169
  'stats.reset': 'Reset',