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/README.md +32 -16
- package/README.zh.md +23 -7
- package/lib/client.js +107 -39
- package/lib/client.js.map +3 -3
- package/lib/index.js +81 -31
- package/lib/types/client/locales.d.ts +7 -5
- package/lib/types/client/settings-card.d.ts +1 -0
- package/lib/types/index.d.ts +9 -1
- package/lib/types/shared/core.d.ts +26 -2
- package/package.json +2 -2
- package/src/client/index.ts +12 -1
- package/src/client/locales.ts +16 -1
- package/src/client/settings-card.tsx +46 -18
- package/src/client/styles.ts +1 -0
- package/src/host/engine.ts +47 -13
- package/src/index.ts +10 -6
- package/src/shared/core.ts +53 -13
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
|
-
|
|
103
|
-
|
|
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
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
const
|
|
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 :
|
|
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
|
-
|
|
574
|
-
|
|
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:
|
|
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
|
-
|
|
746
|
-
|
|
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
|
-
|
|
756
|
-
|
|
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("
|
|
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("
|
|
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;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
/**
|
|
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
|
|
4
|
-
"version": "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",
|
package/src/client/index.ts
CHANGED
|
@@ -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(
|
package/src/client/locales.ts
CHANGED
|
@@ -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
|
|
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',
|