dsh-client-auto-continue 0.7.0 → 0.7.2
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 +11 -9
- package/README.zh.md +11 -9
- package/lib/client.js +67 -15
- package/lib/client.js.map +2 -2
- package/lib/index.js +6 -4
- package/lib/types/client/engine.d.ts +7 -4
- 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 +8 -4
- package/package.json +1 -1
- package/src/client/engine.ts +62 -15
- package/src/client/locales.ts +9 -5
- package/src/client/settings-card.tsx +19 -0
- package/src/index.ts +6 -4
package/lib/index.js
CHANGED
|
@@ -45,10 +45,12 @@ var AutoContinueSchema = z.object({
|
|
|
45
45
|
loopGuard: z.boolean().default(true),
|
|
46
46
|
/** A model message shorter than this many chars counts as a short sentence (loop signal). */
|
|
47
47
|
loopShortChars: z.natural().min(1).default(40),
|
|
48
|
-
/** Consecutive short sentences with no tool call in between trip the loop guard. */
|
|
49
|
-
|
|
50
|
-
/** Consecutive
|
|
51
|
-
|
|
48
|
+
/** Consecutive short sentences within this window (ms) with no tool call in between trip the loop guard. */
|
|
49
|
+
loopWindowMs: z.natural().min(1e3).default(3e4),
|
|
50
|
+
/** Consecutive short sentences trip the loop guard. */
|
|
51
|
+
loopShortCount: z.natural().min(2).default(12),
|
|
52
|
+
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
53
|
+
loopToolRepeat: z.natural().min(2).default(5),
|
|
52
54
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
53
55
|
loopText: z.string().default("(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)")
|
|
54
56
|
});
|
|
@@ -55,9 +55,11 @@ export interface AutoContinueSettings {
|
|
|
55
55
|
loopGuard?: boolean;
|
|
56
56
|
/** A model message shorter than this many chars counts as a "short sentence" (loop signal). */
|
|
57
57
|
loopShortChars?: number;
|
|
58
|
-
/** Consecutive short sentences with no tool call in between trip the loop guard. */
|
|
58
|
+
/** Consecutive short sentences within this window (ms) with no tool call in between trip the loop guard. */
|
|
59
|
+
loopWindowMs?: number;
|
|
60
|
+
/** Consecutive short sentences trip the loop guard. */
|
|
59
61
|
loopShortCount?: number;
|
|
60
|
-
/** Consecutive identical tool calls trip the loop guard. */
|
|
62
|
+
/** Consecutive identical tool calls with identical arguments AND identical results trip the loop guard. */
|
|
61
63
|
loopToolRepeat?: number;
|
|
62
64
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
63
65
|
loopText?: string;
|
|
@@ -186,8 +188,9 @@ export declare class AutoContinueRunner {
|
|
|
186
188
|
/** 从 assistant/message 事件提取纯文本。 */
|
|
187
189
|
private assistantText;
|
|
188
190
|
/**
|
|
189
|
-
* loop guard 信号 1(空转):
|
|
190
|
-
* 短句 = 模型消息文本短于 loopShortChars;
|
|
191
|
+
* loop guard 信号 1(空转): 时间窗内连续短句且期间无工具调用。
|
|
192
|
+
* 短句 = 模型消息文本短于 loopShortChars; 长句、工具调用、或短句间隔超过
|
|
193
|
+
* loopWindowMs(正常思考的短文本散布在长时间里)都会重置计数。
|
|
191
194
|
*/
|
|
192
195
|
private onAssistantMessage;
|
|
193
196
|
/** 两个循环信号的公共检查; 命中且本回合未打断过则打断。 */
|
|
@@ -56,6 +56,8 @@ export declare const zh: {
|
|
|
56
56
|
'field.loopGuardHint': string;
|
|
57
57
|
'field.loopShortChars': string;
|
|
58
58
|
'field.loopShortCharsHint': string;
|
|
59
|
+
'field.loopWindowMs': string;
|
|
60
|
+
'field.loopWindowMsHint': string;
|
|
59
61
|
'field.loopShortCount': string;
|
|
60
62
|
'field.loopShortCountHint': string;
|
|
61
63
|
'field.loopToolRepeat': string;
|
|
@@ -25,6 +25,7 @@ export interface AutoContinueSettingsCardState extends CardShell {
|
|
|
25
25
|
notify: CardFieldState;
|
|
26
26
|
loopGuard: CardFieldState;
|
|
27
27
|
loopShortChars: CardFieldState;
|
|
28
|
+
loopWindowMs: CardFieldState;
|
|
28
29
|
loopShortCount: CardFieldState;
|
|
29
30
|
loopToolRepeat: CardFieldState;
|
|
30
31
|
loopText: CardFieldState;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -51,9 +51,11 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
|
|
|
51
51
|
loopGuard: z<boolean, boolean>;
|
|
52
52
|
/** A model message shorter than this many chars counts as a short sentence (loop signal). */
|
|
53
53
|
loopShortChars: z<number, number>;
|
|
54
|
-
/** Consecutive short sentences with no tool call in between trip the loop guard. */
|
|
54
|
+
/** Consecutive short sentences within this window (ms) with no tool call in between trip the loop guard. */
|
|
55
|
+
loopWindowMs: z<number, number>;
|
|
56
|
+
/** Consecutive short sentences trip the loop guard. */
|
|
55
57
|
loopShortCount: z<number, number>;
|
|
56
|
-
/** Consecutive identical tool calls trip the loop guard. */
|
|
58
|
+
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
57
59
|
loopToolRepeat: z<number, number>;
|
|
58
60
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
59
61
|
loopText: z<string, string>;
|
|
@@ -100,9 +102,11 @@ export declare const AutoContinueSchema: z<Schemastery.ObjectS<{
|
|
|
100
102
|
loopGuard: z<boolean, boolean>;
|
|
101
103
|
/** A model message shorter than this many chars counts as a short sentence (loop signal). */
|
|
102
104
|
loopShortChars: z<number, number>;
|
|
103
|
-
/** Consecutive short sentences with no tool call in between trip the loop guard. */
|
|
105
|
+
/** Consecutive short sentences within this window (ms) with no tool call in between trip the loop guard. */
|
|
106
|
+
loopWindowMs: z<number, number>;
|
|
107
|
+
/** Consecutive short sentences trip the loop guard. */
|
|
104
108
|
loopShortCount: z<number, number>;
|
|
105
|
-
/** Consecutive identical tool calls trip the loop guard. */
|
|
109
|
+
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
106
110
|
loopToolRepeat: z<number, number>;
|
|
107
111
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
108
112
|
loopText: z<string, string>;
|
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.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/types/index.d.ts",
|
package/src/client/engine.ts
CHANGED
|
@@ -64,9 +64,11 @@ export interface AutoContinueSettings {
|
|
|
64
64
|
loopGuard?: boolean;
|
|
65
65
|
/** A model message shorter than this many chars counts as a "short sentence" (loop signal). */
|
|
66
66
|
loopShortChars?: number;
|
|
67
|
-
/** Consecutive short sentences with no tool call in between trip the loop guard. */
|
|
67
|
+
/** Consecutive short sentences within this window (ms) with no tool call in between trip the loop guard. */
|
|
68
|
+
loopWindowMs?: number;
|
|
69
|
+
/** Consecutive short sentences trip the loop guard. */
|
|
68
70
|
loopShortCount?: number;
|
|
69
|
-
/** Consecutive identical tool calls trip the loop guard. */
|
|
71
|
+
/** Consecutive identical tool calls with identical arguments AND identical results trip the loop guard. */
|
|
70
72
|
loopToolRepeat?: number;
|
|
71
73
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
72
74
|
loopText?: string;
|
|
@@ -98,8 +100,9 @@ export const DEFAULT_CONFIG: AutoContinueConfig = {
|
|
|
98
100
|
paused: false,
|
|
99
101
|
loopGuard: true,
|
|
100
102
|
loopShortChars: 40,
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
loopWindowMs: 30000,
|
|
104
|
+
loopShortCount: 12,
|
|
105
|
+
loopToolRepeat: 5,
|
|
103
106
|
loopText: '(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)',
|
|
104
107
|
};
|
|
105
108
|
|
|
@@ -152,6 +155,7 @@ export function resolveConfig(section: AutoContinueSettings | undefined): AutoCo
|
|
|
152
155
|
paused: booleanOr(value.paused, DEFAULT_CONFIG.paused),
|
|
153
156
|
loopGuard: booleanOr(value.loopGuard, DEFAULT_CONFIG.loopGuard),
|
|
154
157
|
loopShortChars: Math.max(1, numberOr(value.loopShortChars, DEFAULT_CONFIG.loopShortChars)),
|
|
158
|
+
loopWindowMs: Math.max(1000, numberOr(value.loopWindowMs, DEFAULT_CONFIG.loopWindowMs)),
|
|
155
159
|
loopShortCount: Math.max(2, numberOr(value.loopShortCount, DEFAULT_CONFIG.loopShortCount)),
|
|
156
160
|
loopToolRepeat: Math.max(2, numberOr(value.loopToolRepeat, DEFAULT_CONFIG.loopToolRepeat)),
|
|
157
161
|
loopText:
|
|
@@ -596,8 +600,24 @@ interface SessionState {
|
|
|
596
600
|
pendingRecoveryAt: number;
|
|
597
601
|
/** 当前连续短句数(loop guard 信号 1: 空转)。 */
|
|
598
602
|
shortRun: number;
|
|
599
|
-
/**
|
|
600
|
-
|
|
603
|
+
/** 最后一条短句的时间(时间窗判定用)。 */
|
|
604
|
+
lastShortAt: number;
|
|
605
|
+
/**
|
|
606
|
+
* 工具重复信号(loop guard 信号 2: 死循环)。
|
|
607
|
+
* 只有「同工具 + 同参数 + 同结果」的连续调用才累计; 参数或结果有变化视为有进展, 计数重置。
|
|
608
|
+
*/
|
|
609
|
+
toolRun:
|
|
610
|
+
| {
|
|
611
|
+
/** 工具名 + 参数(用于判定是否同一调用)。 */
|
|
612
|
+
key: string;
|
|
613
|
+
/** 连续相同调用数(结果确认后更新)。 */
|
|
614
|
+
count: number;
|
|
615
|
+
/** 上次该调用的结果摘要(比较用)。 */
|
|
616
|
+
lastResult: string | undefined;
|
|
617
|
+
/** 本次调用等待结果确认。 */
|
|
618
|
+
waiting: boolean;
|
|
619
|
+
}
|
|
620
|
+
| undefined;
|
|
601
621
|
/** 本回合已触发过 loop guard(防重复打断)。 */
|
|
602
622
|
loopFired: boolean;
|
|
603
623
|
/** 我们主动 cancel 过本回合(区分用户停止)。 */
|
|
@@ -620,6 +640,7 @@ const freshState = (): SessionState => ({
|
|
|
620
640
|
lastTurn: undefined,
|
|
621
641
|
pendingRecoveryAt: 0,
|
|
622
642
|
shortRun: 0,
|
|
643
|
+
lastShortAt: 0,
|
|
623
644
|
toolRun: undefined,
|
|
624
645
|
loopFired: false,
|
|
625
646
|
loopCancelled: false,
|
|
@@ -769,19 +790,35 @@ export class AutoContinueRunner {
|
|
|
769
790
|
const state = this.state(frame.sessionId);
|
|
770
791
|
state.lastTool = name;
|
|
771
792
|
state.lastToolResult = 'pending'; // 已发起, 尚未见结果
|
|
772
|
-
// loop guard 信号 2:
|
|
793
|
+
// loop guard 信号 2: 同工具+同参数才可能是循环; 参数变化 = 有进展
|
|
794
|
+
// (工具调用本身也重置短句信号)。计数在结果确认后才推进。
|
|
773
795
|
state.shortRun = 0;
|
|
774
|
-
|
|
775
|
-
|
|
796
|
+
const key = `${name}\n${frame.event.data.arguments}`;
|
|
797
|
+
if (state.toolRun?.key === key) {
|
|
798
|
+
state.toolRun.waiting = true; // 结果到达时与上次结果比较
|
|
776
799
|
} else {
|
|
777
|
-
state.toolRun = {
|
|
800
|
+
state.toolRun = { key, count: 1, lastResult: undefined, waiting: false };
|
|
778
801
|
}
|
|
779
|
-
this.checkLoop(frame.sessionId, state);
|
|
780
802
|
}
|
|
781
803
|
} else if (frame.event.type === 'tool/result') {
|
|
782
804
|
const state = this.state(frame.sessionId);
|
|
783
805
|
if (state.lastToolResult === 'pending') {
|
|
784
|
-
|
|
806
|
+
const facts = toolResultFacts(frame.event.data);
|
|
807
|
+
state.lastToolResult = facts;
|
|
808
|
+
// 结果确认: 与上次相同 → 计数推进; 不同 → 有进展, 重置
|
|
809
|
+
const run = state.toolRun;
|
|
810
|
+
if (run !== undefined && run.waiting) {
|
|
811
|
+
run.waiting = false;
|
|
812
|
+
if (run.lastResult !== undefined && run.lastResult === facts.excerpt) {
|
|
813
|
+
run.count += 1;
|
|
814
|
+
this.checkLoop(frame.sessionId, state);
|
|
815
|
+
} else {
|
|
816
|
+
run.lastResult = facts.excerpt;
|
|
817
|
+
run.count = 1;
|
|
818
|
+
}
|
|
819
|
+
} else if (run !== undefined && !run.waiting) {
|
|
820
|
+
run.lastResult = facts.excerpt;
|
|
821
|
+
}
|
|
785
822
|
}
|
|
786
823
|
} else if (frame.event.type === 'assistant/message') {
|
|
787
824
|
const state = this.state(frame.sessionId);
|
|
@@ -812,8 +849,9 @@ export class AutoContinueRunner {
|
|
|
812
849
|
}
|
|
813
850
|
|
|
814
851
|
/**
|
|
815
|
-
* loop guard 信号 1(空转):
|
|
816
|
-
* 短句 = 模型消息文本短于 loopShortChars;
|
|
852
|
+
* loop guard 信号 1(空转): 时间窗内连续短句且期间无工具调用。
|
|
853
|
+
* 短句 = 模型消息文本短于 loopShortChars; 长句、工具调用、或短句间隔超过
|
|
854
|
+
* loopWindowMs(正常思考的短文本散布在长时间里)都会重置计数。
|
|
817
855
|
*/
|
|
818
856
|
private onAssistantMessage(
|
|
819
857
|
sessionId: SessionId,
|
|
@@ -823,9 +861,15 @@ export class AutoContinueRunner {
|
|
|
823
861
|
if (!this.getConfig().loopGuard) return;
|
|
824
862
|
const text = this.assistantText(event);
|
|
825
863
|
if (text.trim().length < this.getConfig().loopShortChars) {
|
|
864
|
+
const now = Date.now();
|
|
865
|
+
if (now - state.lastShortAt > this.getConfig().loopWindowMs) {
|
|
866
|
+
state.shortRun = 0; // 超过时间窗: 上一次短句太久远, 不算连续
|
|
867
|
+
}
|
|
826
868
|
state.shortRun += 1;
|
|
869
|
+
state.lastShortAt = now;
|
|
827
870
|
} else {
|
|
828
871
|
state.shortRun = 0; // 长句 = 有实际输出, 重置
|
|
872
|
+
state.lastShortAt = 0;
|
|
829
873
|
}
|
|
830
874
|
this.checkLoop(sessionId, state);
|
|
831
875
|
}
|
|
@@ -840,7 +884,8 @@ export class AutoContinueRunner {
|
|
|
840
884
|
this.log(`检测到空转循环 ${sessionId}: 连续 ${state.shortRun} 条短句且无工具调用`);
|
|
841
885
|
void this.interruptLoop(sessionId, state);
|
|
842
886
|
} else if (state.toolRun !== undefined && state.toolRun.count >= config.loopToolRepeat) {
|
|
843
|
-
|
|
887
|
+
const toolName = state.toolRun.key.split('\n')[0] ?? '?';
|
|
888
|
+
this.log(`检测到工具死循环 ${sessionId}: 「${toolName}」连续 ${state.toolRun.count} 次(同参数同结果)`);
|
|
844
889
|
void this.interruptLoop(sessionId, state);
|
|
845
890
|
}
|
|
846
891
|
}
|
|
@@ -882,6 +927,7 @@ export class AutoContinueRunner {
|
|
|
882
927
|
state.lastToolResult = undefined;
|
|
883
928
|
// loop guard 状态按回合重置
|
|
884
929
|
state.shortRun = 0;
|
|
930
|
+
state.lastShortAt = 0;
|
|
885
931
|
state.toolRun = undefined;
|
|
886
932
|
state.loopFired = false;
|
|
887
933
|
state.loopCancelled = false;
|
|
@@ -905,6 +951,7 @@ export class AutoContinueRunner {
|
|
|
905
951
|
state.consecutive = 0;
|
|
906
952
|
state.pendingRecoveryAt = 0;
|
|
907
953
|
state.shortRun = 0;
|
|
954
|
+
state.lastShortAt = 0;
|
|
908
955
|
state.toolRun = undefined;
|
|
909
956
|
state.lastAttemptAt = 0;
|
|
910
957
|
this.schedule(sessionId, 'loop:aborted');
|
package/src/client/locales.ts
CHANGED
|
@@ -57,10 +57,12 @@ export const zh = {
|
|
|
57
57
|
'field.loopGuardHint': '检测运行中的回合空转: 连续短句且无工具调用, 或连续调用相同工具时, 自动取消并用循环提示文本重启回合。',
|
|
58
58
|
'field.loopShortChars': '短句长度上限 (字符)',
|
|
59
59
|
'field.loopShortCharsHint': '模型消息文本短于该值计为一条短句(空转信号)。',
|
|
60
|
+
'field.loopWindowMs': '短句时间窗 (ms)',
|
|
61
|
+
'field.loopWindowMsHint': '连续短句必须落在这个时间窗内; 正常思考的短文本散布在长时间里不会被误判。',
|
|
60
62
|
'field.loopShortCount': '连续短句阈值',
|
|
61
|
-
'field.loopShortCountHint': '
|
|
62
|
-
'field.loopToolRepeat': '
|
|
63
|
-
'field.loopToolRepeatHint': '
|
|
63
|
+
'field.loopShortCountHint': '时间窗内连续多少条短句且期间无工具调用时判定空转循环。',
|
|
64
|
+
'field.loopToolRepeat': '同工具重复次数',
|
|
65
|
+
'field.loopToolRepeatHint': '同工具+同参数+同结果的连续调用多少次时判定死循环; 参数或结果有变化视为有进展。',
|
|
64
66
|
'field.loopText': '循环提示文本',
|
|
65
67
|
'field.loopTextHint': '打断后重启回合时发送的文本, 支持 {tool} 占位符。',
|
|
66
68
|
'stats.byCode': '按错误码统计',
|
|
@@ -143,10 +145,12 @@ export const en: Record<SettingsCardKey, string> = {
|
|
|
143
145
|
'field.loopGuardHint': 'Detects a running turn spinning in place — many short sentences with no tool calls, or the same tool repeating — cancels it and restarts with the loop text.',
|
|
144
146
|
'field.loopShortChars': 'Short-sentence max (chars)',
|
|
145
147
|
'field.loopShortCharsHint': 'A model message shorter than this counts as a short sentence (spinning signal).',
|
|
148
|
+
'field.loopWindowMs': 'Short-sentence window (ms)',
|
|
149
|
+
'field.loopWindowMsHint': 'Consecutive short sentences must land inside this window; normal thinking spread over time is not misjudged.',
|
|
146
150
|
'field.loopShortCount': 'Short-sentence threshold',
|
|
147
|
-
'field.loopShortCountHint': 'How many consecutive short sentences with no tool call trip the loop guard.',
|
|
151
|
+
'field.loopShortCountHint': 'How many consecutive short sentences inside the window, with no tool call, trip the loop guard.',
|
|
148
152
|
'field.loopToolRepeat': 'Same-tool repeat count',
|
|
149
|
-
'field.loopToolRepeatHint': 'How many consecutive
|
|
153
|
+
'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
154
|
'field.loopText': 'Loop text',
|
|
151
155
|
'field.loopTextHint': 'Text sent after the loop guard restarts a turn; supports the {tool} placeholder.',
|
|
152
156
|
'stats.byCode': 'By error code',
|
|
@@ -11,6 +11,7 @@ import { useEffect, useState, type ReactNode } from 'react';
|
|
|
11
11
|
import { createSnapshotStore, type SettingsScope, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client';
|
|
12
12
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
13
13
|
import {
|
|
14
|
+
DEFAULT_CONFIG,
|
|
14
15
|
pausedSessions,
|
|
15
16
|
readTodayStats,
|
|
16
17
|
resetTodayStats,
|
|
@@ -56,6 +57,7 @@ export interface AutoContinueSettingsCardState extends CardShell {
|
|
|
56
57
|
notify: CardFieldState;
|
|
57
58
|
loopGuard: CardFieldState;
|
|
58
59
|
loopShortChars: CardFieldState;
|
|
60
|
+
loopWindowMs: CardFieldState;
|
|
59
61
|
loopShortCount: CardFieldState;
|
|
60
62
|
loopToolRepeat: CardFieldState;
|
|
61
63
|
loopText: CardFieldState;
|
|
@@ -100,6 +102,7 @@ export class AutoContinueSettingsCardController {
|
|
|
100
102
|
booleanField('notify'),
|
|
101
103
|
booleanField('loopGuard'),
|
|
102
104
|
numberField('loopShortChars', 1),
|
|
105
|
+
numberField('loopWindowMs', 1000),
|
|
103
106
|
numberField('loopShortCount', 2),
|
|
104
107
|
numberField('loopToolRepeat', 2),
|
|
105
108
|
textField('loopText'),
|
|
@@ -131,6 +134,7 @@ export class AutoContinueSettingsCardController {
|
|
|
131
134
|
notify: this.form.field('notify'),
|
|
132
135
|
loopGuard: this.form.field('loopGuard'),
|
|
133
136
|
loopShortChars: this.form.field('loopShortChars'),
|
|
137
|
+
loopWindowMs: this.form.field('loopWindowMs'),
|
|
134
138
|
loopShortCount: this.form.field('loopShortCount'),
|
|
135
139
|
loopToolRepeat: this.form.field('loopToolRepeat'),
|
|
136
140
|
loopText: this.form.field('loopText'),
|
|
@@ -429,6 +433,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
|
|
|
429
433
|
{...shared}
|
|
430
434
|
{...state.continueText}
|
|
431
435
|
onEdit={(text) => props.edit('continueText', text)}
|
|
436
|
+
placeholder={DEFAULT_CONFIG.continueText}
|
|
432
437
|
onReset={() => props.resetField('continueText')}
|
|
433
438
|
/>
|
|
434
439
|
<ValueField
|
|
@@ -438,6 +443,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
|
|
|
438
443
|
{...shared}
|
|
439
444
|
{...state.continueTextMaxTokens}
|
|
440
445
|
onEdit={(text) => props.edit('continueTextMaxTokens', text)}
|
|
446
|
+
placeholder={DEFAULT_CONFIG.continueTextMaxTokens}
|
|
441
447
|
onReset={() => props.resetField('continueTextMaxTokens')}
|
|
442
448
|
/>
|
|
443
449
|
<BooleanField
|
|
@@ -456,6 +462,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
|
|
|
456
462
|
{...shared}
|
|
457
463
|
{...state.guardPendingText}
|
|
458
464
|
onEdit={(text) => props.edit('guardPendingText', text)}
|
|
465
|
+
placeholder={DEFAULT_CONFIG.guardPendingText}
|
|
459
466
|
onReset={() => props.resetField('guardPendingText')}
|
|
460
467
|
/>
|
|
461
468
|
<ValueField
|
|
@@ -465,6 +472,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
|
|
|
465
472
|
{...shared}
|
|
466
473
|
{...state.guardDoneText}
|
|
467
474
|
onEdit={(text) => props.edit('guardDoneText', text)}
|
|
475
|
+
placeholder={DEFAULT_CONFIG.guardDoneText}
|
|
468
476
|
onReset={() => props.resetField('guardDoneText')}
|
|
469
477
|
/>
|
|
470
478
|
<ValueField
|
|
@@ -612,6 +620,16 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
|
|
|
612
620
|
onEdit={(text) => props.edit('loopShortChars', text)}
|
|
613
621
|
onReset={() => props.resetField('loopShortChars')}
|
|
614
622
|
/>
|
|
623
|
+
<ValueField
|
|
624
|
+
id="auto-continue-loop-window-ms"
|
|
625
|
+
label={t('field.loopWindowMs')}
|
|
626
|
+
hint={t('field.loopWindowMsHint')}
|
|
627
|
+
numeric
|
|
628
|
+
{...shared}
|
|
629
|
+
{...state.loopWindowMs}
|
|
630
|
+
onEdit={(text) => props.edit('loopWindowMs', text)}
|
|
631
|
+
onReset={() => props.resetField('loopWindowMs')}
|
|
632
|
+
/>
|
|
615
633
|
<ValueField
|
|
616
634
|
id="auto-continue-loop-short-count"
|
|
617
635
|
label={t('field.loopShortCount')}
|
|
@@ -639,6 +657,7 @@ export function AutoContinueSettingsCard(props: AutoContinueSettingsCardProps) {
|
|
|
639
657
|
{...shared}
|
|
640
658
|
{...state.loopText}
|
|
641
659
|
onEdit={(text) => props.edit('loopText', text)}
|
|
660
|
+
placeholder={DEFAULT_CONFIG.loopText}
|
|
642
661
|
onReset={() => props.resetField('loopText')}
|
|
643
662
|
/>
|
|
644
663
|
<LivePanels t={t} />
|
package/src/index.ts
CHANGED
|
@@ -60,10 +60,12 @@ export const AutoContinueSchema = z.object({
|
|
|
60
60
|
loopGuard: z.boolean().default(true),
|
|
61
61
|
/** A model message shorter than this many chars counts as a short sentence (loop signal). */
|
|
62
62
|
loopShortChars: z.natural().min(1).default(40),
|
|
63
|
-
/** Consecutive short sentences with no tool call in between trip the loop guard. */
|
|
64
|
-
|
|
65
|
-
/** Consecutive
|
|
66
|
-
|
|
63
|
+
/** Consecutive short sentences within this window (ms) with no tool call in between trip the loop guard. */
|
|
64
|
+
loopWindowMs: z.natural().min(1000).default(30000),
|
|
65
|
+
/** Consecutive short sentences trip the loop guard. */
|
|
66
|
+
loopShortCount: z.natural().min(2).default(12),
|
|
67
|
+
/** Consecutive identical tool calls with identical arguments AND results trip the loop guard. */
|
|
68
|
+
loopToolRepeat: z.natural().min(2).default(5),
|
|
67
69
|
/** Text sent after the loop guard cancels and restarts a turn (supports {tool}). */
|
|
68
70
|
loopText: z
|
|
69
71
|
.string()
|