dsh-client-auto-continue 0.9.0 → 0.10.1
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 +20 -15
- package/README.zh.md +11 -6
- package/lib/client.js +71 -38
- package/lib/client.js.map +3 -3
- package/lib/index.js +101 -32
- package/lib/types/client/locales.d.ts +5 -5
- package/lib/types/host/engine.d.ts +1 -0
- package/lib/types/index.d.ts +5 -1
- package/lib/types/shared/core.d.ts +22 -1
- package/package.json +2 -2
- package/src/client/index.ts +12 -1
- package/src/client/locales.ts +12 -1
- package/src/client/settings-card.tsx +6 -6
- package/src/host/engine.ts +68 -16
- package/src/index.ts +24 -6
- package/src/shared/core.ts +37 -11
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,
|
|
@@ -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
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
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;
|
|
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 :
|
|
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).
|
|
@@ -279,7 +317,10 @@ var AutoContinueRunner = class {
|
|
|
279
317
|
this.noticeListeners = /* @__PURE__ */ new Set();
|
|
280
318
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
281
319
|
this.disposed = false;
|
|
282
|
-
|
|
320
|
+
this.disposeSessionEvents = ctx.on(
|
|
321
|
+
"session/event",
|
|
322
|
+
(session, event) => this.onHostEvent(session, event)
|
|
323
|
+
);
|
|
283
324
|
const config = this.getConfig();
|
|
284
325
|
if (config.scanOnBoot) {
|
|
285
326
|
void this.bootScanLoop();
|
|
@@ -341,7 +382,9 @@ var AutoContinueRunner = class {
|
|
|
341
382
|
this.emitState();
|
|
342
383
|
}
|
|
343
384
|
dispose() {
|
|
385
|
+
if (this.disposed) return;
|
|
344
386
|
this.disposed = true;
|
|
387
|
+
this.disposeSessionEvents();
|
|
345
388
|
for (const state of this.states.values()) {
|
|
346
389
|
if (state.pendingTimer !== void 0) clearTimeout(state.pendingTimer);
|
|
347
390
|
if (state.loopRetryTimer !== void 0) clearTimeout(state.loopRetryTimer);
|
|
@@ -522,7 +565,11 @@ ${event.data.arguments}`;
|
|
|
522
565
|
if (state.loopRetryTimer !== void 0) clearTimeout(state.loopRetryTimer);
|
|
523
566
|
state.loopRetryTimer = setTimeout(() => {
|
|
524
567
|
state.loopRetryTimer = void 0;
|
|
525
|
-
|
|
568
|
+
try {
|
|
569
|
+
this.schedule(sessionId, "loop:aborted");
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.error(`[auto-continue] loop 重启异常 ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
572
|
+
}
|
|
526
573
|
}, remaining);
|
|
527
574
|
this.log(`loop 重启延迟 ${remaining}ms(冷却期) ${sessionId}`);
|
|
528
575
|
} else {
|
|
@@ -569,14 +616,15 @@ ${event.data.arguments}`;
|
|
|
569
616
|
onTurnFailure(sessionId, reason, failure) {
|
|
570
617
|
const config = this.getConfig();
|
|
571
618
|
if (config.classify && !isTransientFailure(failure, config.retryableErrorPatterns)) {
|
|
619
|
+
const copy = NOTICE_COPY[config.locale];
|
|
572
620
|
const summary = `${failure.code}${failure.status !== void 0 ? ` (HTTP ${failure.status})` : ""}`;
|
|
573
621
|
this.log(`跳过 ${sessionId}(${reason}): 永久性失败 ${summary} — ${failure.message}`);
|
|
574
622
|
this.bumpStat({ skipped: 1, code: failure.code });
|
|
575
623
|
if (config.notify) {
|
|
576
624
|
this.notify(
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
this.notifyOptions(sessionId)
|
|
625
|
+
copy.notContinuedTitle,
|
|
626
|
+
copy.permanentErrorBody(sessionId, summary),
|
|
627
|
+
this.notifyOptions(sessionId, config.locale)
|
|
580
628
|
);
|
|
581
629
|
}
|
|
582
630
|
return;
|
|
@@ -584,11 +632,12 @@ ${event.data.arguments}`;
|
|
|
584
632
|
this.schedule(sessionId, reason);
|
|
585
633
|
}
|
|
586
634
|
/** 通知操作按钮与回调(「立即续跑」/「暂停该会话 1 小时」)。 */
|
|
587
|
-
notifyOptions(sessionId) {
|
|
635
|
+
notifyOptions(sessionId, locale) {
|
|
636
|
+
const copy = NOTICE_COPY[locale];
|
|
588
637
|
return {
|
|
589
638
|
actions: [
|
|
590
|
-
{ action: "resume", title:
|
|
591
|
-
{ action: "pause1h", title:
|
|
639
|
+
{ action: "resume", title: copy.resumeAction },
|
|
640
|
+
{ action: "pause1h", title: copy.pauseAction }
|
|
592
641
|
],
|
|
593
642
|
onAction: (action) => this.onNotifyAction(sessionId, action)
|
|
594
643
|
};
|
|
@@ -651,7 +700,11 @@ ${event.data.arguments}`;
|
|
|
651
700
|
clearTimeout(state.pendingTimer);
|
|
652
701
|
state.pendingTimer = void 0;
|
|
653
702
|
}
|
|
654
|
-
|
|
703
|
+
try {
|
|
704
|
+
await this.fire(sessionId, "manual:notification", true);
|
|
705
|
+
} catch (error) {
|
|
706
|
+
console.error(`[auto-continue] 手动续跑异常 ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
707
|
+
}
|
|
655
708
|
}
|
|
656
709
|
/** 本会话当前生效的冷却间隔(自适应退避)。 */
|
|
657
710
|
cooldownFor(state) {
|
|
@@ -686,7 +739,11 @@ ${event.data.arguments}`;
|
|
|
686
739
|
const timer = setTimeout(() => {
|
|
687
740
|
if (state.pendingTimer !== timer) return;
|
|
688
741
|
state.pendingTimer = void 0;
|
|
689
|
-
|
|
742
|
+
try {
|
|
743
|
+
void this.fire(sessionId, reason);
|
|
744
|
+
} catch (error) {
|
|
745
|
+
console.error(`[auto-continue] 定时发送异常 ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
746
|
+
}
|
|
690
747
|
}, config.graceMs);
|
|
691
748
|
state.pendingTimer = timer;
|
|
692
749
|
const template = reason.startsWith("loop:") ? config.loopText : reason.includes("max-tokens") ? config.continueTextMaxTokens : config.continueText;
|
|
@@ -745,20 +802,22 @@ ${event.data.arguments}`;
|
|
|
745
802
|
this.bumpStat({ sent: 1, ...state.lastFailure !== void 0 ? { code: state.lastFailure.code } : {} });
|
|
746
803
|
this.log(`已自动发送「${text}」到 ${sessionId}(${reason}), 第 ${state.consecutive} 次连续`);
|
|
747
804
|
if (config.notify) {
|
|
805
|
+
const copy = NOTICE_COPY[config.locale];
|
|
748
806
|
this.notify(
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
this.notifyOptions(sessionId)
|
|
807
|
+
copy.continuedTitle,
|
|
808
|
+
copy.continuedBody(sessionId, text, state.consecutive),
|
|
809
|
+
this.notifyOptions(sessionId, config.locale)
|
|
752
810
|
);
|
|
753
811
|
}
|
|
754
812
|
if (state.consecutive >= config.maxConsecutive) {
|
|
755
813
|
this.bumpStat({ gaveUp: 1 });
|
|
756
814
|
this.log(`达到连续上限 ${config.maxConsecutive} 次, 停止自动继续 ${sessionId}`);
|
|
757
815
|
if (config.notify) {
|
|
816
|
+
const copy = NOTICE_COPY[config.locale];
|
|
758
817
|
this.notify(
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
this.notifyOptions(sessionId)
|
|
818
|
+
copy.stoppedTitle,
|
|
819
|
+
copy.stoppedBody(sessionId, state.consecutive),
|
|
820
|
+
this.notifyOptions(sessionId, config.locale)
|
|
762
821
|
);
|
|
763
822
|
}
|
|
764
823
|
}
|
|
@@ -891,16 +950,18 @@ ${event.data.arguments}`;
|
|
|
891
950
|
// src/index.ts
|
|
892
951
|
var AUTO_CONTINUE_NS = "auto-continue";
|
|
893
952
|
var AutoContinueSchema = z2.object({
|
|
953
|
+
/** Active browser/UI locale mirrored by the client. */
|
|
954
|
+
locale: z2.string().default("zh"),
|
|
894
955
|
/** Text automatically sent after an interruption. */
|
|
895
|
-
continueText: z2.string().default("
|
|
956
|
+
continueText: z2.string().default(""),
|
|
896
957
|
/** Text sent when the output token ceiling is reached (same placeholders as `continueText`). */
|
|
897
|
-
continueTextMaxTokens: z2.string().default("
|
|
958
|
+
continueTextMaxTokens: z2.string().default(""),
|
|
898
959
|
/** Idempotency guard: inspect the last tool call before resuming and steer the model. */
|
|
899
960
|
guardTools: z2.boolean().default(true),
|
|
900
961
|
/** Guard text appended when the last tool call has no confirmed result (it may have partially executed). */
|
|
901
|
-
guardPendingText: z2.string().default("
|
|
962
|
+
guardPendingText: z2.string().default(""),
|
|
902
963
|
/** Guard text appended when the last tool call completed successfully (don't rerun it). */
|
|
903
|
-
guardDoneText: z2.string().default("
|
|
964
|
+
guardDoneText: z2.string().default(""),
|
|
904
965
|
/** Grace period after an interruption before auto-sending (ms). */
|
|
905
966
|
graceMs: z2.natural().default(3e3),
|
|
906
967
|
/** Minimum interval between two auto-continues per session (ms). */
|
|
@@ -940,7 +1001,7 @@ var AutoContinueSchema = z2.object({
|
|
|
940
1001
|
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
941
1002
|
loopToolRepeat: z2.natural().min(2).default(5),
|
|
942
1003
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
943
|
-
loopText: z2.string().default("
|
|
1004
|
+
loopText: z2.string().default("")
|
|
944
1005
|
});
|
|
945
1006
|
function apply(ctx) {
|
|
946
1007
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
@@ -948,11 +1009,14 @@ function apply(ctx) {
|
|
|
948
1009
|
applies: "live"
|
|
949
1010
|
});
|
|
950
1011
|
});
|
|
1012
|
+
let runnerRef;
|
|
951
1013
|
ctx.inject(["settings", "agents", "webServer"], (engineCtx) => {
|
|
1014
|
+
if (runnerRef !== void 0) runnerRef.dispose();
|
|
952
1015
|
const runner = new AutoContinueRunner(
|
|
953
1016
|
engineCtx,
|
|
954
1017
|
() => resolveConfig(engineCtx.settings.get(settingsNamespace(AUTO_CONTINUE_NS)))
|
|
955
1018
|
);
|
|
1019
|
+
runnerRef = runner;
|
|
956
1020
|
const sseClients = /* @__PURE__ */ new Set();
|
|
957
1021
|
const pushToAll = (data) => {
|
|
958
1022
|
for (const send of sseClients) {
|
|
@@ -1027,6 +1091,11 @@ function apply(ctx) {
|
|
|
1027
1091
|
}
|
|
1028
1092
|
});
|
|
1029
1093
|
});
|
|
1094
|
+
ctx.effect(() => () => {
|
|
1095
|
+
const runner = runnerRef;
|
|
1096
|
+
runnerRef = void 0;
|
|
1097
|
+
if (runner !== void 0) runner.dispose();
|
|
1098
|
+
});
|
|
1030
1099
|
}
|
|
1031
1100
|
export {
|
|
1032
1101
|
AUTO_CONTINUE_NS,
|
|
@@ -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;
|
|
@@ -36,6 +36,7 @@ export declare class AutoContinueRunner {
|
|
|
36
36
|
private readonly notices;
|
|
37
37
|
private readonly noticeListeners;
|
|
38
38
|
private readonly stateListeners;
|
|
39
|
+
private readonly disposeSessionEvents;
|
|
39
40
|
private disposed;
|
|
40
41
|
/**
|
|
41
42
|
* @param ctx - host plugin context (agents registry, session events, settings).
|
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`). */
|
|
@@ -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
|
-
/**
|
|
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
|
|
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.1",
|
|
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)',
|
|
@@ -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
|
|
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 {
|
|
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={
|
|
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={
|
|
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={
|
|
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={
|
|
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={
|
|
680
|
+
placeholder={t('default.loopText')}
|
|
681
681
|
onReset={() => props.resetField('loopText')}
|
|
682
682
|
/>
|
|
683
683
|
<LivePanels t={t} />
|