dsh-client-auto-continue 0.7.2 → 0.7.3
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 +4 -2
- package/README.zh.md +4 -2
- package/lib/client.js +39 -2
- package/lib/client.js.map +2 -2
- package/lib/index.js +2 -0
- package/lib/types/client/engine.d.ts +2 -0
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/settings-card.d.ts +1 -0
- package/lib/types/index.d.ts +4 -0
- package/package.json +1 -1
- package/src/client/engine.ts +29 -2
- package/src/client/locales.ts +4 -0
- package/src/client/settings-card.tsx +13 -0
- package/src/index.ts +2 -0
package/lib/index.js
CHANGED
|
@@ -49,6 +49,8 @@ var AutoContinueSchema = z.object({
|
|
|
49
49
|
loopWindowMs: z.natural().min(1e3).default(3e4),
|
|
50
50
|
/** Consecutive short sentences trip the loop guard. */
|
|
51
51
|
loopShortCount: z.natural().min(2).default(12),
|
|
52
|
+
/** Consecutive identical short sentences trip the loop guard (strongest spinning signal). */
|
|
53
|
+
loopRepeatText: z.natural().min(2).default(4),
|
|
52
54
|
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
53
55
|
loopToolRepeat: z.natural().min(2).default(5),
|
|
54
56
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
@@ -61,6 +61,8 @@ export interface AutoContinueSettings {
|
|
|
61
61
|
loopShortCount?: number;
|
|
62
62
|
/** Consecutive identical tool calls with identical arguments AND identical results trip the loop guard. */
|
|
63
63
|
loopToolRepeat?: number;
|
|
64
|
+
/** Consecutive identical short sentences trip the loop guard (strongest spinning signal). */
|
|
65
|
+
loopRepeatText?: number;
|
|
64
66
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
65
67
|
loopText?: string;
|
|
66
68
|
}
|
|
@@ -60,6 +60,8 @@ export declare const zh: {
|
|
|
60
60
|
'field.loopWindowMsHint': string;
|
|
61
61
|
'field.loopShortCount': string;
|
|
62
62
|
'field.loopShortCountHint': string;
|
|
63
|
+
'field.loopRepeatText': string;
|
|
64
|
+
'field.loopRepeatTextHint': string;
|
|
63
65
|
'field.loopToolRepeat': string;
|
|
64
66
|
'field.loopToolRepeatHint': string;
|
|
65
67
|
'field.loopText': string;
|
|
@@ -27,6 +27,7 @@ export interface AutoContinueSettingsCardState extends CardShell {
|
|
|
27
27
|
loopShortChars: CardFieldState;
|
|
28
28
|
loopWindowMs: CardFieldState;
|
|
29
29
|
loopShortCount: CardFieldState;
|
|
30
|
+
loopRepeatText: CardFieldState;
|
|
30
31
|
loopToolRepeat: CardFieldState;
|
|
31
32
|
loopText: CardFieldState;
|
|
32
33
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -55,6 +55,8 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
|
|
|
55
55
|
loopWindowMs: z<number, number>;
|
|
56
56
|
/** Consecutive short sentences trip the loop guard. */
|
|
57
57
|
loopShortCount: z<number, number>;
|
|
58
|
+
/** Consecutive identical short sentences trip the loop guard (strongest spinning signal). */
|
|
59
|
+
loopRepeatText: z<number, number>;
|
|
58
60
|
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
59
61
|
loopToolRepeat: z<number, number>;
|
|
60
62
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
@@ -106,6 +108,8 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
|
|
|
106
108
|
loopWindowMs: z<number, number>;
|
|
107
109
|
/** Consecutive short sentences trip the loop guard. */
|
|
108
110
|
loopShortCount: z<number, number>;
|
|
111
|
+
/** Consecutive identical short sentences trip the loop guard (strongest spinning signal). */
|
|
112
|
+
loopRepeatText: z<number, number>;
|
|
109
113
|
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
110
114
|
loopToolRepeat: z<number, number>;
|
|
111
115
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-client-auto-continue",
|
|
3
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.7.
|
|
4
|
+
"version": "0.7.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
package/src/client/engine.ts
CHANGED
|
@@ -70,6 +70,8 @@ export interface AutoContinueSettings {
|
|
|
70
70
|
loopShortCount?: number;
|
|
71
71
|
/** Consecutive identical tool calls with identical arguments AND identical results trip the loop guard. */
|
|
72
72
|
loopToolRepeat?: number;
|
|
73
|
+
/** Consecutive identical short sentences trip the loop guard (strongest spinning signal). */
|
|
74
|
+
loopRepeatText?: number;
|
|
73
75
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
74
76
|
loopText?: string;
|
|
75
77
|
}
|
|
@@ -102,6 +104,7 @@ export const DEFAULT_CONFIG: AutoContinueConfig = {
|
|
|
102
104
|
loopShortChars: 40,
|
|
103
105
|
loopWindowMs: 30000,
|
|
104
106
|
loopShortCount: 12,
|
|
107
|
+
loopRepeatText: 4,
|
|
105
108
|
loopToolRepeat: 5,
|
|
106
109
|
loopText: '(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)',
|
|
107
110
|
};
|
|
@@ -157,6 +160,7 @@ export function resolveConfig(section: AutoContinueSettings | undefined): AutoCo
|
|
|
157
160
|
loopShortChars: Math.max(1, numberOr(value.loopShortChars, DEFAULT_CONFIG.loopShortChars)),
|
|
158
161
|
loopWindowMs: Math.max(1000, numberOr(value.loopWindowMs, DEFAULT_CONFIG.loopWindowMs)),
|
|
159
162
|
loopShortCount: Math.max(2, numberOr(value.loopShortCount, DEFAULT_CONFIG.loopShortCount)),
|
|
163
|
+
loopRepeatText: Math.max(2, numberOr(value.loopRepeatText, DEFAULT_CONFIG.loopRepeatText)),
|
|
160
164
|
loopToolRepeat: Math.max(2, numberOr(value.loopToolRepeat, DEFAULT_CONFIG.loopToolRepeat)),
|
|
161
165
|
loopText:
|
|
162
166
|
typeof value.loopText === 'string' && value.loopText.trim() !== ''
|
|
@@ -602,6 +606,10 @@ interface SessionState {
|
|
|
602
606
|
shortRun: number;
|
|
603
607
|
/** 最后一条短句的时间(时间窗判定用)。 */
|
|
604
608
|
lastShortAt: number;
|
|
609
|
+
/** 最后一条模型消息的文本(相同文本重复判定用)。 */
|
|
610
|
+
lastAssistantText: string;
|
|
611
|
+
/** 连续相同文本消息数(最强空转信号, 不限长度)。 */
|
|
612
|
+
sameTextRun: number;
|
|
605
613
|
/**
|
|
606
614
|
* 工具重复信号(loop guard 信号 2: 死循环)。
|
|
607
615
|
* 只有「同工具 + 同参数 + 同结果」的连续调用才累计; 参数或结果有变化视为有进展, 计数重置。
|
|
@@ -641,6 +649,8 @@ const freshState = (): SessionState => ({
|
|
|
641
649
|
pendingRecoveryAt: 0,
|
|
642
650
|
shortRun: 0,
|
|
643
651
|
lastShortAt: 0,
|
|
652
|
+
lastAssistantText: '',
|
|
653
|
+
sameTextRun: 0,
|
|
644
654
|
toolRun: undefined,
|
|
645
655
|
loopFired: false,
|
|
646
656
|
loopCancelled: false,
|
|
@@ -860,7 +870,17 @@ export class AutoContinueRunner {
|
|
|
860
870
|
): void {
|
|
861
871
|
if (!this.getConfig().loopGuard) return;
|
|
862
872
|
const text = this.assistantText(event);
|
|
863
|
-
|
|
873
|
+
const trimmed = text.trim();
|
|
874
|
+
// 相同文本重复(不限长度): 模型反复输出完全相同的消息是最强的循环信号,
|
|
875
|
+
// 例如 "Let me test variants of the regex..." 连续 7 遍
|
|
876
|
+
if (trimmed !== '' && trimmed === state.lastAssistantText) {
|
|
877
|
+
state.sameTextRun += 1;
|
|
878
|
+
} else {
|
|
879
|
+
state.lastAssistantText = trimmed;
|
|
880
|
+
state.sameTextRun = 1;
|
|
881
|
+
}
|
|
882
|
+
// 短句计数(长度 < loopShortChars 且落在时间窗内): 空转信号
|
|
883
|
+
if (trimmed.length < this.getConfig().loopShortChars) {
|
|
864
884
|
const now = Date.now();
|
|
865
885
|
if (now - state.lastShortAt > this.getConfig().loopWindowMs) {
|
|
866
886
|
state.shortRun = 0; // 超过时间窗: 上一次短句太久远, 不算连续
|
|
@@ -880,7 +900,10 @@ export class AutoContinueRunner {
|
|
|
880
900
|
if (state.loopFired) return;
|
|
881
901
|
if (!state.running) return; // 只干预运行中的回合
|
|
882
902
|
const config = this.getConfig();
|
|
883
|
-
if (state.
|
|
903
|
+
if (state.sameTextRun >= config.loopRepeatText) {
|
|
904
|
+
this.log(`检测到空转循环 ${sessionId}: 连续 ${state.sameTextRun} 条相同消息`);
|
|
905
|
+
void this.interruptLoop(sessionId, state);
|
|
906
|
+
} else if (state.shortRun >= config.loopShortCount) {
|
|
884
907
|
this.log(`检测到空转循环 ${sessionId}: 连续 ${state.shortRun} 条短句且无工具调用`);
|
|
885
908
|
void this.interruptLoop(sessionId, state);
|
|
886
909
|
} else if (state.toolRun !== undefined && state.toolRun.count >= config.loopToolRepeat) {
|
|
@@ -928,6 +951,8 @@ export class AutoContinueRunner {
|
|
|
928
951
|
// loop guard 状态按回合重置
|
|
929
952
|
state.shortRun = 0;
|
|
930
953
|
state.lastShortAt = 0;
|
|
954
|
+
state.lastAssistantText = '';
|
|
955
|
+
state.sameTextRun = 0;
|
|
931
956
|
state.toolRun = undefined;
|
|
932
957
|
state.loopFired = false;
|
|
933
958
|
state.loopCancelled = false;
|
|
@@ -952,6 +977,8 @@ export class AutoContinueRunner {
|
|
|
952
977
|
state.pendingRecoveryAt = 0;
|
|
953
978
|
state.shortRun = 0;
|
|
954
979
|
state.lastShortAt = 0;
|
|
980
|
+
state.lastAssistantText = '';
|
|
981
|
+
state.sameTextRun = 0;
|
|
955
982
|
state.toolRun = undefined;
|
|
956
983
|
state.lastAttemptAt = 0;
|
|
957
984
|
this.schedule(sessionId, 'loop:aborted');
|
package/src/client/locales.ts
CHANGED
|
@@ -61,6 +61,8 @@ export const zh = {
|
|
|
61
61
|
'field.loopWindowMsHint': '连续短句必须落在这个时间窗内; 正常思考的短文本散布在长时间里不会被误判。',
|
|
62
62
|
'field.loopShortCount': '连续短句阈值',
|
|
63
63
|
'field.loopShortCountHint': '时间窗内连续多少条短句且期间无工具调用时判定空转循环。',
|
|
64
|
+
'field.loopRepeatText': '相同消息重复次数',
|
|
65
|
+
'field.loopRepeatTextHint': '连续输出多少条完全相同的消息时判定空转(最强信号, 不限长度, 如模型反复说同一句话)。',
|
|
64
66
|
'field.loopToolRepeat': '同工具重复次数',
|
|
65
67
|
'field.loopToolRepeatHint': '同工具+同参数+同结果的连续调用多少次时判定死循环; 参数或结果有变化视为有进展。',
|
|
66
68
|
'field.loopText': '循环提示文本',
|
|
@@ -149,6 +151,8 @@ export const en: Record<SettingsCardKey, string> = {
|
|
|
149
151
|
'field.loopWindowMsHint': 'Consecutive short sentences must land inside this window; normal thinking spread over time is not misjudged.',
|
|
150
152
|
'field.loopShortCount': 'Short-sentence threshold',
|
|
151
153
|
'field.loopShortCountHint': 'How many consecutive short sentences inside the window, with no tool call, trip the loop guard.',
|
|
154
|
+
'field.loopRepeatText': 'Identical message count',
|
|
155
|
+
'field.loopRepeatTextHint': 'How many consecutive identical messages trip the guard (strongest signal, any length, e.g. the model repeating the same line).',
|
|
152
156
|
'field.loopToolRepeat': 'Same-tool repeat count',
|
|
153
157
|
'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
158
|
'field.loopText': 'Loop text',
|
|
@@ -59,6 +59,7 @@ export interface AutoContinueSettingsCardState extends CardShell {
|
|
|
59
59
|
loopShortChars: CardFieldState;
|
|
60
60
|
loopWindowMs: CardFieldState;
|
|
61
61
|
loopShortCount: CardFieldState;
|
|
62
|
+
loopRepeatText: CardFieldState;
|
|
62
63
|
loopToolRepeat: CardFieldState;
|
|
63
64
|
loopText: CardFieldState;
|
|
64
65
|
}
|
|
@@ -104,6 +105,7 @@ export class AutoContinueSettingsCardController {
|
|
|
104
105
|
numberField('loopShortChars', 1),
|
|
105
106
|
numberField('loopWindowMs', 1000),
|
|
106
107
|
numberField('loopShortCount', 2),
|
|
108
|
+
numberField('loopRepeatText', 2),
|
|
107
109
|
numberField('loopToolRepeat', 2),
|
|
108
110
|
textField('loopText'),
|
|
109
111
|
]);
|
|
@@ -136,6 +138,7 @@ export class AutoContinueSettingsCardController {
|
|
|
136
138
|
loopShortChars: this.form.field('loopShortChars'),
|
|
137
139
|
loopWindowMs: this.form.field('loopWindowMs'),
|
|
138
140
|
loopShortCount: this.form.field('loopShortCount'),
|
|
141
|
+
loopRepeatText: this.form.field('loopRepeatText'),
|
|
139
142
|
loopToolRepeat: this.form.field('loopToolRepeat'),
|
|
140
143
|
loopText: this.form.field('loopText'),
|
|
141
144
|
};
|
|
@@ -650,6 +653,16 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
|
|
|
650
653
|
onEdit={(text) => props.edit('loopToolRepeat', text)}
|
|
651
654
|
onReset={() => props.resetField('loopToolRepeat')}
|
|
652
655
|
/>
|
|
656
|
+
<ValueField
|
|
657
|
+
id="auto-continue-loop-repeat-text"
|
|
658
|
+
label={t('field.loopRepeatText')}
|
|
659
|
+
hint={t('field.loopRepeatTextHint')}
|
|
660
|
+
numeric
|
|
661
|
+
{...shared}
|
|
662
|
+
{...state.loopRepeatText}
|
|
663
|
+
onEdit={(text) => props.edit('loopRepeatText', text)}
|
|
664
|
+
onReset={() => props.resetField('loopRepeatText')}
|
|
665
|
+
/>
|
|
653
666
|
<ValueField
|
|
654
667
|
id="auto-continue-loop-text"
|
|
655
668
|
label={t('field.loopText')}
|
package/src/index.ts
CHANGED
|
@@ -64,6 +64,8 @@ export const AutoContinueSchema = z.object({
|
|
|
64
64
|
loopWindowMs: z.natural().min(1000).default(30000),
|
|
65
65
|
/** Consecutive short sentences trip the loop guard. */
|
|
66
66
|
loopShortCount: z.natural().min(2).default(12),
|
|
67
|
+
/** Consecutive identical short sentences trip the loop guard (strongest spinning signal). */
|
|
68
|
+
loopRepeatText: z.natural().min(2).default(4),
|
|
67
69
|
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
68
70
|
loopToolRepeat: z.natural().min(2).default(5),
|
|
69
71
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|