@xgjktech/xg_cwork_im 1.17.52 → 1.17.54
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 +489 -489
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -3
- package/dist/index.js.map +1 -1
- package/dist/src/account-connection.d.ts.map +1 -1
- package/dist/src/account-connection.js +31 -2
- package/dist/src/account-connection.js.map +1 -1
- package/dist/src/active-round-registry.d.ts +4 -0
- package/dist/src/active-round-registry.d.ts.map +1 -1
- package/dist/src/active-round-registry.js +20 -0
- package/dist/src/active-round-registry.js.map +1 -1
- package/dist/src/build-project-prompt.test.js +18 -18
- package/dist/src/dispatch-mentioned-reply.d.ts.map +1 -1
- package/dist/src/dispatch-mentioned-reply.js +88 -40
- package/dist/src/dispatch-mentioned-reply.js.map +1 -1
- package/dist/src/gateway-session-queue.d.ts +0 -3
- package/dist/src/gateway-session-queue.d.ts.map +1 -1
- package/dist/src/gateway-session-queue.js +0 -1
- package/dist/src/gateway-session-queue.js.map +1 -1
- package/dist/src/plugin-dispose.d.ts.map +1 -1
- package/dist/src/plugin-dispose.js +4 -0
- package/dist/src/plugin-dispose.js.map +1 -1
- package/dist/src/subagent-monitor.d.ts +13 -0
- package/dist/src/subagent-monitor.d.ts.map +1 -1
- package/dist/src/subagent-monitor.js +32 -13
- package/dist/src/subagent-monitor.js.map +1 -1
- package/dist/src/turn-agent-event-subscription.d.ts +25 -0
- package/dist/src/turn-agent-event-subscription.d.ts.map +1 -0
- package/dist/src/turn-agent-event-subscription.js +172 -0
- package/dist/src/turn-agent-event-subscription.js.map +1 -0
- package/dist/src/turn-end-controller.d.ts +62 -0
- package/dist/src/turn-end-controller.d.ts.map +1 -0
- package/dist/src/turn-end-controller.js +240 -0
- package/dist/src/turn-end-controller.js.map +1 -0
- package/dist/src/turn-end-controller.test.d.ts +2 -0
- package/dist/src/turn-end-controller.test.d.ts.map +1 -0
- package/dist/src/turn-end-controller.test.js +91 -0
- package/dist/src/turn-end-controller.test.js.map +1 -0
- package/package.json +2 -2
- package/dist/src/single-chat-no-reply-fallback.d.ts +0 -14
- package/dist/src/single-chat-no-reply-fallback.d.ts.map +0 -1
- package/dist/src/single-chat-no-reply-fallback.js +0 -20
- package/dist/src/single-chat-no-reply-fallback.js.map +0 -1
- package/dist/src/subagent-progress-hooks.d.ts +0 -11
- package/dist/src/subagent-progress-hooks.d.ts.map +0 -1
- package/dist/src/subagent-progress-hooks.js +0 -41
- package/dist/src/subagent-progress-hooks.js.map +0 -1
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 订阅 OpenClaw agent-event 流(lifecycle / thinking / compaction)并把整轮级信号喂给
|
|
3
|
+
* turn-end-controller,同时为「非 wrapper 覆盖的 resume 段」补发 thinking/compaction 进度。
|
|
4
|
+
*
|
|
5
|
+
* 背景:OpenClaw 7.1+ / 8.x(lifecycle 语义一致;245 实测 2026.7.1-2)中,父会话每次
|
|
6
|
+
* sessions_spawn 都会挂起并结束当前段 run(lifecycle `yielded:true`),子任务返回后父会话被
|
|
7
|
+
* 自动 resume 成新段。dispatch wrapper 只覆盖首段,之后的 resume 段只有 agent-event 流能看到。
|
|
8
|
+
* 因此:
|
|
9
|
+
* - lifecycle 终态(sessionKey 必带)→ 判定「整轮是否真正结束」(P0);
|
|
10
|
+
* - thinking / compaction(仅带 runId)→ 以 lifecycle start 建立的 runId→session 索引定位到轮次,
|
|
11
|
+
* 仅对非 wrapper 首段的 runId 转发,避免与 wrapper 回调重复展示(P1)。
|
|
12
|
+
*
|
|
13
|
+
* handle 内绝不容忍抛错(与 plan-event-subscription 同纪律)。
|
|
14
|
+
*/
|
|
15
|
+
import { findActiveRoundForSession } from "./active-round-registry.js";
|
|
16
|
+
import { getSharedMap } from "./process-shared-store.js";
|
|
17
|
+
import { turnOnActivity, turnOnSegmentEnd, turnOnSegmentStart, } from "./turn-end-controller.js";
|
|
18
|
+
const LOG_PREFIX = "[xg_cwork_im] [turn-agent-events]";
|
|
19
|
+
const runIdToSessionKey = getSharedMap("turn-runid-to-session");
|
|
20
|
+
const wrappedRunIdBySession = getSharedMap("turn-wrapped-runid");
|
|
21
|
+
const lastThinkingByRound = getSharedMap("turn-thinking-last");
|
|
22
|
+
const THINKING_FORWARD_MIN_INTERVAL_MS = 600;
|
|
23
|
+
const THINKING_DISPLAY_MAX = 160;
|
|
24
|
+
function isXgImGroupSessionKey(sessionKey) {
|
|
25
|
+
return (sessionKey.startsWith("agent:") &&
|
|
26
|
+
sessionKey.includes(":xg_cwork_im:group:"));
|
|
27
|
+
}
|
|
28
|
+
function clipWsDisplay(text, max) {
|
|
29
|
+
const t = text.trim().replace(/\r\n/g, "\n").replace(/\n+/g, " ");
|
|
30
|
+
if (!t)
|
|
31
|
+
return "";
|
|
32
|
+
if (t.length <= max)
|
|
33
|
+
return t;
|
|
34
|
+
return `${t.slice(0, Math.max(0, max - 1))}…`;
|
|
35
|
+
}
|
|
36
|
+
async function safeStreamProgress(round, event, displayText, summary) {
|
|
37
|
+
try {
|
|
38
|
+
await round.streamClient.streamProgress({
|
|
39
|
+
groupId: round.groupId,
|
|
40
|
+
replyToMsgId: round.replyToMsgId,
|
|
41
|
+
event,
|
|
42
|
+
displayText,
|
|
43
|
+
...(summary ? { summary } : {}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
console.warn(`${LOG_PREFIX} ${event} progress failed: ${String(err)}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function forwardThinking(round, runId, text) {
|
|
51
|
+
const roundKey = round.roundKey;
|
|
52
|
+
const state = lastThinkingByRound.get(roundKey);
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
if (!state || state.runId !== runId) {
|
|
55
|
+
lastThinkingByRound.set(roundKey, { runId, lastText: text, lastSentAt: 0, lastSentText: "" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
state.lastText = text;
|
|
59
|
+
const delta = text.startsWith(state.lastSentText) ? text.slice(state.lastSentText.length) : text;
|
|
60
|
+
if (!delta.trim())
|
|
61
|
+
return;
|
|
62
|
+
if (now - state.lastSentAt < THINKING_FORWARD_MIN_INTERVAL_MS)
|
|
63
|
+
return;
|
|
64
|
+
state.lastSentAt = now;
|
|
65
|
+
state.lastSentText = text;
|
|
66
|
+
const display = clipWsDisplay(delta, THINKING_DISPLAY_MAX);
|
|
67
|
+
if (!display)
|
|
68
|
+
return;
|
|
69
|
+
void safeStreamProgress(round, "RUN_EVENT", display, "thinking");
|
|
70
|
+
}
|
|
71
|
+
function forwardCompaction(round, phase) {
|
|
72
|
+
void safeStreamProgress(round, "RUN_EVENT", phase === "start" ? "正在整理上下文…" : "上下文整理完成");
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 记录 wrapper 首段 runId(dispatch wrapper 的 onAgentRunStart)。
|
|
76
|
+
* 该 run 的 thinking/compaction 已由 wrapper 回调展示,订阅侧跳过,避免重复。
|
|
77
|
+
*/
|
|
78
|
+
export function noteWrappedRunId(sessionKey, runId) {
|
|
79
|
+
if (sessionKey && runId) {
|
|
80
|
+
wrappedRunIdBySession.set(sessionKey, runId);
|
|
81
|
+
console.log(`${LOG_PREFIX} wrapperRunId session=${sessionKey} runId=${runId}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export function createTurnAgentEventHandler() {
|
|
85
|
+
return (event, _ctx) => {
|
|
86
|
+
try {
|
|
87
|
+
const stream = event.stream;
|
|
88
|
+
const runId = event.runId;
|
|
89
|
+
if (!runId)
|
|
90
|
+
return;
|
|
91
|
+
if (stream === "lifecycle") {
|
|
92
|
+
const sessionKey = event.sessionKey;
|
|
93
|
+
if (!sessionKey || !isXgImGroupSessionKey(sessionKey))
|
|
94
|
+
return;
|
|
95
|
+
const phase = event.data?.phase;
|
|
96
|
+
const round = findActiveRoundForSession(sessionKey);
|
|
97
|
+
runIdToSessionKey.set(runId, sessionKey);
|
|
98
|
+
if (phase === "start") {
|
|
99
|
+
console.log(`${LOG_PREFIX} lifecycle start runId=${runId} session=${sessionKey} ` +
|
|
100
|
+
`round=${round ? `${round.roundKey}#${round.generation}` : "(none)"}`);
|
|
101
|
+
if (round) {
|
|
102
|
+
turnOnSegmentStart(round.roundKey, round.generation);
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (phase === "end" || phase === "error") {
|
|
107
|
+
const info = {
|
|
108
|
+
phase,
|
|
109
|
+
yielded: event.data?.yielded === true,
|
|
110
|
+
...(typeof event.data?.stopReason === "string"
|
|
111
|
+
? { stopReason: event.data.stopReason }
|
|
112
|
+
: {}),
|
|
113
|
+
};
|
|
114
|
+
console.log(`${LOG_PREFIX} lifecycle ${phase} runId=${runId} session=${sessionKey} ` +
|
|
115
|
+
`yielded=${info.yielded} stopReason=${info.stopReason ?? "(none)"} ` +
|
|
116
|
+
`round=${round ? `${round.roundKey}#${round.generation}` : "(none)"}`);
|
|
117
|
+
if (round) {
|
|
118
|
+
turnOnSegmentEnd(round.roundKey, round.generation, info);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (stream === "thinking" || stream === "compaction") {
|
|
124
|
+
const sessionKey = runIdToSessionKey.get(runId);
|
|
125
|
+
if (!sessionKey)
|
|
126
|
+
return;
|
|
127
|
+
const round = findActiveRoundForSession(sessionKey);
|
|
128
|
+
if (!round)
|
|
129
|
+
return;
|
|
130
|
+
turnOnActivity(round.roundKey, round.generation);
|
|
131
|
+
const isWrappedRun = wrappedRunIdBySession.get(sessionKey) === runId;
|
|
132
|
+
if (isWrappedRun) {
|
|
133
|
+
console.debug(`${LOG_PREFIX} ${stream} skip runId=${runId} session=${sessionKey} ` +
|
|
134
|
+
`(wrapper first segment runId)`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
console.log(`${LOG_PREFIX} ${stream} forward runId=${runId} session=${sessionKey} ` +
|
|
138
|
+
`round=${round.roundKey}#${round.generation}`);
|
|
139
|
+
if (stream === "thinking") {
|
|
140
|
+
const text = typeof event.data?.text === "string" ? event.data.text : "";
|
|
141
|
+
if (text.trim())
|
|
142
|
+
forwardThinking(round, runId, text);
|
|
143
|
+
}
|
|
144
|
+
else if (stream === "compaction") {
|
|
145
|
+
const phase = String(event.data?.phase ?? "");
|
|
146
|
+
if (phase === "start" || phase === "end") {
|
|
147
|
+
forwardCompaction(round, phase);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
console.warn(`${LOG_PREFIX} handle 内部异常,已吞: ${String(err)}`);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** 插件卸载时清理全部事件索引。 */
|
|
159
|
+
export function disposeTurnAgentEvents() {
|
|
160
|
+
runIdToSessionKey.clear();
|
|
161
|
+
wrappedRunIdBySession.clear();
|
|
162
|
+
lastThinkingByRound.clear();
|
|
163
|
+
}
|
|
164
|
+
export function registerTurnAgentEventSubscription(api) {
|
|
165
|
+
api.agent.events.registerAgentEventSubscription({
|
|
166
|
+
id: "xg-cwork-im-turn-progress",
|
|
167
|
+
description: "订阅 lifecycle/thinking/compaction:驱动整轮收尾判定(子任务 resume 场景的 FINISHED)与 resume 段思考/整理进度。",
|
|
168
|
+
streams: ["lifecycle", "thinking", "compaction"],
|
|
169
|
+
handle: createTurnAgentEventHandler(),
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=turn-agent-event-subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-agent-event-subscription.js","sourceRoot":"","sources":["../../src/turn-agent-event-subscription.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 整轮(user-turn)结束仲裁。
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw 7.1+ / 8.x(lifecycle 语义一致;245 实测为 2026.7.1-2):父会话(主会话)
|
|
5
|
+
* 自己的 finish 就是它某段 run 的 lifecycle `end/error` 终态且 `yielded !== true`
|
|
6
|
+
* (`yielded:true` 只出现在该段挂起了子任务、会话被挂起等待 resume 时——核心据此把
|
|
7
|
+
* requester 保留为可唤醒)。因此:
|
|
8
|
+
* - 「yield 段终态」≠ 整轮结束(主会话还会 resume 继续派发/产出最终答复);
|
|
9
|
+
* - 「非 yield 段终态」= 主会话 finish:该段不会再挂起子任务、不会再被唤醒。
|
|
10
|
+
*
|
|
11
|
+
* 收尾条件(防御性补强,主判据仍是主会话的非 yield finish):
|
|
12
|
+
* dispatch 已返回 ∧ 无段在跑(segmentActive=false,避免拿旧段终态收尾)
|
|
13
|
+
* ∧ 无运行中子任务(兜非标准边界:异常/乱序下非 yield 终态先于子任务结束到达)
|
|
14
|
+
* ∧ 已出现非 yield 段终态。
|
|
15
|
+
*
|
|
16
|
+
* 本模块只维护进程级共享的每轮状态与「可收尾」信号,不持有任何 WS/HTTP 副作用:
|
|
17
|
+
* - wrapper finally、subagent spawn/end、lifecycle start/end、以及组内活动心跳都来喂状态;
|
|
18
|
+
* - dispatch-mentioned-reply 在 wrapper 里 await 本模块的可收尾信号后再发 RUN_USAGE/FINISHED;
|
|
19
|
+
* - 子任务完成但主会话无法被唤醒(announce/handoff 投递,不再产生 resume 终态)、
|
|
20
|
+
* 或 lifecycle 事件完全不可达(订阅不可用等环境)时,靠静默兜底定时器收尾,保证不悬挂。
|
|
21
|
+
*/
|
|
22
|
+
export interface TurnSegmentTerminalInfo {
|
|
23
|
+
phase: "end" | "error";
|
|
24
|
+
yielded?: boolean;
|
|
25
|
+
stopReason?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function turnOnDispatchReturned(roundKey: string, gen: number): void;
|
|
28
|
+
/** index.ts `subagent_spawned`:记录本轮见过子任务并 +1 活跃数。 */
|
|
29
|
+
export declare function turnOnSubagentSpawn(roundKey: string, gen: number): void;
|
|
30
|
+
/** index.ts `subagent_ended` / monitor settle:活跃子任务数 -1 并重新评估。 */
|
|
31
|
+
export declare function turnOnSubagentEnded(roundKey: string, gen: number): void;
|
|
32
|
+
/**
|
|
33
|
+
* 监控层对账:本轮全部已知子任务完成(settle 触发)。与 subagent_ended 事件互补,
|
|
34
|
+
* 覆盖个别 ended 事件丢失导致计数器不为 0、从而永远等不到收尾信号的情况。
|
|
35
|
+
*/
|
|
36
|
+
export declare function turnOnAllChildrenDone(roundKey: string, gen: number): void;
|
|
37
|
+
/** lifecycle start(父会话新段开始):清掉旧终态,等待该段终态。 */
|
|
38
|
+
export declare function turnOnSegmentStart(roundKey: string, gen: number): void;
|
|
39
|
+
/** lifecycle end/error(父会话某段结束):记录是否 yield 后评估。 */
|
|
40
|
+
export declare function turnOnSegmentEnd(roundKey: string, gen: number, info: TurnSegmentTerminalInfo): void;
|
|
41
|
+
/**
|
|
42
|
+
* 组内活动心跳(before_tool_call / thinking / assistant 等属于本轮的其它事件):
|
|
43
|
+
* 说明父会话仍在工作,取消静默收尾计时,但不算“段已结束”。
|
|
44
|
+
*/
|
|
45
|
+
export declare function turnOnActivity(roundKey: string, gen: number): void;
|
|
46
|
+
/** wrapper 侧首次建轮后登记状态;sawSubagent 由后续 spawn 事件置位。 */
|
|
47
|
+
export declare function turnEnsureRound(roundKey: string, gen: number): void;
|
|
48
|
+
export declare function turnIsSawSubagent(roundKey: string, gen: number): boolean;
|
|
49
|
+
export declare function turnIsEligible(roundKey: string, gen: number): boolean;
|
|
50
|
+
/** 收尾完成 / 中断 / 插件卸载后清理。 */
|
|
51
|
+
export declare function turnForget(roundKey: string, gen: number): void;
|
|
52
|
+
/** 插件卸载时清理全部轮次状态。 */
|
|
53
|
+
export declare function disposeTurnEndController(): void;
|
|
54
|
+
/** 单测辅助。 */
|
|
55
|
+
export declare function __resetTurnEndControllerForTests(): void;
|
|
56
|
+
/** 单测辅助:缩短各计时窗口,避免真实等待。 */
|
|
57
|
+
export declare function __configureTurnEndForTests(overrides: {
|
|
58
|
+
nonYieldDebounceMs?: number;
|
|
59
|
+
resumeWaitFallbackMs?: number;
|
|
60
|
+
noLifecycleFallbackMs?: number;
|
|
61
|
+
}): void;
|
|
62
|
+
//# sourceMappingURL=turn-end-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-end-controller.d.ts","sourceRoot":"","sources":["../../src/turn-end-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,WAAW,uBAAuB;IACpC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAyGD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAQ1E;AAED,qDAAqD;AACrD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUvE;AAED,kEAAkE;AAClE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAQvE;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAQzE;AAED,6CAA6C;AAC7C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAWtE;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAC5B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,uBAAuB,GAC9B,IAAI,CAYN;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAUlE;AAED,qDAAqD;AACrD,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAiBnE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAExE;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAErE;AAED,2BAA2B;AAC3B,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAM9D;AAED,qBAAqB;AACrB,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C;AAED,YAAY;AACZ,wBAAgB,gCAAgC,IAAI,IAAI,CAEvD;AAED,2BAA2B;AAC3B,wBAAgB,0BAA0B,CAAC,SAAS,EAAE;IAClD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAClC,GAAG,IAAI,CAIP"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 整轮(user-turn)结束仲裁。
|
|
3
|
+
*
|
|
4
|
+
* OpenClaw 7.1+ / 8.x(lifecycle 语义一致;245 实测为 2026.7.1-2):父会话(主会话)
|
|
5
|
+
* 自己的 finish 就是它某段 run 的 lifecycle `end/error` 终态且 `yielded !== true`
|
|
6
|
+
* (`yielded:true` 只出现在该段挂起了子任务、会话被挂起等待 resume 时——核心据此把
|
|
7
|
+
* requester 保留为可唤醒)。因此:
|
|
8
|
+
* - 「yield 段终态」≠ 整轮结束(主会话还会 resume 继续派发/产出最终答复);
|
|
9
|
+
* - 「非 yield 段终态」= 主会话 finish:该段不会再挂起子任务、不会再被唤醒。
|
|
10
|
+
*
|
|
11
|
+
* 收尾条件(防御性补强,主判据仍是主会话的非 yield finish):
|
|
12
|
+
* dispatch 已返回 ∧ 无段在跑(segmentActive=false,避免拿旧段终态收尾)
|
|
13
|
+
* ∧ 无运行中子任务(兜非标准边界:异常/乱序下非 yield 终态先于子任务结束到达)
|
|
14
|
+
* ∧ 已出现非 yield 段终态。
|
|
15
|
+
*
|
|
16
|
+
* 本模块只维护进程级共享的每轮状态与「可收尾」信号,不持有任何 WS/HTTP 副作用:
|
|
17
|
+
* - wrapper finally、subagent spawn/end、lifecycle start/end、以及组内活动心跳都来喂状态;
|
|
18
|
+
* - dispatch-mentioned-reply 在 wrapper 里 await 本模块的可收尾信号后再发 RUN_USAGE/FINISHED;
|
|
19
|
+
* - 子任务完成但主会话无法被唤醒(announce/handoff 投递,不再产生 resume 终态)、
|
|
20
|
+
* 或 lifecycle 事件完全不可达(订阅不可用等环境)时,靠静默兜底定时器收尾,保证不悬挂。
|
|
21
|
+
*/
|
|
22
|
+
import { getSharedMap } from "./process-shared-store.js";
|
|
23
|
+
/** 非 yield 终态到达后、置可收尾前的防抖(吞掉与 resume 的竞态)。 */
|
|
24
|
+
let NON_YIELD_DEBOUNCE_MS = 1_500;
|
|
25
|
+
/** 子任务全完成但父会话暂未 resume/无 lifecycle 时的静默兜底窗口。 */
|
|
26
|
+
let RESUME_WAIT_FALLBACK_MS = 20_000;
|
|
27
|
+
/** 完全没有 lifecycle 事件(订阅不可用)时的兜底窗口。 */
|
|
28
|
+
let NO_LIFECYCLE_FALLBACK_MS = 30_000;
|
|
29
|
+
const states = getSharedMap("turn-end-states");
|
|
30
|
+
const DIAG = "[xg_cwork_im] [turn-diag]";
|
|
31
|
+
function stateOf(roundKey, gen) {
|
|
32
|
+
const s = states.get(roundKey);
|
|
33
|
+
if (!s || s.gen !== gen)
|
|
34
|
+
return undefined;
|
|
35
|
+
return s;
|
|
36
|
+
}
|
|
37
|
+
function clearTimer(s) {
|
|
38
|
+
if (s.timer !== undefined) {
|
|
39
|
+
clearTimeout(s.timer);
|
|
40
|
+
s.timer = undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isQuiescent(s) {
|
|
44
|
+
return s.dispatchReturned && !s.segmentActive && s.activeChildren <= 0;
|
|
45
|
+
}
|
|
46
|
+
/** 防抖置位:静默窗口内无新事件才真正 eligible(避免与 resume 的毫秒级竞态)。 */
|
|
47
|
+
function setEligible(roundKey, gen, s, reason) {
|
|
48
|
+
clearTimer(s);
|
|
49
|
+
s.timer = setTimeout(() => {
|
|
50
|
+
s.timer = undefined;
|
|
51
|
+
const cur = stateOf(roundKey, gen);
|
|
52
|
+
if (!cur)
|
|
53
|
+
return;
|
|
54
|
+
if (isQuiescent(cur) && !cur.eligible) {
|
|
55
|
+
cur.eligible = true;
|
|
56
|
+
console.log(`${DIAG} eligible round=${roundKey}#${gen} reason=${reason} ` +
|
|
57
|
+
`children=${cur.activeChildren} segActive=${cur.segmentActive} ` +
|
|
58
|
+
`lifecycleSeen=${cur.lifecycleSeen}`);
|
|
59
|
+
}
|
|
60
|
+
}, NON_YIELD_DEBOUNCE_MS);
|
|
61
|
+
s.timer.unref?.();
|
|
62
|
+
}
|
|
63
|
+
function armTimer(roundKey, s, delayMs, reason) {
|
|
64
|
+
if (s.timer !== undefined)
|
|
65
|
+
return;
|
|
66
|
+
console.log(`${DIAG} fallback arm round=${roundKey}#${s.gen} reason=${reason} delayMs=${delayMs} ` +
|
|
67
|
+
`children=${s.activeChildren} segActive=${s.segmentActive}`);
|
|
68
|
+
s.timer = setTimeout(() => {
|
|
69
|
+
s.timer = undefined;
|
|
70
|
+
const cur = stateOf(roundKey, s.gen);
|
|
71
|
+
if (!cur)
|
|
72
|
+
return;
|
|
73
|
+
if (isQuiescent(cur) && !cur.eligible) {
|
|
74
|
+
cur.eligible = true;
|
|
75
|
+
console.log(`${DIAG} eligible round=${roundKey}#${s.gen} reason=fallback:${reason} ` +
|
|
76
|
+
`children=${cur.activeChildren} segActive=${cur.segmentActive}`);
|
|
77
|
+
}
|
|
78
|
+
}, delayMs);
|
|
79
|
+
s.timer.unref?.();
|
|
80
|
+
}
|
|
81
|
+
/** 任一事件后统一评估:满足「已非段中、无子任务、dispatch 已返回」时决定防抖收尾或等待。 */
|
|
82
|
+
function reevaluate(roundKey, s) {
|
|
83
|
+
if (!s.dispatchReturned)
|
|
84
|
+
return;
|
|
85
|
+
if (!isQuiescent(s)) {
|
|
86
|
+
// 还有段在跑 / 还有子任务:绝不收尾,也不保留旧的可收尾状态。
|
|
87
|
+
s.eligible = false;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (s.lifecycleSeen && s.lastEnd && s.lastEnd.yielded !== true) {
|
|
91
|
+
// 主会话 finish:最新一段父 run 是非 yield 终态 → 不会再 resume → 防抖后收尾。
|
|
92
|
+
setEligible(roundKey, s.gen, s, "non-yield-end");
|
|
93
|
+
}
|
|
94
|
+
else if (s.lifecycleSeen && s.lastEnd) {
|
|
95
|
+
// 最新一段以 yield 结束 → 主会话仍可能被 resume;等 runStart/新活动取消,
|
|
96
|
+
// 否则静默窗口后收尾(覆盖子任务完成但主会话无法被唤醒的 handoff 路径)。
|
|
97
|
+
armTimer(roundKey, s, RESUME_WAIT_FALLBACK_MS, "yield-end-wait-resume");
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// 完全没见到 lifecycle(订阅不可用等):纯兜底窗口收尾。
|
|
101
|
+
armTimer(roundKey, s, NO_LIFECYCLE_FALLBACK_MS, "no-lifecycle-seen");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
export function turnOnDispatchReturned(roundKey, gen) {
|
|
105
|
+
const s = stateOf(roundKey, gen);
|
|
106
|
+
if (!s)
|
|
107
|
+
return;
|
|
108
|
+
s.dispatchReturned = true;
|
|
109
|
+
console.log(`${DIAG} dispatchReturned round=${roundKey}#${gen} children=${s.activeChildren} segActive=${s.segmentActive}`);
|
|
110
|
+
reevaluate(roundKey, s);
|
|
111
|
+
}
|
|
112
|
+
/** index.ts `subagent_spawned`:记录本轮见过子任务并 +1 活跃数。 */
|
|
113
|
+
export function turnOnSubagentSpawn(roundKey, gen) {
|
|
114
|
+
const s = stateOf(roundKey, gen);
|
|
115
|
+
if (!s)
|
|
116
|
+
return;
|
|
117
|
+
s.sawSubagent = true;
|
|
118
|
+
s.activeChildren += 1;
|
|
119
|
+
clearTimer(s);
|
|
120
|
+
s.eligible = false;
|
|
121
|
+
console.log(`${DIAG} spawn round=${roundKey}#${gen} activeChildren=${s.activeChildren} sawSubagent=${s.sawSubagent}`);
|
|
122
|
+
}
|
|
123
|
+
/** index.ts `subagent_ended` / monitor settle:活跃子任务数 -1 并重新评估。 */
|
|
124
|
+
export function turnOnSubagentEnded(roundKey, gen) {
|
|
125
|
+
const s = stateOf(roundKey, gen);
|
|
126
|
+
if (!s)
|
|
127
|
+
return;
|
|
128
|
+
if (s.activeChildren > 0)
|
|
129
|
+
s.activeChildren -= 1;
|
|
130
|
+
console.log(`${DIAG} childEnded round=${roundKey}#${gen} activeChildren=${s.activeChildren}`);
|
|
131
|
+
reevaluate(roundKey, s);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 监控层对账:本轮全部已知子任务完成(settle 触发)。与 subagent_ended 事件互补,
|
|
135
|
+
* 覆盖个别 ended 事件丢失导致计数器不为 0、从而永远等不到收尾信号的情况。
|
|
136
|
+
*/
|
|
137
|
+
export function turnOnAllChildrenDone(roundKey, gen) {
|
|
138
|
+
const s = stateOf(roundKey, gen);
|
|
139
|
+
if (!s)
|
|
140
|
+
return;
|
|
141
|
+
s.activeChildren = 0;
|
|
142
|
+
console.log(`${DIAG} allChildrenDone round=${roundKey}#${gen} (settle reconcile)`);
|
|
143
|
+
reevaluate(roundKey, s);
|
|
144
|
+
}
|
|
145
|
+
/** lifecycle start(父会话新段开始):清掉旧终态,等待该段终态。 */
|
|
146
|
+
export function turnOnSegmentStart(roundKey, gen) {
|
|
147
|
+
const s = stateOf(roundKey, gen);
|
|
148
|
+
if (!s)
|
|
149
|
+
return;
|
|
150
|
+
s.lifecycleSeen = true;
|
|
151
|
+
s.segmentActive = true;
|
|
152
|
+
s.lastEnd = undefined;
|
|
153
|
+
s.eligible = false;
|
|
154
|
+
clearTimer(s);
|
|
155
|
+
console.log(`${DIAG} segmentStart round=${roundKey}#${gen} (resume/new segment begins)`);
|
|
156
|
+
}
|
|
157
|
+
/** lifecycle end/error(父会话某段结束):记录是否 yield 后评估。 */
|
|
158
|
+
export function turnOnSegmentEnd(roundKey, gen, info) {
|
|
159
|
+
const s = stateOf(roundKey, gen);
|
|
160
|
+
if (!s)
|
|
161
|
+
return;
|
|
162
|
+
s.lifecycleSeen = true;
|
|
163
|
+
s.segmentActive = false;
|
|
164
|
+
s.lastEnd = { ...info, at: Date.now() };
|
|
165
|
+
console.log(`${DIAG} segmentEnd round=${roundKey}#${gen} phase=${info.phase} ` +
|
|
166
|
+
`yielded=${info.yielded === true} stopReason=${info.stopReason ?? "(none)"} ` +
|
|
167
|
+
`children=${s.activeChildren}`);
|
|
168
|
+
reevaluate(roundKey, s);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 组内活动心跳(before_tool_call / thinking / assistant 等属于本轮的其它事件):
|
|
172
|
+
* 说明父会话仍在工作,取消静默收尾计时,但不算“段已结束”。
|
|
173
|
+
*/
|
|
174
|
+
export function turnOnActivity(roundKey, gen) {
|
|
175
|
+
const s = stateOf(roundKey, gen);
|
|
176
|
+
if (!s)
|
|
177
|
+
return;
|
|
178
|
+
if (s.timer !== undefined) {
|
|
179
|
+
console.debug(`${DIAG} activity cancels fallback round=${roundKey}#${gen} (timer cleared)`);
|
|
180
|
+
}
|
|
181
|
+
clearTimer(s);
|
|
182
|
+
s.eligible = false;
|
|
183
|
+
}
|
|
184
|
+
/** wrapper 侧首次建轮后登记状态;sawSubagent 由后续 spawn 事件置位。 */
|
|
185
|
+
export function turnEnsureRound(roundKey, gen) {
|
|
186
|
+
const existing = states.get(roundKey);
|
|
187
|
+
if (existing && existing.gen === gen)
|
|
188
|
+
return;
|
|
189
|
+
if (existing) {
|
|
190
|
+
// 同一 group 的新一代轮次:清掉旧代计时与状态,避免旧代事件/定时器干扰新轮。
|
|
191
|
+
clearTimer(existing);
|
|
192
|
+
console.log(`${DIAG} ensure new generation round=${roundKey}#${gen} (was #${existing.gen})`);
|
|
193
|
+
}
|
|
194
|
+
states.set(roundKey, {
|
|
195
|
+
gen,
|
|
196
|
+
sawSubagent: false,
|
|
197
|
+
activeChildren: 0,
|
|
198
|
+
dispatchReturned: false,
|
|
199
|
+
lifecycleSeen: false,
|
|
200
|
+
segmentActive: false,
|
|
201
|
+
eligible: false,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
export function turnIsSawSubagent(roundKey, gen) {
|
|
205
|
+
return stateOf(roundKey, gen)?.sawSubagent === true;
|
|
206
|
+
}
|
|
207
|
+
export function turnIsEligible(roundKey, gen) {
|
|
208
|
+
return stateOf(roundKey, gen)?.eligible === true;
|
|
209
|
+
}
|
|
210
|
+
/** 收尾完成 / 中断 / 插件卸载后清理。 */
|
|
211
|
+
export function turnForget(roundKey, gen) {
|
|
212
|
+
const s = stateOf(roundKey, gen);
|
|
213
|
+
if (!s)
|
|
214
|
+
return;
|
|
215
|
+
console.log(`${DIAG} forget round=${roundKey}#${gen} (round finished/cleaned)`);
|
|
216
|
+
clearTimer(s);
|
|
217
|
+
states.delete(roundKey);
|
|
218
|
+
}
|
|
219
|
+
/** 插件卸载时清理全部轮次状态。 */
|
|
220
|
+
export function disposeTurnEndController() {
|
|
221
|
+
for (const s of states.values()) {
|
|
222
|
+
if (s.timer !== undefined)
|
|
223
|
+
clearTimeout(s.timer);
|
|
224
|
+
}
|
|
225
|
+
states.clear();
|
|
226
|
+
}
|
|
227
|
+
/** 单测辅助。 */
|
|
228
|
+
export function __resetTurnEndControllerForTests() {
|
|
229
|
+
disposeTurnEndController();
|
|
230
|
+
}
|
|
231
|
+
/** 单测辅助:缩短各计时窗口,避免真实等待。 */
|
|
232
|
+
export function __configureTurnEndForTests(overrides) {
|
|
233
|
+
if (overrides.nonYieldDebounceMs !== undefined)
|
|
234
|
+
NON_YIELD_DEBOUNCE_MS = overrides.nonYieldDebounceMs;
|
|
235
|
+
if (overrides.resumeWaitFallbackMs !== undefined)
|
|
236
|
+
RESUME_WAIT_FALLBACK_MS = overrides.resumeWaitFallbackMs;
|
|
237
|
+
if (overrides.noLifecycleFallbackMs !== undefined)
|
|
238
|
+
NO_LIFECYCLE_FALLBACK_MS = overrides.noLifecycleFallbackMs;
|
|
239
|
+
}
|
|
240
|
+
//# sourceMappingURL=turn-end-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-end-controller.js","sourceRoot":"","sources":["../../src/turn-end-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-end-controller.test.d.ts","sourceRoot":"","sources":["../../src/turn-end-controller.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { test, describe, beforeEach } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { turnEnsureRound, turnOnDispatchReturned, turnOnSubagentSpawn, turnOnSubagentEnded, turnOnSegmentStart, turnOnSegmentEnd, turnOnActivity, turnIsEligible, turnIsSawSubagent, turnForget, __resetTurnEndControllerForTests, __configureTurnEndForTests, } from "./turn-end-controller.js";
|
|
4
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
describe("turn-end-controller(OpenClaw 7.1+/8.x 子任务整轮收尾)", () => {
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
__resetTurnEndControllerForTests();
|
|
8
|
+
__configureTurnEndForTests({
|
|
9
|
+
nonYieldDebounceMs: 20,
|
|
10
|
+
resumeWaitFallbackMs: 40,
|
|
11
|
+
noLifecycleFallbackMs: 40,
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
test("顺序多子任务:仅首个/各段子任务完成不触发收尾,父会话非 yield 终态后才 eligible", async () => {
|
|
15
|
+
const rk = "acc:g1";
|
|
16
|
+
const gen = 1;
|
|
17
|
+
turnEnsureRound(rk, gen);
|
|
18
|
+
turnOnDispatchReturned(rk, gen);
|
|
19
|
+
// 第 1 段:start → spawn(child1) → yield end
|
|
20
|
+
turnOnSegmentStart(rk, gen);
|
|
21
|
+
turnOnSubagentSpawn(rk, gen);
|
|
22
|
+
turnOnSegmentEnd(rk, gen, { phase: "end", yielded: true, stopReason: "end_turn" });
|
|
23
|
+
assert.equal(turnIsEligible(rk, gen), false, "段以 yield 结束 → 不收尾");
|
|
24
|
+
// child1 完成
|
|
25
|
+
turnOnSubagentEnded(rk, gen);
|
|
26
|
+
assert.equal(turnIsEligible(rk, gen), false, "子任务完成但父会话可能 resume → 不立即收尾");
|
|
27
|
+
// 父会话 resume(新段)→ 派发 child2 → yield end
|
|
28
|
+
turnOnSegmentStart(rk, gen);
|
|
29
|
+
turnOnSubagentSpawn(rk, gen);
|
|
30
|
+
turnOnSegmentEnd(rk, gen, { phase: "end", yielded: true, stopReason: "end_turn" });
|
|
31
|
+
turnOnSubagentEnded(rk, gen);
|
|
32
|
+
assert.equal(turnIsEligible(rk, gen), false);
|
|
33
|
+
// 父会话最后一次 resume,产出最终答复后以非 yield 终态结束
|
|
34
|
+
turnOnSegmentStart(rk, gen);
|
|
35
|
+
turnOnSegmentEnd(rk, gen, { phase: "end", yielded: false, stopReason: "end_turn" });
|
|
36
|
+
await sleep(60);
|
|
37
|
+
assert.equal(turnIsEligible(rk, gen), true, "非 yield 终态 → 防抖后收尾");
|
|
38
|
+
});
|
|
39
|
+
test("子任务完成但父会话无法被唤醒(handoff)→ 静默兜底后收尾", async () => {
|
|
40
|
+
const rk = "acc:g2";
|
|
41
|
+
const gen = 1;
|
|
42
|
+
turnEnsureRound(rk, gen);
|
|
43
|
+
turnOnDispatchReturned(rk, gen);
|
|
44
|
+
turnOnSegmentStart(rk, gen);
|
|
45
|
+
turnOnSubagentSpawn(rk, gen);
|
|
46
|
+
turnOnSegmentEnd(rk, gen, { phase: "end", yielded: true });
|
|
47
|
+
turnOnSubagentEnded(rk, gen);
|
|
48
|
+
assert.equal(turnIsEligible(rk, gen), false);
|
|
49
|
+
await sleep(80);
|
|
50
|
+
assert.equal(turnIsEligible(rk, gen), true, "resume 未到来 → 兜底窗口后收尾");
|
|
51
|
+
});
|
|
52
|
+
test("组内活动心跳可延续等待(父会话还在工作时不误收尾)", async () => {
|
|
53
|
+
const rk = "acc:g3";
|
|
54
|
+
const gen = 1;
|
|
55
|
+
turnEnsureRound(rk, gen);
|
|
56
|
+
turnOnDispatchReturned(rk, gen);
|
|
57
|
+
turnOnSegmentStart(rk, gen);
|
|
58
|
+
turnOnSubagentSpawn(rk, gen);
|
|
59
|
+
turnOnSegmentEnd(rk, gen, { phase: "end", yielded: true });
|
|
60
|
+
turnOnSubagentEnded(rk, gen);
|
|
61
|
+
// 兜底窗口内出现组内工具活动 → 取消收尾计时
|
|
62
|
+
turnOnActivity(rk, gen);
|
|
63
|
+
await sleep(80);
|
|
64
|
+
assert.equal(turnIsEligible(rk, gen), false, "心跳延续等待,不能提前收尾");
|
|
65
|
+
turnOnSegmentStart(rk, gen);
|
|
66
|
+
turnOnSegmentEnd(rk, gen, { phase: "end", yielded: false });
|
|
67
|
+
await sleep(60);
|
|
68
|
+
assert.equal(turnIsEligible(rk, gen), true);
|
|
69
|
+
});
|
|
70
|
+
test("lifecycle 完全不可见(订阅不可用)→ 兜底收尾且不悬挂", async () => {
|
|
71
|
+
const rk = "acc:g4";
|
|
72
|
+
const gen = 1;
|
|
73
|
+
turnEnsureRound(rk, gen);
|
|
74
|
+
turnOnDispatchReturned(rk, gen);
|
|
75
|
+
turnOnSubagentSpawn(rk, gen);
|
|
76
|
+
turnOnSubagentEnded(rk, gen);
|
|
77
|
+
await sleep(80);
|
|
78
|
+
assert.equal(turnIsEligible(rk, gen), true);
|
|
79
|
+
});
|
|
80
|
+
test("sawSubagent 标记来自 spawn 事件;turnForget 清理状态", () => {
|
|
81
|
+
const rk = "acc:g5";
|
|
82
|
+
const gen = 1;
|
|
83
|
+
turnEnsureRound(rk, gen);
|
|
84
|
+
assert.equal(turnIsSawSubagent(rk, gen), false);
|
|
85
|
+
turnOnSubagentSpawn(rk, gen);
|
|
86
|
+
assert.equal(turnIsSawSubagent(rk, gen), true);
|
|
87
|
+
turnForget(rk, gen);
|
|
88
|
+
assert.equal(turnIsSawSubagent(rk, gen), false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=turn-end-controller.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-end-controller.test.js","sourceRoot":"","sources":["../../src/turn-end-controller.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xgjktech/xg_cwork_im",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.54",
|
|
4
4
|
"description": "XG CWork IM channel plugin for OpenClaw",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bot",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"build": "tsc -p tsconfig.json",
|
|
26
26
|
"type-check": "tsc -p tsconfig.json --noEmit",
|
|
27
27
|
"lint": "tsc -p tsconfig.json --noEmit",
|
|
28
|
-
"test": "npm run build && node --test dist/src/account-reconciler.test.js dist/src/inbound-terminal-state.test.js dist/src/user-facing-errors.test.js dist/src/deliver-text.test.js dist/src/agent-context-types.test.js dist/src/build-project-prompt.test.js dist/src/nas-volumes.test.js dist/src/workspace-event.test.js dist/src/workspace-event-scheduler.test.js dist/src/connection-workspace-event.test.js dist/src/block-streaming-config.test.js dist/src/channel-disabled-account.test.js dist/src/per-message-fallback-notice.test.js dist/src/thinking-ws-progress.test.js dist/src/resource-file.test.js dist/src/turn-deliver-buffer.test.js dist/src/run-usage.test.js dist/src/ws-run-event-unmapped.test.js dist/src/item-event-guard.test.js dist/src/inbound-model.test.js dist/src/auto-model-router.test.js dist/src/channel-config.test.js dist/src/send-service.test.js dist/src/send-file-tool.test.js dist/src/plan-tool-result.test.js dist/src/plan-blocked-notice.test.js dist/src/plan-store-access.test.js dist/src/plan-tool-middleware.test.js dist/src/plan-event-subscription.test.js dist/src/plan-ws-registry.test.js dist/src/plan-stat-event.test.js dist/src/active-round-registry.test.js dist/src/interrupt-round.test.js dist/src/subagent-monitor.test.js dist/src/session-env-store.test.js dist/src/register-exec-env-hook.test.js dist/src/cron-appkey-binding-store.test.js dist/src/ws-inbound-schema.test.js",
|
|
28
|
+
"test": "npm run build && node --test dist/src/account-reconciler.test.js dist/src/inbound-terminal-state.test.js dist/src/user-facing-errors.test.js dist/src/deliver-text.test.js dist/src/agent-context-types.test.js dist/src/build-project-prompt.test.js dist/src/nas-volumes.test.js dist/src/workspace-event.test.js dist/src/workspace-event-scheduler.test.js dist/src/connection-workspace-event.test.js dist/src/block-streaming-config.test.js dist/src/channel-disabled-account.test.js dist/src/per-message-fallback-notice.test.js dist/src/thinking-ws-progress.test.js dist/src/resource-file.test.js dist/src/turn-deliver-buffer.test.js dist/src/run-usage.test.js dist/src/ws-run-event-unmapped.test.js dist/src/item-event-guard.test.js dist/src/inbound-model.test.js dist/src/auto-model-router.test.js dist/src/channel-config.test.js dist/src/send-service.test.js dist/src/send-file-tool.test.js dist/src/plan-tool-result.test.js dist/src/plan-blocked-notice.test.js dist/src/plan-store-access.test.js dist/src/plan-tool-middleware.test.js dist/src/plan-event-subscription.test.js dist/src/plan-ws-registry.test.js dist/src/plan-stat-event.test.js dist/src/active-round-registry.test.js dist/src/interrupt-round.test.js dist/src/subagent-monitor.test.js dist/src/turn-end-controller.test.js dist/src/session-env-store.test.js dist/src/register-exec-env-hook.test.js dist/src/cron-appkey-binding-store.test.js dist/src/ws-inbound-schema.test.js",
|
|
29
29
|
"install:prod": "npm install --omit=dev",
|
|
30
30
|
"prepublishOnly": "npm run build"
|
|
31
31
|
},
|