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/README.md
CHANGED
|
@@ -43,7 +43,7 @@ For [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh we
|
|
|
43
43
|
- **Idempotency guard** — before resuming, the plugin inspects the last tool call: if its result is unconfirmed (the turn died mid-tool, e.g. a `git push` that may have gone through), the resume message tells the model to check state first and not to rerun; if the tool is confirmed done, it says so and asks not to repeat it; a failed tool gets no guard (retrying it is the point). Both guard texts are configurable (`{tool}` / `{result}` placeholders)
|
|
44
44
|
- **Pause** — a global **Pause auto-continue** toggle in the settings card stops everything (live + scan) instantly; per-session pauses (e.g. via a notification button) suspend only one session until they expire. The **Resume now** notification button is the one explicit exception: pressing it is the user asking for exactly one send, pause or not
|
|
45
45
|
- **Notification buttons** — notifications carry **Resume now** (send immediately, ignoring cooldown, the consecutive cap and any pause) and **Pause this session 1h** actions
|
|
46
|
-
- **Loop guard** — watches **running** turns too: many
|
|
46
|
+
- **Loop guard** — watches **running** turns too: many short model messages inside a short time window with no tool call in between (the "Let me read…" spin), or the same tool called repeatedly with the **same arguments and the same results** (a changed argument or result counts as progress), trips the guard, which cancels the turn and restarts it with a configurable loop text ("stop repeating, try another way"). The cancel carries an internal marker so it is never confused with a user stop — the restart only happens for guard-initiated cancels. Thresholds, the time window and the loop text are configurable
|
|
47
47
|
- **Stats panel** — the settings card shows today's auto-continue count, recoveries, failures, permanent skips, give-ups and loop breaks, broken down by error code, with a one-click reset
|
|
48
48
|
- **Browser notifications** — optional alerts when auto-continue fires, gives up, or hits a permanent error; the browser asks for permission on first use, and nothing is shown again after a denial
|
|
49
49
|
|
|
@@ -175,8 +175,9 @@ auto-continue:
|
|
|
175
175
|
notify: false
|
|
176
176
|
loopGuard: true
|
|
177
177
|
loopShortChars: 40
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
loopWindowMs: 30000
|
|
179
|
+
loopShortCount: 12
|
|
180
|
+
loopToolRepeat: 5
|
|
180
181
|
loopText: '(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)'
|
|
181
182
|
```
|
|
182
183
|
|
|
@@ -199,11 +200,12 @@ auto-continue:
|
|
|
199
200
|
| Idempotency guard | `on` | Inspect the last tool call before resuming and steer the model (see What It Does) |
|
|
200
201
|
| Loop guard | `on` | Detect a running turn spinning in place and restart it (see What It Does) |
|
|
201
202
|
| Short-sentence max (chars) | `40` | A model message shorter than this counts as a short sentence (spinning signal) |
|
|
202
|
-
| Short-sentence
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
| Guard text (
|
|
203
|
+
| Short-sentence window (ms) | `30000` | Consecutive short sentences must land inside this window; normal thinking spread over time is not misjudged |
|
|
204
|
+
| Short-sentence threshold | `12` | Consecutive short sentences inside the window, with no tool call in between, trip the loop guard |
|
|
205
|
+
| Same-tool repeat count | `5` | Consecutive calls of the same tool with identical arguments and results trip the loop guard |
|
|
206
|
+
| Loop text | `(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)` | Text sent after the loop guard restarts a turn; `{tool}` placeholder |
|
|
207
|
+
| Guard text (unconfirmed result) | `(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)` | Appended when the last tool may have partially executed; `{tool}` placeholder |
|
|
208
|
+
| Guard text (tool succeeded) | `(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)` | Appended when the last tool is confirmed done; `{tool}` / `{result}` placeholders |
|
|
207
209
|
| Grace period (ms) | `3000` | Wait after an interruption; cancelled if the host recovers on its own |
|
|
208
210
|
| Cooldown (ms) | `20000` | Min interval between auto-continues per session (failed attempts count too) |
|
|
209
211
|
| Max consecutive | `3` | Max consecutive auto-continues; stops until a user intervenes or a turn completes |
|
|
@@ -239,7 +241,7 @@ The plugin is browser-only and touches **no files, credentials, or network beyon
|
|
|
239
241
|
npm run typecheck # tsc --noEmit
|
|
240
242
|
npm run build # lib/client.js + lib/index.js + lib/types
|
|
241
243
|
npm run watch # rebuild on change; host HMR hot-reloads without a page refresh
|
|
242
|
-
npm run test # node tests/simulate.mjs —
|
|
244
|
+
npm run test # node tests/simulate.mjs — 36 behavioral scenarios
|
|
243
245
|
```
|
|
244
246
|
|
|
245
247
|
While `npm run watch` runs, the profile's client-hmr row polls `lib/client.js` every 500 ms and hot-reloads the plugin in the browser — no server restart needed for code changes.
|
package/README.zh.md
CHANGED
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
- **幂等护栏** — 续跑前检查上一步工具调用: 结果未确认(回合在工具执行中途夭折, 如 `git push` 可能已经推上去了)时, 续跑消息会提示模型先确认状态、不要重复执行; 工具已确认成功时说明已完成、请勿重复; 工具失败则不加护栏(重试本来就是目的)。两段护栏文本都可配置(支持 `{tool}` / `{result}` 占位符)
|
|
44
44
|
- **暂停** — 设置卡片里的全局 **暂停自动继续** 开关可立即停掉一切(实时 + 扫描); 会话级暂停(如通过通知按钮)只挂起单个会话, 到期自动恢复。唯一的显式例外是通知里的 **立即续跑** 按钮——按下它等于用户明确要求发送这一次, 不受暂停限制
|
|
45
45
|
- **通知按钮** — 通知带 **立即续跑**(无视冷却、连续上限与暂停, 马上发送)和 **暂停该会话 1 小时** 按钮
|
|
46
|
-
- **循环守卫** — 连**运行中的回合**也盯着:
|
|
46
|
+
- **循环守卫** — 连**运行中的回合**也盯着: 短时间内连续多条短句且期间无工具调用(典型的「Let me read…」空转), 或同一工具被连续反复调用且**参数与结果都相同**(参数或结果有变化视为有进展), 都会触发守卫: 取消当前回合并用可配置的循环提示文本重启(「停止重复, 换一种方式」)。取消带有内部来源标记, 绝不会与用户手动停止混淆——只有守卫发起的取消才会重启。阈值、时间窗与提示文本都可配置
|
|
47
47
|
- **统计面板** — 设置卡片展示今日自动继续次数、恢复成功、继续后失败、永久性跳过、达上限停止、循环打断, 按错误码统计, 可一键清零
|
|
48
48
|
- **浏览器通知** — 可选: 自动继续成功 / 放弃 / 遇到永久性错误时弹出提醒; 首次使用时请求权限, 被拒绝后不再打扰
|
|
49
49
|
|
|
@@ -175,8 +175,9 @@ auto-continue:
|
|
|
175
175
|
notify: false
|
|
176
176
|
loopGuard: true
|
|
177
177
|
loopShortChars: 40
|
|
178
|
-
|
|
179
|
-
|
|
178
|
+
loopWindowMs: 30000
|
|
179
|
+
loopShortCount: 12
|
|
180
|
+
loopToolRepeat: 5
|
|
180
181
|
loopText: '(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)'
|
|
181
182
|
```
|
|
182
183
|
|
|
@@ -199,11 +200,12 @@ auto-continue:
|
|
|
199
200
|
| 幂等护栏 | 开 | 续跑前检查上一步工具调用并给出指引(见「它做什么」) |
|
|
200
201
|
| 循环守卫 | 开 | 检测运行中的回合空转并重启(见「它做什么」) |
|
|
201
202
|
| 短句长度上限 (字符) | `40` | 模型消息文本短于该值计为一条短句(空转信号) |
|
|
202
|
-
|
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
|
|
|
203
|
+
| 短句时间窗 (ms) | `30000` | 连续短句必须落在这个时间窗内; 正常思考的短文本散布在长时间里不会被误判 |
|
|
204
|
+
| 连续短句阈值 | `12` | 时间窗内连续多少条短句且期间无工具调用时判定空转循环 |
|
|
205
|
+
| 同工具重复次数 | `5` | 同工具+同参数+同结果的连续调用多少次时判定死循环 |
|
|
206
|
+
| 循环提示文本 | `(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)` | 打断后重启回合时发送的文本; 支持 {tool} 占位符 |
|
|
207
|
+
| 结果未确认时的护栏文本 | `(上一步工具「{tool}」可能未完成, 先确认状态再继续, 不要重复执行)` | 上一步工具可能已部分执行时附加; 支持 {tool} 占位符 |
|
|
208
|
+
| 工具已成功时的护栏文本 | `(上一步工具「{tool}」已完成, 结果: {result}; 不要重复执行, 直接继续)` | 上一步工具已确认成功时附加; 支持 {tool} / {result} 占位符 |
|
|
207
209
|
| 宽限期 (ms) | `3000` | 中断后等待的时长; 期间宿主自行恢复则取消 |
|
|
208
210
|
| 冷却时间 (ms) | `20000` | 同一会话两次自动「继续」的最小间隔(失败尝试也计入) |
|
|
209
211
|
| 最大连续次数 | `3` | 同一会话连续自动「继续」上限; 超过后停止, 直到用户介入或成功回合 |
|
|
@@ -239,7 +241,7 @@ auto-continue:
|
|
|
239
241
|
npm run typecheck # tsc --noEmit
|
|
240
242
|
npm run build # lib/client.js + lib/index.js + lib/types
|
|
241
243
|
npm run watch # 监听变更自动重建; 宿主 HMR 免刷新热重载
|
|
242
|
-
npm run test # node tests/simulate.mjs —
|
|
244
|
+
npm run test # node tests/simulate.mjs — 36 个行为场景
|
|
243
245
|
```
|
|
244
246
|
|
|
245
247
|
`npm run watch` 运行时, profile 的 client-hmr 行每 500ms 轮询 `lib/client.js` 并在浏览器中热重载插件——改代码无需重启服务。
|
package/lib/client.js
CHANGED
|
@@ -61,8 +61,9 @@ var DEFAULT_CONFIG = {
|
|
|
61
61
|
paused: false,
|
|
62
62
|
loopGuard: true,
|
|
63
63
|
loopShortChars: 40,
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
loopWindowMs: 3e4,
|
|
65
|
+
loopShortCount: 12,
|
|
66
|
+
loopToolRepeat: 5,
|
|
66
67
|
loopText: "(检测到你可能陷入循环, 请停止重复刚才的动作, 换一种方式继续)"
|
|
67
68
|
};
|
|
68
69
|
function numberOr(value, fallback) {
|
|
@@ -99,6 +100,7 @@ function resolveConfig(section) {
|
|
|
99
100
|
paused: booleanOr(value.paused, DEFAULT_CONFIG.paused),
|
|
100
101
|
loopGuard: booleanOr(value.loopGuard, DEFAULT_CONFIG.loopGuard),
|
|
101
102
|
loopShortChars: Math.max(1, numberOr(value.loopShortChars, DEFAULT_CONFIG.loopShortChars)),
|
|
103
|
+
loopWindowMs: Math.max(1e3, numberOr(value.loopWindowMs, DEFAULT_CONFIG.loopWindowMs)),
|
|
102
104
|
loopShortCount: Math.max(2, numberOr(value.loopShortCount, DEFAULT_CONFIG.loopShortCount)),
|
|
103
105
|
loopToolRepeat: Math.max(2, numberOr(value.loopToolRepeat, DEFAULT_CONFIG.loopToolRepeat)),
|
|
104
106
|
loopText: typeof value.loopText === "string" && value.loopText.trim() !== "" ? value.loopText : DEFAULT_CONFIG.loopText
|
|
@@ -331,6 +333,7 @@ var freshState = () => ({
|
|
|
331
333
|
lastTurn: void 0,
|
|
332
334
|
pendingRecoveryAt: 0,
|
|
333
335
|
shortRun: 0,
|
|
336
|
+
lastShortAt: 0,
|
|
334
337
|
toolRun: void 0,
|
|
335
338
|
loopFired: false,
|
|
336
339
|
loopCancelled: false
|
|
@@ -445,17 +448,32 @@ var AutoContinueRunner = class {
|
|
|
445
448
|
state.lastTool = name;
|
|
446
449
|
state.lastToolResult = "pending";
|
|
447
450
|
state.shortRun = 0;
|
|
448
|
-
|
|
449
|
-
|
|
451
|
+
const key = `${name}
|
|
452
|
+
${frame.event.data.arguments}`;
|
|
453
|
+
if (state.toolRun?.key === key) {
|
|
454
|
+
state.toolRun.waiting = true;
|
|
450
455
|
} else {
|
|
451
|
-
state.toolRun = {
|
|
456
|
+
state.toolRun = { key, count: 1, lastResult: void 0, waiting: false };
|
|
452
457
|
}
|
|
453
|
-
this.checkLoop(frame.sessionId, state);
|
|
454
458
|
}
|
|
455
459
|
} else if (frame.event.type === "tool/result") {
|
|
456
460
|
const state = this.state(frame.sessionId);
|
|
457
461
|
if (state.lastToolResult === "pending") {
|
|
458
|
-
|
|
462
|
+
const facts = toolResultFacts(frame.event.data);
|
|
463
|
+
state.lastToolResult = facts;
|
|
464
|
+
const run = state.toolRun;
|
|
465
|
+
if (run !== void 0 && run.waiting) {
|
|
466
|
+
run.waiting = false;
|
|
467
|
+
if (run.lastResult !== void 0 && run.lastResult === facts.excerpt) {
|
|
468
|
+
run.count += 1;
|
|
469
|
+
this.checkLoop(frame.sessionId, state);
|
|
470
|
+
} else {
|
|
471
|
+
run.lastResult = facts.excerpt;
|
|
472
|
+
run.count = 1;
|
|
473
|
+
}
|
|
474
|
+
} else if (run !== void 0 && !run.waiting) {
|
|
475
|
+
run.lastResult = facts.excerpt;
|
|
476
|
+
}
|
|
459
477
|
}
|
|
460
478
|
} else if (frame.event.type === "assistant/message") {
|
|
461
479
|
const state = this.state(frame.sessionId);
|
|
@@ -481,16 +499,23 @@ var AutoContinueRunner = class {
|
|
|
481
499
|
return content.filter((part) => part.type === "text").map((part) => part.text).join("");
|
|
482
500
|
}
|
|
483
501
|
/**
|
|
484
|
-
* loop guard 信号 1(空转):
|
|
485
|
-
* 短句 = 模型消息文本短于 loopShortChars;
|
|
502
|
+
* loop guard 信号 1(空转): 时间窗内连续短句且期间无工具调用。
|
|
503
|
+
* 短句 = 模型消息文本短于 loopShortChars; 长句、工具调用、或短句间隔超过
|
|
504
|
+
* loopWindowMs(正常思考的短文本散布在长时间里)都会重置计数。
|
|
486
505
|
*/
|
|
487
506
|
onAssistantMessage(sessionId, state, event) {
|
|
488
507
|
if (!this.getConfig().loopGuard) return;
|
|
489
508
|
const text = this.assistantText(event);
|
|
490
509
|
if (text.trim().length < this.getConfig().loopShortChars) {
|
|
510
|
+
const now = Date.now();
|
|
511
|
+
if (now - state.lastShortAt > this.getConfig().loopWindowMs) {
|
|
512
|
+
state.shortRun = 0;
|
|
513
|
+
}
|
|
491
514
|
state.shortRun += 1;
|
|
515
|
+
state.lastShortAt = now;
|
|
492
516
|
} else {
|
|
493
517
|
state.shortRun = 0;
|
|
518
|
+
state.lastShortAt = 0;
|
|
494
519
|
}
|
|
495
520
|
this.checkLoop(sessionId, state);
|
|
496
521
|
}
|
|
@@ -504,7 +529,8 @@ var AutoContinueRunner = class {
|
|
|
504
529
|
this.log(`检测到空转循环 ${sessionId}: 连续 ${state.shortRun} 条短句且无工具调用`);
|
|
505
530
|
void this.interruptLoop(sessionId, state);
|
|
506
531
|
} else if (state.toolRun !== void 0 && state.toolRun.count >= config.loopToolRepeat) {
|
|
507
|
-
|
|
532
|
+
const toolName = state.toolRun.key.split("\n")[0] ?? "?";
|
|
533
|
+
this.log(`检测到工具死循环 ${sessionId}: 「${toolName}」连续 ${state.toolRun.count} 次(同参数同结果)`);
|
|
508
534
|
void this.interruptLoop(sessionId, state);
|
|
509
535
|
}
|
|
510
536
|
}
|
|
@@ -541,6 +567,7 @@ var AutoContinueRunner = class {
|
|
|
541
567
|
state.lastTool = void 0;
|
|
542
568
|
state.lastToolResult = void 0;
|
|
543
569
|
state.shortRun = 0;
|
|
570
|
+
state.lastShortAt = 0;
|
|
544
571
|
state.toolRun = void 0;
|
|
545
572
|
state.loopFired = false;
|
|
546
573
|
state.loopCancelled = false;
|
|
@@ -561,6 +588,7 @@ var AutoContinueRunner = class {
|
|
|
561
588
|
state.consecutive = 0;
|
|
562
589
|
state.pendingRecoveryAt = 0;
|
|
563
590
|
state.shortRun = 0;
|
|
591
|
+
state.lastShortAt = 0;
|
|
564
592
|
state.toolRun = void 0;
|
|
565
593
|
state.lastAttemptAt = 0;
|
|
566
594
|
this.schedule(sessionId, "loop:aborted");
|
|
@@ -1050,10 +1078,12 @@ var zh = {
|
|
|
1050
1078
|
"field.loopGuardHint": "检测运行中的回合空转: 连续短句且无工具调用, 或连续调用相同工具时, 自动取消并用循环提示文本重启回合。",
|
|
1051
1079
|
"field.loopShortChars": "短句长度上限 (字符)",
|
|
1052
1080
|
"field.loopShortCharsHint": "模型消息文本短于该值计为一条短句(空转信号)。",
|
|
1081
|
+
"field.loopWindowMs": "短句时间窗 (ms)",
|
|
1082
|
+
"field.loopWindowMsHint": "连续短句必须落在这个时间窗内; 正常思考的短文本散布在长时间里不会被误判。",
|
|
1053
1083
|
"field.loopShortCount": "连续短句阈值",
|
|
1054
|
-
"field.loopShortCountHint": "
|
|
1055
|
-
"field.loopToolRepeat": "
|
|
1056
|
-
"field.loopToolRepeatHint": "
|
|
1084
|
+
"field.loopShortCountHint": "时间窗内连续多少条短句且期间无工具调用时判定空转循环。",
|
|
1085
|
+
"field.loopToolRepeat": "同工具重复次数",
|
|
1086
|
+
"field.loopToolRepeatHint": "同工具+同参数+同结果的连续调用多少次时判定死循环; 参数或结果有变化视为有进展。",
|
|
1057
1087
|
"field.loopText": "循环提示文本",
|
|
1058
1088
|
"field.loopTextHint": "打断后重启回合时发送的文本, 支持 {tool} 占位符。",
|
|
1059
1089
|
"stats.byCode": "按错误码统计",
|
|
@@ -1131,10 +1161,12 @@ var en = {
|
|
|
1131
1161
|
"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.",
|
|
1132
1162
|
"field.loopShortChars": "Short-sentence max (chars)",
|
|
1133
1163
|
"field.loopShortCharsHint": "A model message shorter than this counts as a short sentence (spinning signal).",
|
|
1164
|
+
"field.loopWindowMs": "Short-sentence window (ms)",
|
|
1165
|
+
"field.loopWindowMsHint": "Consecutive short sentences must land inside this window; normal thinking spread over time is not misjudged.",
|
|
1134
1166
|
"field.loopShortCount": "Short-sentence threshold",
|
|
1135
|
-
"field.loopShortCountHint": "How many consecutive short sentences with no tool call trip the loop guard.",
|
|
1167
|
+
"field.loopShortCountHint": "How many consecutive short sentences inside the window, with no tool call, trip the loop guard.",
|
|
1136
1168
|
"field.loopToolRepeat": "Same-tool repeat count",
|
|
1137
|
-
"field.loopToolRepeatHint": "How many consecutive
|
|
1169
|
+
"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.",
|
|
1138
1170
|
"field.loopText": "Loop text",
|
|
1139
1171
|
"field.loopTextHint": "Text sent after the loop guard restarts a turn; supports the {tool} placeholder.",
|
|
1140
1172
|
"stats.byCode": "By error code",
|
|
@@ -1544,6 +1576,7 @@ var AutoContinueSettingsCardController = class {
|
|
|
1544
1576
|
booleanField("notify"),
|
|
1545
1577
|
booleanField("loopGuard"),
|
|
1546
1578
|
numberField("loopShortChars", 1),
|
|
1579
|
+
numberField("loopWindowMs", 1e3),
|
|
1547
1580
|
numberField("loopShortCount", 2),
|
|
1548
1581
|
numberField("loopToolRepeat", 2),
|
|
1549
1582
|
textField("loopText")
|
|
@@ -1574,6 +1607,7 @@ var AutoContinueSettingsCardController = class {
|
|
|
1574
1607
|
notify: this.form.field("notify"),
|
|
1575
1608
|
loopGuard: this.form.field("loopGuard"),
|
|
1576
1609
|
loopShortChars: this.form.field("loopShortChars"),
|
|
1610
|
+
loopWindowMs: this.form.field("loopWindowMs"),
|
|
1577
1611
|
loopShortCount: this.form.field("loopShortCount"),
|
|
1578
1612
|
loopToolRepeat: this.form.field("loopToolRepeat"),
|
|
1579
1613
|
loopText: this.form.field("loopText")
|
|
@@ -1832,6 +1866,7 @@ function AutoContinueSettingsCard(props) {
|
|
|
1832
1866
|
...shared,
|
|
1833
1867
|
...state.continueText,
|
|
1834
1868
|
onEdit: (text) => props.edit("continueText", text),
|
|
1869
|
+
placeholder: DEFAULT_CONFIG.continueText,
|
|
1835
1870
|
onReset: () => props.resetField("continueText")
|
|
1836
1871
|
}
|
|
1837
1872
|
),
|
|
@@ -1844,6 +1879,7 @@ function AutoContinueSettingsCard(props) {
|
|
|
1844
1879
|
...shared,
|
|
1845
1880
|
...state.continueTextMaxTokens,
|
|
1846
1881
|
onEdit: (text) => props.edit("continueTextMaxTokens", text),
|
|
1882
|
+
placeholder: DEFAULT_CONFIG.continueTextMaxTokens,
|
|
1847
1883
|
onReset: () => props.resetField("continueTextMaxTokens")
|
|
1848
1884
|
}
|
|
1849
1885
|
),
|
|
@@ -1868,6 +1904,7 @@ function AutoContinueSettingsCard(props) {
|
|
|
1868
1904
|
...shared,
|
|
1869
1905
|
...state.guardPendingText,
|
|
1870
1906
|
onEdit: (text) => props.edit("guardPendingText", text),
|
|
1907
|
+
placeholder: DEFAULT_CONFIG.guardPendingText,
|
|
1871
1908
|
onReset: () => props.resetField("guardPendingText")
|
|
1872
1909
|
}
|
|
1873
1910
|
),
|
|
@@ -1880,6 +1917,7 @@ function AutoContinueSettingsCard(props) {
|
|
|
1880
1917
|
...shared,
|
|
1881
1918
|
...state.guardDoneText,
|
|
1882
1919
|
onEdit: (text) => props.edit("guardDoneText", text),
|
|
1920
|
+
placeholder: DEFAULT_CONFIG.guardDoneText,
|
|
1883
1921
|
onReset: () => props.resetField("guardDoneText")
|
|
1884
1922
|
}
|
|
1885
1923
|
),
|
|
@@ -2073,6 +2111,19 @@ function AutoContinueSettingsCard(props) {
|
|
|
2073
2111
|
onReset: () => props.resetField("loopShortChars")
|
|
2074
2112
|
}
|
|
2075
2113
|
),
|
|
2114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2115
|
+
ValueField,
|
|
2116
|
+
{
|
|
2117
|
+
id: "auto-continue-loop-window-ms",
|
|
2118
|
+
label: t("field.loopWindowMs"),
|
|
2119
|
+
hint: t("field.loopWindowMsHint"),
|
|
2120
|
+
numeric: true,
|
|
2121
|
+
...shared,
|
|
2122
|
+
...state.loopWindowMs,
|
|
2123
|
+
onEdit: (text) => props.edit("loopWindowMs", text),
|
|
2124
|
+
onReset: () => props.resetField("loopWindowMs")
|
|
2125
|
+
}
|
|
2126
|
+
),
|
|
2076
2127
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
2077
2128
|
ValueField,
|
|
2078
2129
|
{
|
|
@@ -2108,6 +2159,7 @@ function AutoContinueSettingsCard(props) {
|
|
|
2108
2159
|
...shared,
|
|
2109
2160
|
...state.loopText,
|
|
2110
2161
|
onEdit: (text) => props.edit("loopText", text),
|
|
2162
|
+
placeholder: DEFAULT_CONFIG.loopText,
|
|
2111
2163
|
onReset: () => props.resetField("loopText")
|
|
2112
2164
|
}
|
|
2113
2165
|
),
|