dsh-client-auto-continue 0.10.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 +1 -0
- package/README.zh.md +1 -0
- package/lib/index.js +29 -4
- package/lib/types/host/engine.d.ts +1 -0
- package/package.json +1 -1
- package/src/host/engine.ts +22 -4
- package/src/index.ts +16 -0
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
<a href="https://github.com/HsiangNianian/dsh-auto-continue/stargazers"><img src="https://img.shields.io/github/stars/HsiangNianian/dsh-auto-continue?logo=github&label=Stars" alt="GitHub stars"></a>
|
|
18
18
|
<a href="https://github.com/HsiangNianian/dsh-auto-continue/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-65a30d?style=flat" alt="MIT license"></a>
|
|
19
19
|
<a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin"></a>
|
|
20
|
+
<a href="https://www.dsh.so/artifact/dsh-auto-continue/"><img src="https://www.dsh.so/badge/install/dsh-auto-continue.svg" alt="dsh.so install"></a>
|
|
20
21
|
<br>
|
|
21
22
|
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=fff" alt="TypeScript">
|
|
22
23
|
<img src="https://img.shields.io/badge/esbuild-FFCF00?style=flat&logo=esbuild&logoColor=000" alt="esbuild">
|
package/README.zh.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
<a href="https://github.com/HsiangNianian/dsh-auto-continue/stargazers"><img src="https://img.shields.io/github/stars/HsiangNianian/dsh-auto-continue?logo=github&label=Stars" alt="GitHub stars"></a>
|
|
18
18
|
<a href="https://github.com/HsiangNianian/dsh-auto-continue/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-65a30d?style=flat" alt="MIT license"></a>
|
|
19
19
|
<a href="https://awesome-dsh-plugin.com"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome · DSH plugin"></a>
|
|
20
|
+
<a href="https://www.dsh.so/artifact/dsh-auto-continue/"><img src="https://www.dsh.so/badge/install/dsh-auto-continue.svg" alt="dsh.so install"></a>
|
|
20
21
|
<br>
|
|
21
22
|
<img src="https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=fff" alt="TypeScript">
|
|
22
23
|
<img src="https://img.shields.io/badge/esbuild-FFCF00?style=flat&logo=esbuild&logoColor=000" alt="esbuild">
|
package/lib/index.js
CHANGED
|
@@ -317,7 +317,10 @@ var AutoContinueRunner = class {
|
|
|
317
317
|
this.noticeListeners = /* @__PURE__ */ new Set();
|
|
318
318
|
this.stateListeners = /* @__PURE__ */ new Set();
|
|
319
319
|
this.disposed = false;
|
|
320
|
-
|
|
320
|
+
this.disposeSessionEvents = ctx.on(
|
|
321
|
+
"session/event",
|
|
322
|
+
(session, event) => this.onHostEvent(session, event)
|
|
323
|
+
);
|
|
321
324
|
const config = this.getConfig();
|
|
322
325
|
if (config.scanOnBoot) {
|
|
323
326
|
void this.bootScanLoop();
|
|
@@ -379,7 +382,9 @@ var AutoContinueRunner = class {
|
|
|
379
382
|
this.emitState();
|
|
380
383
|
}
|
|
381
384
|
dispose() {
|
|
385
|
+
if (this.disposed) return;
|
|
382
386
|
this.disposed = true;
|
|
387
|
+
this.disposeSessionEvents();
|
|
383
388
|
for (const state of this.states.values()) {
|
|
384
389
|
if (state.pendingTimer !== void 0) clearTimeout(state.pendingTimer);
|
|
385
390
|
if (state.loopRetryTimer !== void 0) clearTimeout(state.loopRetryTimer);
|
|
@@ -560,7 +565,11 @@ ${event.data.arguments}`;
|
|
|
560
565
|
if (state.loopRetryTimer !== void 0) clearTimeout(state.loopRetryTimer);
|
|
561
566
|
state.loopRetryTimer = setTimeout(() => {
|
|
562
567
|
state.loopRetryTimer = void 0;
|
|
563
|
-
|
|
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
|
+
}
|
|
564
573
|
}, remaining);
|
|
565
574
|
this.log(`loop 重启延迟 ${remaining}ms(冷却期) ${sessionId}`);
|
|
566
575
|
} else {
|
|
@@ -691,7 +700,11 @@ ${event.data.arguments}`;
|
|
|
691
700
|
clearTimeout(state.pendingTimer);
|
|
692
701
|
state.pendingTimer = void 0;
|
|
693
702
|
}
|
|
694
|
-
|
|
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
|
+
}
|
|
695
708
|
}
|
|
696
709
|
/** 本会话当前生效的冷却间隔(自适应退避)。 */
|
|
697
710
|
cooldownFor(state) {
|
|
@@ -726,7 +739,11 @@ ${event.data.arguments}`;
|
|
|
726
739
|
const timer = setTimeout(() => {
|
|
727
740
|
if (state.pendingTimer !== timer) return;
|
|
728
741
|
state.pendingTimer = void 0;
|
|
729
|
-
|
|
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
|
+
}
|
|
730
747
|
}, config.graceMs);
|
|
731
748
|
state.pendingTimer = timer;
|
|
732
749
|
const template = reason.startsWith("loop:") ? config.loopText : reason.includes("max-tokens") ? config.continueTextMaxTokens : config.continueText;
|
|
@@ -992,11 +1009,14 @@ function apply(ctx) {
|
|
|
992
1009
|
applies: "live"
|
|
993
1010
|
});
|
|
994
1011
|
});
|
|
1012
|
+
let runnerRef;
|
|
995
1013
|
ctx.inject(["settings", "agents", "webServer"], (engineCtx) => {
|
|
1014
|
+
if (runnerRef !== void 0) runnerRef.dispose();
|
|
996
1015
|
const runner = new AutoContinueRunner(
|
|
997
1016
|
engineCtx,
|
|
998
1017
|
() => resolveConfig(engineCtx.settings.get(settingsNamespace(AUTO_CONTINUE_NS)))
|
|
999
1018
|
);
|
|
1019
|
+
runnerRef = runner;
|
|
1000
1020
|
const sseClients = /* @__PURE__ */ new Set();
|
|
1001
1021
|
const pushToAll = (data) => {
|
|
1002
1022
|
for (const send of sseClients) {
|
|
@@ -1071,6 +1091,11 @@ function apply(ctx) {
|
|
|
1071
1091
|
}
|
|
1072
1092
|
});
|
|
1073
1093
|
});
|
|
1094
|
+
ctx.effect(() => () => {
|
|
1095
|
+
const runner = runnerRef;
|
|
1096
|
+
runnerRef = void 0;
|
|
1097
|
+
if (runner !== void 0) runner.dispose();
|
|
1098
|
+
});
|
|
1074
1099
|
}
|
|
1075
1100
|
export {
|
|
1076
1101
|
AUTO_CONTINUE_NS,
|
|
@@ -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/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 a localized continue prompt when a request is interrupted by network errors or other non-human causes",
|
|
4
|
-
"version": "0.10.
|
|
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/host/engine.ts
CHANGED
|
@@ -143,6 +143,7 @@ export class AutoContinueRunner {
|
|
|
143
143
|
private readonly notices: HostNotice[] = [];
|
|
144
144
|
private readonly noticeListeners = new Set<() => void>();
|
|
145
145
|
private readonly stateListeners = new Set<() => void>();
|
|
146
|
+
private readonly disposeSessionEvents: () => void;
|
|
146
147
|
private disposed = false;
|
|
147
148
|
|
|
148
149
|
/**
|
|
@@ -154,7 +155,9 @@ export class AutoContinueRunner {
|
|
|
154
155
|
private readonly getConfig: () => AutoContinueConfig,
|
|
155
156
|
) {
|
|
156
157
|
// 单实例事件源: 宿主进程内的会话事件 firehose, 天然覆盖所有会话。
|
|
157
|
-
ctx.on('session/event', (session, event) =>
|
|
158
|
+
this.disposeSessionEvents = ctx.on('session/event', (session, event) =>
|
|
159
|
+
this.onHostEvent(session, event),
|
|
160
|
+
);
|
|
158
161
|
const config = this.getConfig();
|
|
159
162
|
if (config.scanOnBoot) {
|
|
160
163
|
void this.bootScanLoop();
|
|
@@ -226,7 +229,9 @@ export class AutoContinueRunner {
|
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
dispose(): void {
|
|
232
|
+
if (this.disposed) return;
|
|
229
233
|
this.disposed = true;
|
|
234
|
+
this.disposeSessionEvents();
|
|
230
235
|
for (const state of this.states.values()) {
|
|
231
236
|
if (state.pendingTimer !== undefined) clearTimeout(state.pendingTimer);
|
|
232
237
|
if (state.loopRetryTimer !== undefined) clearTimeout(state.loopRetryTimer);
|
|
@@ -434,7 +439,11 @@ export class AutoContinueRunner {
|
|
|
434
439
|
if (state.loopRetryTimer !== undefined) clearTimeout(state.loopRetryTimer);
|
|
435
440
|
state.loopRetryTimer = setTimeout(() => {
|
|
436
441
|
state.loopRetryTimer = undefined;
|
|
437
|
-
|
|
442
|
+
try {
|
|
443
|
+
this.schedule(sessionId, 'loop:aborted');
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error(`[auto-continue] loop 重启异常 ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
446
|
+
}
|
|
438
447
|
}, remaining);
|
|
439
448
|
this.log(`loop 重启延迟 ${remaining}ms(冷却期) ${sessionId}`);
|
|
440
449
|
} else {
|
|
@@ -591,7 +600,11 @@ export class AutoContinueRunner {
|
|
|
591
600
|
clearTimeout(state.pendingTimer);
|
|
592
601
|
state.pendingTimer = undefined;
|
|
593
602
|
}
|
|
594
|
-
|
|
603
|
+
try {
|
|
604
|
+
await this.fire(sessionId, 'manual:notification', true);
|
|
605
|
+
} catch (error) {
|
|
606
|
+
console.error(`[auto-continue] 手动续跑异常 ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
607
|
+
}
|
|
595
608
|
}
|
|
596
609
|
|
|
597
610
|
/** 本会话当前生效的冷却间隔(自适应退避)。 */
|
|
@@ -628,7 +641,12 @@ export class AutoContinueRunner {
|
|
|
628
641
|
const timer = setTimeout(() => {
|
|
629
642
|
if (state.pendingTimer !== timer) return;
|
|
630
643
|
state.pendingTimer = undefined;
|
|
631
|
-
|
|
644
|
+
// 保险丝: 定时器回调内任何异常(含 inactive context)都不得成为未捕获异常炸掉进程。
|
|
645
|
+
try {
|
|
646
|
+
void this.fire(sessionId, reason);
|
|
647
|
+
} catch (error) {
|
|
648
|
+
console.error(`[auto-continue] 定时发送异常 ${sessionId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
649
|
+
}
|
|
632
650
|
}, config.graceMs);
|
|
633
651
|
state.pendingTimer = timer;
|
|
634
652
|
const template = reason.startsWith('loop:')
|
package/src/index.ts
CHANGED
|
@@ -98,11 +98,19 @@ export function apply(ctx: Context): void {
|
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
// 引擎引用: inject 回调可能重入(依赖组合变化), 顶层 effect 在 fiber 卸载时必跑。
|
|
102
|
+
// dispose 绑定必须挂在 apply 的顶层 ctx 上——挂 inject 派生 ctx 的 effect 在 config HMR
|
|
103
|
+
// 替换行时不会执行, 悬空定时器会撞上 inactive context 炸掉整个进程。
|
|
104
|
+
let runnerRef: AutoContinueRunner | undefined;
|
|
105
|
+
|
|
101
106
|
// 单实例引擎: host 进程内监听会话事件, 所有标签页共享同一个引擎。
|
|
102
107
|
ctx.inject(['settings', 'agents', 'webServer'], (engineCtx) => {
|
|
108
|
+
// 回调重入时先清理旧引擎, 避免定时器与监听器叠加。
|
|
109
|
+
if (runnerRef !== undefined) runnerRef.dispose();
|
|
103
110
|
const runner = new AutoContinueRunner(engineCtx, () =>
|
|
104
111
|
resolveConfig(engineCtx.settings.get(settingsNamespace(AUTO_CONTINUE_NS)) as AutoContinueSettings | undefined),
|
|
105
112
|
);
|
|
113
|
+
runnerRef = runner;
|
|
106
114
|
|
|
107
115
|
// 状态桥: browser 侧订阅通知与运行时状态(SSE)。
|
|
108
116
|
const sseClients = new Set<(data: string) => void>();
|
|
@@ -178,4 +186,12 @@ export function apply(ctx: Context): void {
|
|
|
178
186
|
},
|
|
179
187
|
});
|
|
180
188
|
});
|
|
189
|
+
|
|
190
|
+
// 顶层生命周期绑定: fiber 卸载时清理引擎(定时器/监听器)。
|
|
191
|
+
// 挂在 apply 的 ctx 上保证 Cordis 一定会调用, 防止 inactive context 崩溃。
|
|
192
|
+
ctx.effect(() => () => {
|
|
193
|
+
const runner = runnerRef;
|
|
194
|
+
runnerRef = undefined;
|
|
195
|
+
if (runner !== undefined) runner.dispose();
|
|
196
|
+
});
|
|
181
197
|
}
|