dsh-client-auto-continue 0.9.0 → 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,
@@ -122,8 +136,7 @@ var DEFAULT_CONFIG = {
122
136
  loopWindowMs: 3e4,
123
137
  loopShortCount: 12,
124
138
  loopRepeatText: 4,
125
- loopToolRepeat: 5,
126
- loopText: "(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)"
139
+ loopToolRepeat: 5
127
140
  };
128
141
  function numberOr(value, fallback) {
129
142
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : fallback;
@@ -133,11 +146,14 @@ function booleanOr(value, fallback) {
133
146
  }
134
147
  function resolveConfig(section) {
135
148
  const value = section ?? {};
136
- const text = typeof value.continueText === "string" && value.continueText.trim() !== "" ? value.continueText : DEFAULT_CONFIG.continueText;
137
- const maxTokensText = typeof value.continueTextMaxTokens === "string" && value.continueTextMaxTokens.trim() !== "" ? value.continueTextMaxTokens : DEFAULT_CONFIG.continueTextMaxTokens;
138
- const guardPendingText = typeof value.guardPendingText === "string" && value.guardPendingText.trim() !== "" ? value.guardPendingText : DEFAULT_CONFIG.guardPendingText;
139
- 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;
140
155
  return {
156
+ locale,
141
157
  continueText: text,
142
158
  continueTextMaxTokens: maxTokensText,
143
159
  guardTools: booleanOr(value.guardTools, DEFAULT_CONFIG.guardTools),
@@ -162,7 +178,7 @@ function resolveConfig(section) {
162
178
  loopShortCount: Math.max(2, numberOr(value.loopShortCount, DEFAULT_CONFIG.loopShortCount)),
163
179
  loopRepeatText: Math.max(2, numberOr(value.loopRepeatText, DEFAULT_CONFIG.loopRepeatText)),
164
180
  loopToolRepeat: Math.max(2, numberOr(value.loopToolRepeat, DEFAULT_CONFIG.loopToolRepeat)),
165
- 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
166
182
  };
167
183
  }
168
184
  function isNonHumanReason(kind) {
@@ -264,6 +280,28 @@ function isOurEcho(state, event) {
264
280
  }
265
281
 
266
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
+ };
267
305
  var AutoContinueRunner = class {
268
306
  /**
269
307
  * @param ctx - host plugin context (agents registry, session events, settings).
@@ -569,14 +607,15 @@ ${event.data.arguments}`;
569
607
  onTurnFailure(sessionId, reason, failure) {
570
608
  const config = this.getConfig();
571
609
  if (config.classify && !isTransientFailure(failure, config.retryableErrorPatterns)) {
610
+ const copy = NOTICE_COPY[config.locale];
572
611
  const summary = `${failure.code}${failure.status !== void 0 ? ` (HTTP ${failure.status})` : ""}`;
573
612
  this.log(`跳过 ${sessionId}(${reason}): 永久性失败 ${summary} — ${failure.message}`);
574
613
  this.bumpStat({ skipped: 1, code: failure.code });
575
614
  if (config.notify) {
576
615
  this.notify(
577
- "dsh-auto-continue: 未自动继续",
578
- `${sessionId}: 永久性错误 ${summary},需要人工处理`,
579
- this.notifyOptions(sessionId)
616
+ copy.notContinuedTitle,
617
+ copy.permanentErrorBody(sessionId, summary),
618
+ this.notifyOptions(sessionId, config.locale)
580
619
  );
581
620
  }
582
621
  return;
@@ -584,11 +623,12 @@ ${event.data.arguments}`;
584
623
  this.schedule(sessionId, reason);
585
624
  }
586
625
  /** 通知操作按钮与回调(「立即续跑」/「暂停该会话 1 小时」)。 */
587
- notifyOptions(sessionId) {
626
+ notifyOptions(sessionId, locale) {
627
+ const copy = NOTICE_COPY[locale];
588
628
  return {
589
629
  actions: [
590
- { action: "resume", title: "立即续跑" },
591
- { action: "pause1h", title: "暂停该会话 1 小时" }
630
+ { action: "resume", title: copy.resumeAction },
631
+ { action: "pause1h", title: copy.pauseAction }
592
632
  ],
593
633
  onAction: (action) => this.onNotifyAction(sessionId, action)
594
634
  };
@@ -745,20 +785,22 @@ ${event.data.arguments}`;
745
785
  this.bumpStat({ sent: 1, ...state.lastFailure !== void 0 ? { code: state.lastFailure.code } : {} });
746
786
  this.log(`已自动发送「${text}」到 ${sessionId}(${reason}), 第 ${state.consecutive} 次连续`);
747
787
  if (config.notify) {
788
+ const copy = NOTICE_COPY[config.locale];
748
789
  this.notify(
749
- "dsh-auto-continue: 已自动继续",
750
- `${sessionId}: 已发送「${text}」(第 ${state.consecutive} 次连续)`,
751
- this.notifyOptions(sessionId)
790
+ copy.continuedTitle,
791
+ copy.continuedBody(sessionId, text, state.consecutive),
792
+ this.notifyOptions(sessionId, config.locale)
752
793
  );
753
794
  }
754
795
  if (state.consecutive >= config.maxConsecutive) {
755
796
  this.bumpStat({ gaveUp: 1 });
756
797
  this.log(`达到连续上限 ${config.maxConsecutive} 次, 停止自动继续 ${sessionId}`);
757
798
  if (config.notify) {
799
+ const copy = NOTICE_COPY[config.locale];
758
800
  this.notify(
759
- "dsh-auto-continue: 已停止自动继续",
760
- `${sessionId}: 连续失败 ${state.consecutive} 次, 需要人工介入`,
761
- this.notifyOptions(sessionId)
801
+ copy.stoppedTitle,
802
+ copy.stoppedBody(sessionId, state.consecutive),
803
+ this.notifyOptions(sessionId, config.locale)
762
804
  );
763
805
  }
764
806
  }
@@ -891,16 +933,18 @@ ${event.data.arguments}`;
891
933
  // src/index.ts
892
934
  var AUTO_CONTINUE_NS = "auto-continue";
893
935
  var AutoContinueSchema = z2.object({
936
+ /** Active browser/UI locale mirrored by the client. */
937
+ locale: z2.string().default("zh"),
894
938
  /** Text automatically sent after an interruption. */
895
- continueText: z2.string().default("继续"),
939
+ continueText: z2.string().default(""),
896
940
  /** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
897
- continueTextMaxTokens: z2.string().default("继续"),
941
+ continueTextMaxTokens: z2.string().default(""),
898
942
  /** Idempotency guard: inspect the last tool call before resuming and steer the model. */
899
943
  guardTools: z2.boolean().default(true),
900
944
  /** Guard text appended when the last tool call has no confirmed result (it may have partially executed). */
901
- guardPendingText: z2.string().default("(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)"),
945
+ guardPendingText: z2.string().default(""),
902
946
  /** Guard text appended when the last tool call completed successfully (don't rerun it). */
903
- guardDoneText: z2.string().default("(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)"),
947
+ guardDoneText: z2.string().default(""),
904
948
  /** Grace period after an interruption before auto-sending (ms). */
905
949
  graceMs: z2.natural().default(3e3),
906
950
  /** Minimum interval between two auto-continues per session (ms). */
@@ -940,7 +984,7 @@ var AutoContinueSchema = z2.object({
940
984
  /** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
941
985
  loopToolRepeat: z2.natural().min(2).default(5),
942
986
  /** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
943
- loopText: z2.string().default("(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)")
987
+ loopText: z2.string().default("")
944
988
  });
945
989
  function apply(ctx) {
946
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;
@@ -64,6 +63,7 @@ export declare const zh: {
64
63
  'field.loopToolRepeatHint': string;
65
64
  'field.loopText': string;
66
65
  'field.loopTextHint': string;
66
+ 'default.loopText': "(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)";
67
67
  'stats.byCode': string;
68
68
  'stats.empty': string;
69
69
  'stats.reset': string;
@@ -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`). */
@@ -66,6 +68,8 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
66
68
  /** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
67
69
  loopText: z<string, string>;
68
70
  }>, Schemastery.ObjectT<{
71
+ /** Active browser/UI locale mirrored by the client. */
72
+ locale: z<string, string>;
69
73
  /** Text automatically sent after an interruption. */
70
74
  continueText: z<string, string>;
71
75
  /** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
@@ -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`). */
@@ -60,7 +81,7 @@ export interface AutoContinueSettings {
60
81
  }
61
82
  /** Fully resolved configuration (built-in defaults + user overrides). */
62
83
  export type AutoContinueConfig = Required<AutoContinueSettings>;
63
- /** 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. */
64
85
  export declare const DEFAULT_CONFIG: AutoContinueConfig;
65
86
  /** Resolve a (possibly partial / not-yet-loaded) settings section to a full config. */
66
87
  export declare function resolveConfig(section: AutoContinueSettings | undefined): AutoContinueConfig;
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.9.0",
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)',
@@ -65,6 +70,7 @@ export const zh = {
65
70
  'field.loopToolRepeatHint': '同工具+同参数+同结果的连续调用多少次时判定死循环; 参数或结果有变化视为有进展。',
66
71
  'field.loopText': '循环提示文本',
67
72
  'field.loopTextHint': '打断后重启回合时发送的文本, 支持 {tool} 占位符。',
73
+ 'default.loopText': LOCALIZED_TEXT_DEFAULTS.zh.loopText,
68
74
  'stats.byCode': '按错误码统计',
69
75
  'stats.empty': '今天还没有自动继续记录。',
70
76
  'stats.reset': '清零',
@@ -95,19 +101,23 @@ export type SettingsCardKey = keyof typeof zh;
95
101
  /** English dictionary, checked complete against the zh key set. */
96
102
  export const en: Record<SettingsCardKey, string> = {
97
103
  'card.title': 'Auto continue',
98
- '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.',
99
105
  'field.paused': 'Pause auto-continue',
100
106
  'field.pausedHint': 'Globally pause: no live or scan auto-send fires, and queued pending sends are cancelled.',
101
107
  'field.continueText': 'Continue text',
102
108
  'field.continueTextHint': 'Message automatically sent after an interruption.',
109
+ 'default.continueText': LOCALIZED_TEXT_DEFAULTS.en.continueText,
103
110
  'field.continueTextMaxTokens': 'Continue text (max tokens)',
104
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,
105
113
  'field.guardTools': 'Idempotency guard',
106
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.',
107
115
  'field.guardPendingText': 'Guard text (unconfirmed result)',
108
116
  'field.guardPendingTextHint': 'Appended when the last tool may have partially executed; supports the {tool} placeholder.',
117
+ 'default.guardPendingText': LOCALIZED_TEXT_DEFAULTS.en.guardPendingText,
109
118
  'field.guardDoneText': 'Guard text (tool succeeded)',
110
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,
111
121
  'field.graceMs': 'Grace period (ms)',
112
122
  'field.graceMsHint': 'Wait after an interruption; cancelled if the host recovers on its own.',
113
123
  'field.cooldownMs': 'Cooldown (ms)',
@@ -153,6 +163,7 @@ export const en: Record<SettingsCardKey, string> = {
153
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.',
154
164
  'field.loopText': 'Loop text',
155
165
  'field.loopTextHint': 'Text sent after the loop guard restarts a turn; supports the {tool} placeholder.',
166
+ 'default.loopText': LOCALIZED_TEXT_DEFAULTS.en.loopText,
156
167
  'stats.byCode': 'By error code',
157
168
  'stats.empty': 'No auto-continue activity today.',
158
169
  'stats.reset': 'Reset',
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { useEffect, useState, type ReactNode } from 'react';
11
11
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
12
- import { DEFAULT_CONFIG, type AutoContinueSettings } from './engine.ts';
12
+ import { type AutoContinueSettings } from './engine.ts';
13
13
  import { createSnapshotStore, type SettingsScope, type SnapshotStore } from './dsh-store-compat.ts';
14
14
  import {
15
15
  pausedSessions,
@@ -452,7 +452,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
452
452
  {...shared}
453
453
  {...state.continueText}
454
454
  onEdit={(text) => props.edit('continueText', text)}
455
- placeholder={DEFAULT_CONFIG.continueText}
455
+ placeholder={t('default.continueText')}
456
456
  onReset={() => props.resetField('continueText')}
457
457
  />
458
458
  <ValueField
@@ -462,7 +462,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
462
462
  {...shared}
463
463
  {...state.continueTextMaxTokens}
464
464
  onEdit={(text) => props.edit('continueTextMaxTokens', text)}
465
- placeholder={DEFAULT_CONFIG.continueTextMaxTokens}
465
+ placeholder={t('default.continueTextMaxTokens')}
466
466
  onReset={() => props.resetField('continueTextMaxTokens')}
467
467
  />
468
468
  <BooleanField
@@ -481,7 +481,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
481
481
  {...shared}
482
482
  {...state.guardPendingText}
483
483
  onEdit={(text) => props.edit('guardPendingText', text)}
484
- placeholder={DEFAULT_CONFIG.guardPendingText}
484
+ placeholder={t('default.guardPendingText')}
485
485
  onReset={() => props.resetField('guardPendingText')}
486
486
  />
487
487
  <ValueField
@@ -491,7 +491,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
491
491
  {...shared}
492
492
  {...state.guardDoneText}
493
493
  onEdit={(text) => props.edit('guardDoneText', text)}
494
- placeholder={DEFAULT_CONFIG.guardDoneText}
494
+ placeholder={t('default.guardDoneText')}
495
495
  onReset={() => props.resetField('guardDoneText')}
496
496
  />
497
497
  <ValueField
@@ -677,7 +677,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
677
677
  {...shared}
678
678
  {...state.loopText}
679
679
  onEdit={(text) => props.edit('loopText', text)}
680
- placeholder={DEFAULT_CONFIG.loopText}
680
+ placeholder={t('default.loopText')}
681
681
  onReset={() => props.resetField('loopText')}
682
682
  />
683
683
  <LivePanels t={t} />
@@ -34,6 +34,7 @@ import {
34
34
  todayKey,
35
35
  toolResultFacts,
36
36
  type AutoContinueConfig,
37
+ type AutoContinueLocale,
37
38
  type DayStats,
38
39
  type FailureFacts,
39
40
  type SessionState,
@@ -42,6 +43,35 @@ import {
42
43
  type TemplateContext,
43
44
  } from '../shared/core.ts';
44
45
 
46
+ const NOTICE_COPY = {
47
+ zh: {
48
+ notContinuedTitle: 'dsh-auto-continue: 未自动继续',
49
+ permanentErrorBody: (sessionId: SessionId, summary: string) =>
50
+ `${sessionId}: 永久性错误 ${summary},需要人工处理`,
51
+ resumeAction: '立即续跑',
52
+ pauseAction: '暂停该会话 1 小时',
53
+ continuedTitle: 'dsh-auto-continue: 已自动继续',
54
+ continuedBody: (sessionId: SessionId, text: string, count: number) =>
55
+ `${sessionId}: 已发送「${text}」(第 ${count} 次连续)`,
56
+ stoppedTitle: 'dsh-auto-continue: 已停止自动继续',
57
+ stoppedBody: (sessionId: SessionId, count: number) =>
58
+ `${sessionId}: 连续失败 ${count} 次, 需要人工介入`,
59
+ },
60
+ en: {
61
+ notContinuedTitle: 'dsh-auto-continue: Not continued',
62
+ permanentErrorBody: (sessionId: SessionId, summary: string) =>
63
+ `${sessionId}: Permanent error ${summary}; manual intervention required`,
64
+ resumeAction: 'Resume now',
65
+ pauseAction: 'Pause this session for 1 hour',
66
+ continuedTitle: 'dsh-auto-continue: Continued automatically',
67
+ continuedBody: (sessionId: SessionId, text: string, count: number) =>
68
+ `${sessionId}: Sent "${text}" (consecutive attempt ${count})`,
69
+ stoppedTitle: 'dsh-auto-continue: Auto-continue stopped',
70
+ stoppedBody: (sessionId: SessionId, count: number) =>
71
+ `${sessionId}: ${count} consecutive failures; manual intervention required`,
72
+ },
73
+ } as const satisfies Record<AutoContinueLocale, Record<string, unknown>>;
74
+
45
75
  /** 通知桥事件: host 引擎产生, browser 侧订阅展示(Notification / 动作按钮)。 */
46
76
  export interface HostNotice {
47
77
  /** 稳定标识(供 browser 去重)。 */
@@ -460,14 +490,15 @@ export class AutoContinueRunner {
460
490
  private onTurnFailure(sessionId: SessionId, reason: string, failure: FailureFacts): void {
461
491
  const config = this.getConfig();
462
492
  if (config.classify && !isTransientFailure(failure, config.retryableErrorPatterns)) {
493
+ const copy = NOTICE_COPY[config.locale];
463
494
  const summary = `${failure.code}${failure.status !== undefined ? ` (HTTP ${failure.status})` : ''}`;
464
495
  this.log(`跳过 ${sessionId}(${reason}): 永久性失败 ${summary} — ${failure.message}`);
465
496
  this.bumpStat({ skipped: 1, code: failure.code });
466
497
  if (config.notify) {
467
498
  this.notify(
468
- 'dsh-auto-continue: 未自动继续',
469
- `${sessionId}: 永久性错误 ${summary},需要人工处理`,
470
- this.notifyOptions(sessionId),
499
+ copy.notContinuedTitle,
500
+ copy.permanentErrorBody(sessionId, summary),
501
+ this.notifyOptions(sessionId, config.locale),
471
502
  );
472
503
  }
473
504
  return;
@@ -476,11 +507,12 @@ export class AutoContinueRunner {
476
507
  }
477
508
 
478
509
  /** 通知操作按钮与回调(「立即续跑」/「暂停该会话 1 小时」)。 */
479
- private notifyOptions(sessionId: SessionId): NotifyOptions {
510
+ private notifyOptions(sessionId: SessionId, locale: AutoContinueLocale): NotifyOptions {
511
+ const copy = NOTICE_COPY[locale];
480
512
  return {
481
513
  actions: [
482
- { action: 'resume', title: '立即续跑' },
483
- { action: 'pause1h', title: '暂停该会话 1 小时' },
514
+ { action: 'resume', title: copy.resumeAction },
515
+ { action: 'pause1h', title: copy.pauseAction },
484
516
  ],
485
517
  onAction: (action) => this.onNotifyAction(sessionId, action),
486
518
  };
@@ -668,20 +700,22 @@ export class AutoContinueRunner {
668
700
  this.bumpStat({ sent: 1, ...(state.lastFailure !== undefined ? { code: state.lastFailure.code } : {}) });
669
701
  this.log(`已自动发送「${text}」到 ${sessionId}(${reason}), 第 ${state.consecutive} 次连续`);
670
702
  if (config.notify) {
703
+ const copy = NOTICE_COPY[config.locale];
671
704
  this.notify(
672
- 'dsh-auto-continue: 已自动继续',
673
- `${sessionId}: 已发送「${text}」(第 ${state.consecutive} 次连续)`,
674
- this.notifyOptions(sessionId),
705
+ copy.continuedTitle,
706
+ copy.continuedBody(sessionId, text, state.consecutive),
707
+ this.notifyOptions(sessionId, config.locale),
675
708
  );
676
709
  }
677
710
  if (state.consecutive >= config.maxConsecutive) {
678
711
  this.bumpStat({ gaveUp: 1 });
679
712
  this.log(`达到连续上限 ${config.maxConsecutive} 次, 停止自动继续 ${sessionId}`);
680
713
  if (config.notify) {
714
+ const copy = NOTICE_COPY[config.locale];
681
715
  this.notify(
682
- 'dsh-auto-continue: 已停止自动继续',
683
- `${sessionId}: 连续失败 ${state.consecutive} 次, 需要人工介入`,
684
- this.notifyOptions(sessionId),
716
+ copy.stoppedTitle,
717
+ copy.stoppedBody(sessionId, state.consecutive),
718
+ this.notifyOptions(sessionId, config.locale),
685
719
  );
686
720
  }
687
721
  }
package/src/index.ts CHANGED
@@ -24,22 +24,24 @@ import type {} from '@deepseek-ai/dsh-session';
24
24
  /** Settings namespace of the auto-continue plugin (lowercase kebab-case). */
25
25
  export const AUTO_CONTINUE_NS = 'auto-continue';
26
26
 
27
- /** Wire schema of the auto-continue section; defaults are the plugin's built-in values. */
27
+ /** Wire schema; blank localized text fields tell resolveConfig() to select the active locale's defaults. */
28
28
  export const AutoContinueSchema = z.object({
29
+ /** Active browser/UI locale mirrored by the client. */
30
+ locale: z.string().default('zh'),
29
31
  /** Text automatically sent after an interruption. */
30
- continueText: z.string().default('继续'),
32
+ continueText: z.string().default(''),
31
33
  /** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
32
- continueTextMaxTokens: z.string().default('继续'),
34
+ continueTextMaxTokens: z.string().default(''),
33
35
  /** Idempotency guard: inspect the last tool call before resuming and steer the model. */
34
36
  guardTools: z.boolean().default(true),
35
37
  /** Guard text appended when the last tool call has no confirmed result (it may have partially executed). */
36
38
  guardPendingText: z
37
39
  .string()
38
- .default('(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)'),
40
+ .default(''),
39
41
  /** Guard text appended when the last tool call completed successfully (don't rerun it). */
40
42
  guardDoneText: z
41
43
  .string()
42
- .default('(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)'),
44
+ .default(''),
43
45
  /** Grace period after an interruption before auto-sending (ms). */
44
46
  graceMs: z.natural().default(3000),
45
47
  /** Minimum interval between two auto-continues per session (ms). */
@@ -81,7 +83,7 @@ export const AutoContinueSchema = z.object({
81
83
  /** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
82
84
  loopText: z
83
85
  .string()
84
- .default('(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)'),
86
+ .default(''),
85
87
  });
86
88
 
87
89
  /**