@zhushanwen/subagent-core 0.8.0 → 0.9.0
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/dist/{chunk-OL5BK4VN.js → chunk-LZJOEDGQ.js} +455 -203
- package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
- package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
- package/dist/execution/engine/engine-discovery-scan.cjs +216 -155
- package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
- package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
- package/dist/execution/engine/engine-discovery-scan.js +1 -1
- package/dist/execution/relay-env.js +5 -4
- package/dist/index.cjs +7686 -5894
- package/dist/index.d.cts +1876 -1360
- package/dist/index.d.ts +1876 -1360
- package/dist/index.js +7048 -5493
- package/dist.bundle/index.cjs +7747 -5957
- package/package.json +4 -4
- package/src/__tests__/manifest-store.test.ts +16 -15
- package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
- package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
- package/src/execution/__tests__/alive-store.test.ts +117 -1
- package/src/execution/__tests__/batch-finalized.test.ts +206 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
- package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
- package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
- package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
- package/src/execution/__tests__/delivery-methods.test.ts +90 -176
- package/src/execution/__tests__/dialog-queue.test.ts +70 -3
- package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
- package/src/execution/__tests__/execute-nesting.test.ts +8 -5
- package/src/execution/__tests__/execution-record.test.ts +8 -148
- package/src/execution/__tests__/finalize-record.test.ts +295 -129
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
- package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
- package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
- package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
- package/src/execution/__tests__/idle-gc.test.ts +60 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
- package/src/execution/__tests__/model-config-service.test.ts +111 -0
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
- package/src/execution/__tests__/notify-ledger.test.ts +42 -1
- package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
- package/src/execution/__tests__/record-binding.test.ts +613 -0
- package/src/execution/__tests__/record-origin.test.ts +318 -0
- package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
- package/src/execution/__tests__/record-store.test.ts +72 -54
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
- package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
- package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
- package/src/execution/__tests__/run-orchestration.test.ts +199 -0
- package/src/execution/__tests__/session-file-gc.test.ts +35 -4
- package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
- package/src/execution/__tests__/state-marker.test.ts +288 -0
- package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
- package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
- package/src/execution/__tests__/subagent-service.test.ts +17 -215
- package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
- package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
- package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
- package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
- package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
- package/src/execution/alive-store.ts +37 -28
- package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
- package/src/execution/config.ts +1 -1
- package/src/execution/conversation-continuation.ts +541 -0
- package/src/execution/dialog-queue.ts +60 -12
- package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
- package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
- package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
- package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
- package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
- package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
- package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
- package/src/execution/engine/__tests__/registry.test.ts +0 -1
- package/src/execution/engine/__tests__/routing.test.ts +0 -1
- package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
- package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
- package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
- package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
- package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
- package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
- package/src/execution/engine/client/engine-client.ts +81 -25
- package/src/execution/engine/client/mirror.ts +9 -0
- package/src/execution/engine/client/remote-engine.ts +52 -34
- package/src/execution/engine/client/reverse-router.ts +6 -27
- package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
- package/src/execution/engine/common/capability-gate.ts +27 -0
- package/src/execution/engine/common/data-dir.ts +3 -2
- package/src/execution/engine/common/errors.ts +1 -1
- package/src/execution/engine/common/journal-wiring.ts +18 -9
- package/src/execution/engine/common/run-signals.ts +83 -0
- package/src/execution/engine/d8-compat.ts +1 -1
- package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
- package/src/execution/engine/host/host-bridge.ts +19 -41
- package/src/execution/engine/host/pi-host-binding.ts +5 -5
- package/src/execution/engine/port.ts +34 -54
- package/src/execution/engine/types.ts +11 -14
- package/src/execution/execution-record.ts +15 -47
- package/src/execution/finalize-record.ts +94 -197
- package/src/execution/idle-gc.ts +9 -3
- package/src/execution/lifecycle-manager.ts +25 -361
- package/src/execution/lifecycle-predicates.ts +22 -19
- package/src/execution/manifest-store.ts +40 -42
- package/src/execution/model-config-service.ts +1 -1
- package/src/execution/notifier.ts +39 -1
- package/src/execution/notify-host.ts +13 -4
- package/src/execution/record-entry.ts +20 -2
- package/src/execution/record-store.ts +925 -160
- package/src/execution/relay-env.ts +8 -3
- package/src/execution/round-supervisor/domain.ts +17 -5
- package/src/execution/round-supervisor/service-binding.test.ts +89 -53
- package/src/execution/round-supervisor/service-binding.ts +71 -21
- package/src/execution/round-supervisor/supervisor.ts +23 -9
- package/src/execution/service/record-access.ts +547 -0
- package/src/execution/service/record-lifecycle.ts +542 -0
- package/src/execution/service/run-orchestration.ts +1647 -0
- package/src/execution/service/service-bootstrap.ts +111 -0
- package/src/execution/service/service-constants.ts +27 -0
- package/src/execution/service/session-baselines.ts +401 -0
- package/src/execution/service/sync-collect-domain.ts +442 -0
- package/src/execution/service/workflow-dispatch.ts +567 -0
- package/src/execution/session-file-gc.ts +11 -8
- package/src/execution/session-reconstructor.ts +44 -1
- package/src/execution/sessions-index.ts +20 -3
- package/src/execution/settled-watchdog.ts +86 -43
- package/src/execution/state-marker.ts +516 -0
- package/src/execution/stream-sink.ts +1 -1
- package/src/execution/subagent-actions-core.ts +53 -31
- package/src/execution/subagent-service.ts +643 -2980
- package/src/execution/subprocess-agent-runner.ts +46 -323
- package/src/execution/types.ts +60 -43
- package/src/execution/ui-request-handler-factory.ts +5 -0
- package/src/execution/worktree-manager.ts +1 -1
- package/src/execution/worktree-registry.ts +1 -1
- package/src/index.ts +6 -5
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
- package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
- package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
- package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
- package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
- package/src/orchestration/execute-agent-call.ts +1 -1
- package/src/orchestration/file-run-store.ts +1 -1
- package/src/orchestration/models/ports.ts +20 -13
- package/src/orchestration/models/types.ts +11 -13
- package/src/orchestration/run-snapshot.ts +18 -22
- package/src/orchestration/worker-message-pump.ts +33 -57
- package/src/shared/atomic-write.ts +10 -8
- package/dist/chunk-T2SYBW3I.js +0 -24
- package/dist/engine-discovery-scan-BXLA8y-z.d.cts +0 -1624
- package/dist/engine-discovery-scan-BXLA8y-z.d.ts +0 -1624
- package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
- package/src/execution/__tests__/finalized-marker.test.ts +0 -104
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
- package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
- package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
- package/src/execution/__tests__/tombstone-store.test.ts +0 -73
- package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
- package/src/execution/finalized-marker.ts +0 -70
- package/src/execution/tombstone-store.ts +0 -72
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
//
|
|
5
5
|
// 形态对齐协议 v1(stdout 独占 NDJSON;stdin 帧驱动):
|
|
6
6
|
// - FAKE_PROTOCOL_FIXTURE:fixture JSON 路径(必填);本件 = 该 fixture 的回放引擎;
|
|
7
|
-
// - 正向方法(
|
|
8
|
-
//
|
|
7
|
+
// - 正向方法(9 个):initialize / probe / run / cancel / read / listModels /
|
|
8
|
+
// validateModel / dispose / ping——结果取 fixture 对应段([H1 U5] interact 已随
|
|
9
|
+
// chat-run 统一退役);
|
|
9
10
|
// - 反向通道(8 个):host/log / host/askUser / host/permission / host/streamDelta /
|
|
10
11
|
// host/poolResolved / host/handleReady / host/childSpawned / host/childStateChanged
|
|
11
|
-
// ——run 期间按 fixture.run.script
|
|
12
|
+
// ——run 期间按 fixture.run.script 逐动作播放([H1 U5] 曾有的轮次相位通道已随
|
|
13
|
+
// chat-run 统一退役);
|
|
12
14
|
// - 错误帧:FAKE_PROTOCOL_ERROR=run_failed(run 回错误帧)| unknown_method(对任意
|
|
13
15
|
// 未知名方法回 engine_method_unknown——不在 v1 错误码表内的码也必须原样透传,
|
|
14
16
|
// 断言的是帧形态而非码表);FAKE_PROTOCOL_VERSION 越界 → initialize 回
|
|
@@ -20,18 +22,16 @@
|
|
|
20
22
|
// - FAKE_PID_FILE:启动时写自身 pid(SIGTERM 用例的 kill 定位锚——core 侧 pidfile
|
|
21
23
|
// 归 EngineClient 所有,fake 侧不触碰,路径自建自删归测试);
|
|
22
24
|
// - FAKE_CAPABILITIES_CONVERSATION:覆写 initialize 应答的
|
|
23
|
-
// capabilities.conversation(
|
|
25
|
+
// capabilities.conversation(resume 能力位 gate 负向:旧形态引擎 manifest 无该位);
|
|
24
26
|
// - 新 script op(env 注入 script,沿 FAKE_RUN_HANG 先例):
|
|
25
27
|
// { op:"stderr", text } → 引擎 stderr 写点(engine_crashed
|
|
26
28
|
// 的 stderr 尾 400 窗口实证);
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
// - FAKE_RUN_LIFECYCLE=1 / FAKE_RUN_RECORD_DELTA=1 / FAKE_RUN_STDERR_HANG=1:
|
|
34
|
-
// run script 注入上述动作段(三相位 × 双键 / recordId 键 delta / stderr+hang)。
|
|
29
|
+
// streamDelta op 支持 action.recordId → recordId 键 delta(chat 续聊轮关联键);
|
|
30
|
+
// - 数据面应答回声:recordId-delta 帧的②应答到达时回发 host/log("ack <label>")——
|
|
31
|
+
// core 侧断言「宿主确认收到」回执链闭合的观测锚;
|
|
32
|
+
// - FAKE_RUN_RECORD_DELTA=1 / FAKE_RUN_STDERR_HANG=1:run script 注入上述动作段
|
|
33
|
+
// (recordId 键 delta / stderr+hang)。[H1 U5] FAKE_RUN_LIFECYCLE(轮次相位 ×
|
|
34
|
+
// 双键注入段)已随通道退役删除。
|
|
35
35
|
|
|
36
36
|
import { spawn } from "node:child_process";
|
|
37
37
|
import { readFileSync, writeFileSync } from "node:fs";
|
|
@@ -119,8 +119,8 @@ async function playRunScript(runId, script) {
|
|
|
119
119
|
});
|
|
120
120
|
break;
|
|
121
121
|
case "streamDelta": {
|
|
122
|
-
// [W6] action.recordId 有值 = 续聊轮 recordId 键 delta(关联键分路——
|
|
123
|
-
// D1-A
|
|
122
|
+
// [W6] action.recordId 有值 = chat 续聊轮 recordId 键 delta(关联键分路——
|
|
123
|
+
// D1-A:续聊轮经 handle.sessionRef 的 recordId 关联)。
|
|
124
124
|
const streamReqId = `rev-stream-${nextRequestId++}`;
|
|
125
125
|
if (action.recordId !== undefined) {
|
|
126
126
|
ackEchoLabels.set(streamReqId, `streamDelta recordId=${action.recordId}`);
|
|
@@ -141,24 +141,6 @@ async function playRunScript(runId, script) {
|
|
|
141
141
|
// 后续动作可用 host/log 做到达锚)。
|
|
142
142
|
process.stderr.write(action.text ?? "");
|
|
143
143
|
break;
|
|
144
|
-
case "roundLifecycle": {
|
|
145
|
-
// [W6] host/roundLifecycle 帧(三相位 × runId|recordId 双关联键)。
|
|
146
|
-
// failed 相位必含 error{code,message}(isHostRoundLifecycleParams 判据)。
|
|
147
|
-
const lcReqId = `rev-lc-${nextRequestId++}`;
|
|
148
|
-
const keyed =
|
|
149
|
-
action.keyedBy === "recordId"
|
|
150
|
-
? { recordId: action.key }
|
|
151
|
-
: { runId: action.key };
|
|
152
|
-
ackEchoLabels.set(lcReqId, `roundLifecycle ${action.key} ${action.phase}`);
|
|
153
|
-
reverseRequest(lcReqId, "host/roundLifecycle", {
|
|
154
|
-
...keyed,
|
|
155
|
-
phase: action.phase,
|
|
156
|
-
...(action.usage !== undefined ? { usage: action.usage } : {}),
|
|
157
|
-
...(action.error !== undefined ? { error: action.error } : {}),
|
|
158
|
-
...(action.anchor !== undefined ? { anchor: action.anchor } : {}),
|
|
159
|
-
});
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
144
|
case "permission":
|
|
163
145
|
reverseRequest(`rev-perm-${nextRequestId++}`, "host/permission", {
|
|
164
146
|
runId,
|
|
@@ -187,7 +169,7 @@ async function playRunScript(runId, script) {
|
|
|
187
169
|
}
|
|
188
170
|
|
|
189
171
|
const KNOWN_METHODS = new Set([
|
|
190
|
-
"initialize", "probe", "run", "cancel", "
|
|
172
|
+
"initialize", "probe", "run", "cancel", "read",
|
|
191
173
|
"listModels", "validateModel", "dispose", "ping",
|
|
192
174
|
]);
|
|
193
175
|
|
|
@@ -205,9 +187,8 @@ createInterface({ input: process.stdin }).on("line", (line) => {
|
|
|
205
187
|
}
|
|
206
188
|
|
|
207
189
|
// 帧②(core 对反向请求的应答):askUser 最终结果到达——回放层记录后继续。
|
|
208
|
-
// [W6] 数据面(
|
|
209
|
-
//
|
|
210
|
-
// run 应答帧到达)。
|
|
190
|
+
// [W6] 数据面(recordId-delta)应答到达 → 回发 host/log 回声(core 侧「宿主
|
|
191
|
+
// 确认收到」回执链闭合的观测锚;NDJSON 单连接有序,回声先于 run 应答帧到达)。
|
|
211
192
|
if (typeof frame.id === "string") {
|
|
212
193
|
const echoLabel = ackEchoLabels.get(frame.id);
|
|
213
194
|
if (echoLabel !== undefined) {
|
|
@@ -278,9 +259,6 @@ createInterface({ input: process.stdin }).on("line", (line) => {
|
|
|
278
259
|
case "validateModel":
|
|
279
260
|
send({ id, result: fixture.validateModel.result });
|
|
280
261
|
return;
|
|
281
|
-
case "interact":
|
|
282
|
-
send({ id, result: fixture.interact.result });
|
|
283
|
-
return;
|
|
284
262
|
case "read":
|
|
285
263
|
send({ id, result: fixture.read.result });
|
|
286
264
|
return;
|
|
@@ -327,18 +305,6 @@ createInterface({ input: process.stdin }).on("line", (line) => {
|
|
|
327
305
|
{ op: "hang" },
|
|
328
306
|
);
|
|
329
307
|
}
|
|
330
|
-
if (process.env.FAKE_RUN_LIFECYCLE === "1") {
|
|
331
|
-
injected.push(
|
|
332
|
-
// 三相位 × 双关联键(settled 带 usage;idle/failed 带 anchor;failed 带
|
|
333
|
-
// error{code,message}——isHostRoundLifecycleParams 的相位居留形状)。
|
|
334
|
-
{ op: "roundLifecycle", keyedBy: "runId", key: "run-smoke-1", phase: "settled", usage: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 } },
|
|
335
|
-
{ op: "roundLifecycle", keyedBy: "runId", key: "run-smoke-1", phase: "idle", anchor: { sessionRef: { sessionId: "sess-fixture" }, poolKey: "shared" } },
|
|
336
|
-
{ op: "roundLifecycle", keyedBy: "runId", key: "run-smoke-1", phase: "failed", error: { code: "engine_run_failed", message: "round failed (scripted)", recovery: "Inspect the task and retry." } },
|
|
337
|
-
{ op: "roundLifecycle", keyedBy: "recordId", key: "rec-chat-1", phase: "settled" },
|
|
338
|
-
{ op: "roundLifecycle", keyedBy: "recordId", key: "rec-chat-1", phase: "idle", anchor: { sessionRef: { sessionId: "sess-chat-1" }, poolKey: "shared" } },
|
|
339
|
-
{ op: "roundLifecycle", keyedBy: "recordId", key: "rec-chat-1", phase: "failed", error: { code: "engine_run_failed", message: "chat round failed (scripted)", recovery: "Inspect the task and retry." } },
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
308
|
if (process.env.FAKE_RUN_RECORD_DELTA === "1") {
|
|
343
309
|
injected.push({ op: "streamDelta", recordId: "rec-chat-1", delta: "chat-delta-not-routed-to-runId" });
|
|
344
310
|
}
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
RELAY_ENV_SOCKET,
|
|
39
39
|
RELAY_PROTOCOL_VERSION,
|
|
40
40
|
isRelayActive,
|
|
41
|
-
} from "
|
|
41
|
+
} from "@zhushanwen/subagent-engine-sdk";
|
|
42
42
|
import { assertAgentEventInvariants } from "./agent-event-invariants.ts";
|
|
43
43
|
|
|
44
44
|
const LIVE = process.env["ENGINE_CONFORMANCE_LIVE"] === "1";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// protocol-blackbox.test.ts —— W10 协议黑盒套件(基线三层①的自动层)。
|
|
2
2
|
//
|
|
3
3
|
// 覆盖(impl-plan §2.10 / 设计 §4 探针挂钩①):
|
|
4
|
-
// -
|
|
4
|
+
// - 9 正向方法(initialize/probe/run/cancel/read/listModels/
|
|
5
5
|
// validateModel/dispose/ping)× fake 引擎 CLI 往返;
|
|
6
6
|
// - 8 反向通道(host/log、host/askUser、host/permission、host/streamDelta、
|
|
7
7
|
// host/poolResolved、host/handleReady、host/childSpawned、host/childStateChanged)
|
|
@@ -137,8 +137,8 @@ function makeHarness(extraEnv: Record<string, string> = {}, routeRunId = fixture
|
|
|
137
137
|
return h;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
describe("协议黑盒:
|
|
141
|
-
it("initialize/probe/listModels/validateModel/
|
|
140
|
+
describe("协议黑盒:9 正向方法 × fake 引擎回放", () => {
|
|
141
|
+
it("initialize/probe/listModels/validateModel/read/ping 往返(fixture 白名单比对)", async () => {
|
|
142
142
|
const h = makeHarness();
|
|
143
143
|
try {
|
|
144
144
|
await h.client.ensureConnected();
|
|
@@ -162,12 +162,8 @@ describe("协议黑盒:10 正向方法 × fake 引擎回放", () => {
|
|
|
162
162
|
)) as { canonicalRef: string };
|
|
163
163
|
expect(validated.canonicalRef).toBe("fake/fake-1");
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
{ handle: fixture.run.result.handle, action: { kind: "message", text: "hi" } },
|
|
168
|
-
{ timeoutMs: 5_000 },
|
|
169
|
-
)) as { accepted: boolean };
|
|
170
|
-
expect(interact.accepted).toBe(true);
|
|
165
|
+
// [H1 U5] interact 方法已随 chat-run 统一退役(D5)——往返断言随之删除,
|
|
166
|
+
// fake 引擎对未知方法回 engine_method_unknown(unknown_method 模式覆盖)。
|
|
171
167
|
|
|
172
168
|
const view = (await h.client.request(
|
|
173
169
|
"read",
|
|
@@ -125,9 +125,6 @@ function stubEngine(id: string): EnginePort {
|
|
|
125
125
|
run: async () => {
|
|
126
126
|
throw new Error("unused");
|
|
127
127
|
},
|
|
128
|
-
interact: async () => {
|
|
129
|
-
throw new Error("unused");
|
|
130
|
-
},
|
|
131
128
|
read: async () => ({ engineId: id, turns: [], source: "outcome-only" }),
|
|
132
129
|
};
|
|
133
130
|
}
|
|
@@ -33,9 +33,6 @@ function fakeEngine(id: string, models: Array<{ id: string; name?: string }> | n
|
|
|
33
33
|
run: async () => {
|
|
34
34
|
throw new Error("not used in this test");
|
|
35
35
|
},
|
|
36
|
-
interact: async () => {
|
|
37
|
-
throw new Error("not used in this test");
|
|
38
|
-
},
|
|
39
36
|
read: async () => ({ engineId: id, turns: [], source: "outcome-only" }),
|
|
40
37
|
...(models !== null ? { listModels: () => models } : {}),
|
|
41
38
|
};
|
|
@@ -53,7 +53,6 @@ function makeFakeEngine(id: string): EnginePort {
|
|
|
53
53
|
Promise.resolve({ ok: true, engineVersion: "0.0.0-test", checks: [{ name: "stub", ok: true }] }),
|
|
54
54
|
run: (_task: AgentCallOpts, _ctx: RunContext) =>
|
|
55
55
|
Promise.reject(new Error("fake engine: run not implemented")),
|
|
56
|
-
interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
|
|
57
56
|
read: (_handle: Parameters<EnginePort["read"]>[0]): Promise<SessionView> =>
|
|
58
57
|
Promise.resolve({ engineId: id, turns: [], source: "outcome-only" }),
|
|
59
58
|
};
|
|
@@ -40,7 +40,6 @@ function makeFakeEngine(id: string, probeOk: boolean): EnginePort {
|
|
|
40
40
|
} satisfies ProbeReport,
|
|
41
41
|
),
|
|
42
42
|
run: (_t: AgentCallOpts, _c: RunContext) => Promise.reject(new Error("fake: run not implemented")),
|
|
43
|
-
interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
|
|
44
43
|
read: (_h: Parameters<EnginePort["read"]>[0]): Promise<SessionView> =>
|
|
45
44
|
Promise.resolve({ engineId: id, turns: [], source: "outcome-only" }),
|
|
46
45
|
};
|
|
@@ -39,9 +39,6 @@ function makeRecord(worktreeHandle?: WorktreeHandle): ExecutionRecord {
|
|
|
39
39
|
|
|
40
40
|
function makeDeps(cleanup: ReturnType<typeof vi.fn>): FinalizeDeps {
|
|
41
41
|
return {
|
|
42
|
-
manifestStore: {
|
|
43
|
-
writeManifest: vi.fn(async () => undefined),
|
|
44
|
-
} as unknown as FinalizeDeps["manifestStore"],
|
|
45
42
|
worktreeManager: { cleanup } as unknown as FinalizeDeps["worktreeManager"],
|
|
46
43
|
store: { archive: vi.fn() } as unknown as FinalizeDeps["store"],
|
|
47
44
|
modelService: { getAgentDir: () => "/tmp/w3-test-agentdir" } as unknown as FinalizeDeps["modelService"],
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// src/execution/engine/client/__tests__/engine-client-reap.test.ts
|
|
2
|
+
//
|
|
3
|
+
// [H1 U2 / 红线①收割链] 引擎退出链孤儿收割单测(设计
|
|
4
|
+
// docs/design/subagent-chat-run-unification.md §3.3 红线① + §5 U2 验收「收割链各分支」)。
|
|
5
|
+
//
|
|
6
|
+
// 覆盖分支:
|
|
7
|
+
// - 非主动死亡(!intentionalKill && alreadyDead)POSIX → 复用 killProcessTree 负 pid
|
|
8
|
+
// 组杀(fire-and-forget),且仅组杀一次(无外层逐 pid 兜底——grace 窗不被压缩);
|
|
9
|
+
// - intentionalKill 路径跳过(主动 dispose 链已有两层杀——不叠加、优雅窗零压缩);
|
|
10
|
+
// - 引擎进程仍活(握手失败重建路径)不触发收割(下方既有组杀兜底承接,行为不变);
|
|
11
|
+
// - Windows 通道:置死前 mirror.snapshot 过滤活孤儿(state==="running" && !killed——
|
|
12
|
+
// 死 pid 与 killed 项不进清单)后逐 pid **异步 spawn** taskkill(无 spawnSync——
|
|
13
|
+
// 同步逐个会冻结 onEngineExit 回调、推迟 pending reject);
|
|
14
|
+
// - 收割插入点 = mirror.killAll() 置死清空之前(Windows 通道依赖快照活项)。
|
|
15
|
+
//
|
|
16
|
+
// 平台分派说明:Windows 通道经私有方法 reapOrphansViaMirrorSnapshot 直调覆盖
|
|
17
|
+
//(process.platform 判据在 vitest worker 内不可 stub——一行字面分派由 U4 真机 Windows
|
|
18
|
+
// 验收覆盖);POSIX 分支用本机真实平台(darwin/linux)断言。
|
|
19
|
+
|
|
20
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
21
|
+
|
|
22
|
+
const { loggerMock } = vi.hoisted(() => ({
|
|
23
|
+
loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
|
|
24
|
+
}));
|
|
25
|
+
vi.mock("../../../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
|
|
26
|
+
|
|
27
|
+
// killProcessTree spy(POSIX 组杀断言);waitForChildExit 保留真实现(killAll 消费)。
|
|
28
|
+
const { killProcessTreeSpy } = vi.hoisted(() => ({ killProcessTreeSpy: vi.fn() }));
|
|
29
|
+
vi.mock("../reaper.ts", async (importOriginal) => {
|
|
30
|
+
const actual = await importOriginal<typeof import("../reaper.ts")>();
|
|
31
|
+
return { ...actual, killProcessTree: (...args: unknown[]) => killProcessTreeSpy(...(args as [])) };
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// node:child_process spawn spy(Windows 通道 taskkill 断言;本文件不走 ensureConnected,
|
|
35
|
+
// 引擎 CLI spawn 不被触发)。
|
|
36
|
+
const { spawnSpy, spawnSyncSpy } = vi.hoisted(() => ({
|
|
37
|
+
spawnSpy: vi.fn(() => ({
|
|
38
|
+
unref: vi.fn(),
|
|
39
|
+
on: vi.fn(),
|
|
40
|
+
pid: 424242,
|
|
41
|
+
})),
|
|
42
|
+
spawnSyncSpy: vi.fn(() => ({ status: 0, error: undefined })),
|
|
43
|
+
}));
|
|
44
|
+
vi.mock("node:child_process", async (importOriginal) => {
|
|
45
|
+
const actual = await importOriginal<typeof import("node:child_process")>();
|
|
46
|
+
return { ...actual, spawn: spawnSpy, spawnSync: spawnSyncSpy };
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
import { EngineClient } from "../engine-client.ts";
|
|
50
|
+
import type { MirrorEntry } from "../mirror.ts";
|
|
51
|
+
|
|
52
|
+
function makeClient(): EngineClient {
|
|
53
|
+
return new EngineClient({
|
|
54
|
+
engineId: "fake",
|
|
55
|
+
command: process.execPath,
|
|
56
|
+
args: ["-e", ""],
|
|
57
|
+
hostKind: "test",
|
|
58
|
+
hostVersion: "test-host-1.0",
|
|
59
|
+
dataDir: "/tmp/engine-client-reap-test",
|
|
60
|
+
envPrefixes: [],
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 塞一个已死的假引擎 child(exitCode 非 null = alreadyDead 判据命中)。 */
|
|
65
|
+
function plantDeadChild(client: EngineClient, pid: number): void {
|
|
66
|
+
(client as unknown as { child: unknown }).child = {
|
|
67
|
+
pid,
|
|
68
|
+
exitCode: 1,
|
|
69
|
+
signalCode: null,
|
|
70
|
+
removeAllListeners: vi.fn(),
|
|
71
|
+
on: vi.fn(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 塞一个活着的假引擎 child(alreadyDead 不命中——既有组杀兜底路径)。 */
|
|
76
|
+
function plantLiveChild(client: EngineClient, pid: number): void {
|
|
77
|
+
(client as unknown as { child: unknown }).child = {
|
|
78
|
+
pid,
|
|
79
|
+
exitCode: null,
|
|
80
|
+
signalCode: null,
|
|
81
|
+
removeAllListeners: vi.fn(),
|
|
82
|
+
on: vi.fn(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** 在途请求 pending 直插(pending reject 时序断言用)。 */
|
|
87
|
+
function plantPendingRequest(client: EngineClient, id: number): { reject: ReturnType<typeof vi.fn> } {
|
|
88
|
+
const pending = { resolve: vi.fn(), reject: vi.fn(), timer: undefined };
|
|
89
|
+
(client as unknown as { pending: Map<number, typeof pending> }).pending.set(id, pending);
|
|
90
|
+
return pending;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function mirrorEntry(pid: number, state: "running" | "exited", killed: boolean): MirrorEntry {
|
|
94
|
+
return { pid, recordId: `sa-${pid}`, state, killed, updatedAt: Date.now() };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
describe("红线①收割链:非主动死亡路径(POSIX 组杀)", () => {
|
|
98
|
+
beforeEach(() => {
|
|
99
|
+
killProcessTreeSpy.mockClear();
|
|
100
|
+
spawnSpy.mockClear();
|
|
101
|
+
spawnSyncSpy.mockClear();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
afterEach(() => {
|
|
105
|
+
vi.restoreAllMocks();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("引擎意外死(alreadyDead + !intentionalKill)→ teardownProcess 内 mirror.killAll 之前组杀引擎 pid", () => {
|
|
109
|
+
const client = makeClient();
|
|
110
|
+
plantDeadChild(client, 4321);
|
|
111
|
+
client.mirror.recordSpawned(9100, "sa-a"); // 死前 spawn 的任务子进程(活孤儿)
|
|
112
|
+
|
|
113
|
+
(client as unknown as { teardownProcess: (d: string) => void }).teardownProcess("signal SIGKILL");
|
|
114
|
+
|
|
115
|
+
// POSIX:复用 killProcessTree 负 pid 组杀(目标 = 引擎 pid,组内含任务子进程——
|
|
116
|
+
// 不依赖镜像)
|
|
117
|
+
expect(killProcessTreeSpy).toHaveBeenCalledTimes(1);
|
|
118
|
+
expect(killProcessTreeSpy).toHaveBeenCalledWith(
|
|
119
|
+
4321,
|
|
120
|
+
expect.stringContaining("reap orphaned children"),
|
|
121
|
+
{ engineId: "fake" },
|
|
122
|
+
);
|
|
123
|
+
// 收割插入点在镜像置死之前:killAll 后快照已清(此处仅验证组杀发生在 teardown 内
|
|
124
|
+
// 且镜像最终被置死清空)
|
|
125
|
+
expect(client.mirror.size).toBe(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("无外层逐 pid 兜底:组杀仅一次(5s grace 窗不被同步校验压缩)", () => {
|
|
129
|
+
const client = makeClient();
|
|
130
|
+
plantDeadChild(client, 4321);
|
|
131
|
+
for (let i = 0; i < 5; i++) client.mirror.recordSpawned(9000 + i, `sa-${i}`); // 多个活孤儿
|
|
132
|
+
|
|
133
|
+
(client as unknown as { teardownProcess: (d: string) => void }).teardownProcess("signal SIGKILL");
|
|
134
|
+
|
|
135
|
+
// 升级链(SIGTERM → 5s grace → SIGKILL)内建覆盖残余职责:不逐 pid 补杀
|
|
136
|
+
expect(killProcessTreeSpy).toHaveBeenCalledTimes(1);
|
|
137
|
+
expect(spawnSpy).not.toHaveBeenCalled();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("intentionalKill 路径跳过收割(主动 dispose 链已有两层杀——不叠加、优雅窗零压缩)", () => {
|
|
141
|
+
const client = makeClient();
|
|
142
|
+
plantDeadChild(client, 4321);
|
|
143
|
+
client.mirror.recordSpawned(9100, "sa-a");
|
|
144
|
+
(client as unknown as { intentionalKill: boolean }).intentionalKill = true;
|
|
145
|
+
|
|
146
|
+
(client as unknown as { teardownProcess: (d: string) => void }).teardownProcess("after intentional kill");
|
|
147
|
+
|
|
148
|
+
expect(killProcessTreeSpy).not.toHaveBeenCalled();
|
|
149
|
+
expect(spawnSpy).not.toHaveBeenCalled();
|
|
150
|
+
expect(client.mirror.size).toBe(0); // 置死清空照常
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("引擎进程仍活(握手失败重建路径)→ 不触发收割分支(既有组杀兜底承接,行为不变)", () => {
|
|
154
|
+
const client = makeClient();
|
|
155
|
+
plantLiveChild(client, 4321);
|
|
156
|
+
|
|
157
|
+
(client as unknown as { teardownProcess: (d: string) => void }).teardownProcess("handshake failed");
|
|
158
|
+
|
|
159
|
+
// alreadyDead 不命中 → 收割链跳过;活进程组杀由 teardown 既有兜底承接(reason 原样)
|
|
160
|
+
expect(killProcessTreeSpy).toHaveBeenCalledTimes(1);
|
|
161
|
+
expect(killProcessTreeSpy).toHaveBeenCalledWith(4321, "handshake failed", { engineId: "fake" });
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe("红线①收割链:Windows 通道(镜像快照过滤活孤儿 + 异步 taskkill)", () => {
|
|
166
|
+
beforeEach(() => {
|
|
167
|
+
killProcessTreeSpy.mockClear();
|
|
168
|
+
spawnSpy.mockClear();
|
|
169
|
+
spawnSyncSpy.mockClear();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
afterEach(() => {
|
|
173
|
+
vi.restoreAllMocks();
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("置死前快照过滤活孤儿:仅 state=running && !killed 进 taskkill 清单(历史死 pid 与 killed 项排除)", () => {
|
|
177
|
+
const client = makeClient();
|
|
178
|
+
plantDeadChild(client, 4321);
|
|
179
|
+
client.mirror.recordSpawned(9100, "sa-live-orphan"); // 活孤儿 → 进清单
|
|
180
|
+
client.mirror.recordSpawned(9101, "sa-exited");
|
|
181
|
+
client.mirror.recordStateChanged({ pid: 9101, recordId: "sa-exited", state: "exited", killed: false });
|
|
182
|
+
client.mirror.recordSpawned(9102, "sa-killed");
|
|
183
|
+
client.mirror.recordStateChanged({ pid: 9102, recordId: "sa-killed", state: "exited", killed: true });
|
|
184
|
+
|
|
185
|
+
(client as unknown as { reapOrphansViaMirrorSnapshot: (d: string) => void }).reapOrphansViaMirrorSnapshot(
|
|
186
|
+
"signal SIGKILL",
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(spawnSpy).toHaveBeenCalledTimes(1);
|
|
190
|
+
expect(spawnSpy).toHaveBeenCalledWith("taskkill", ["/PID", "9100", "/T", "/F"], { stdio: "ignore" });
|
|
191
|
+
expect(killProcessTreeSpy).not.toHaveBeenCalled(); // win32 不复用引擎树杀(树根已死 = 空操作)
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("taskkill 异步 spawn 不阻塞 teardown(pending reject 在同步段完成——Continuation 失败通知链源头不被推迟)", () => {
|
|
195
|
+
const client = makeClient();
|
|
196
|
+
plantDeadChild(client, 4321);
|
|
197
|
+
client.mirror.recordSpawned(9100, "sa-live-orphan");
|
|
198
|
+
const pending = plantPendingRequest(client, 7);
|
|
199
|
+
|
|
200
|
+
// Windows 通道直调 + teardown 全流程(快照 → 异步 spawn → killAll → pending reject)
|
|
201
|
+
(client as unknown as { reapOrphansViaMirrorSnapshot: (d: string) => void }).reapOrphansViaMirrorSnapshot(
|
|
202
|
+
"signal SIGKILL",
|
|
203
|
+
);
|
|
204
|
+
(client as unknown as { teardownProcess: (d: string) => void }).teardownProcess("signal SIGKILL");
|
|
205
|
+
|
|
206
|
+
// spawn 是异步 fire-and-forget(返回即继续,无同步等待);spawnSync 零调用——
|
|
207
|
+
// 逐个同步 N×10s 上界会冻结 onEngineExit 同步回调与宿主事件循环
|
|
208
|
+
expect(spawnSpy).toHaveBeenCalled();
|
|
209
|
+
expect(spawnSyncSpy).not.toHaveBeenCalled();
|
|
210
|
+
expect(pending.reject).toHaveBeenCalledTimes(1); // 同步段完成(engine_crashed)
|
|
211
|
+
});
|
|
212
|
+
});
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
// 3. 置死后清空,isResumable 回落「无句柄」;
|
|
5
5
|
// killed 必含判据 = `entry !== undefined && !entry.killed`。
|
|
6
6
|
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
8
8
|
|
|
9
|
+
import {
|
|
10
|
+
DialogGlobalQueue,
|
|
11
|
+
registerActiveDialogQueue,
|
|
12
|
+
type UiRequest,
|
|
13
|
+
type UiResponse,
|
|
14
|
+
} from "../../../dialog-queue.ts";
|
|
9
15
|
import { SpawnedChildrenMirror } from "../mirror.ts";
|
|
10
16
|
|
|
11
17
|
describe("SpawnedChildrenMirror", () => {
|
|
@@ -65,3 +71,69 @@ describe("SpawnedChildrenMirror", () => {
|
|
|
65
71
|
expect(mirror.isResumable("rec-2")).toBe(true);
|
|
66
72
|
});
|
|
67
73
|
});
|
|
74
|
+
|
|
75
|
+
// ── [SR-4 接线] 子进程退出 → 取消该 pid 挂起的 dialog ──
|
|
76
|
+
//
|
|
77
|
+
// 原绑定点(宿主 spawn pi 子进程时代的 session-runner child close)随协议化消失;
|
|
78
|
+
// 新链路 = 引擎 host/childStateChanged(exited) → 本镜像 → notifyChildProcessExited。
|
|
79
|
+
describe("SpawnedChildrenMirror — 子进程退出取消挂起 dialog(SR-4 接线)", () => {
|
|
80
|
+
afterEach(() => {
|
|
81
|
+
registerActiveDialogQueue(undefined);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
/** 入队一个永不 settle 的 dialog(绑定 pid),返回其结果观察 promise。 */
|
|
85
|
+
function enqueuePending(queue: DialogGlobalQueue, pid: number, id: string): Promise<UiResponse> {
|
|
86
|
+
const req: UiRequest = { method: "select", id, title: `q-${id}` };
|
|
87
|
+
return queue.enqueue(req, () => new Promise<UiResponse>(() => {}), { child: { pid } });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
it("state=exited → 该 pid 的挂起 dialog resolve cancelled", async () => {
|
|
91
|
+
const queue = new DialogGlobalQueue();
|
|
92
|
+
registerActiveDialogQueue(queue);
|
|
93
|
+
const pending = enqueuePending(queue, 101, "req-1");
|
|
94
|
+
|
|
95
|
+
const mirror = new SpawnedChildrenMirror();
|
|
96
|
+
mirror.recordSpawned(101, "rec-1");
|
|
97
|
+
mirror.recordStateChanged({ pid: 101, recordId: "rec-1", state: "exited", killed: true, exitCode: 0 });
|
|
98
|
+
|
|
99
|
+
await expect(pending).resolves.toEqual({ cancelled: true });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("state=running 不触发取消(进程仍在跑,dialog 继续排队)", async () => {
|
|
103
|
+
const queue = new DialogGlobalQueue();
|
|
104
|
+
registerActiveDialogQueue(queue);
|
|
105
|
+
let settled = false;
|
|
106
|
+
void enqueuePending(queue, 102, "req-2").then(() => {
|
|
107
|
+
settled = true;
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const mirror = new SpawnedChildrenMirror();
|
|
111
|
+
mirror.recordStateChanged({ pid: 102, recordId: "rec-2", state: "running", killed: false });
|
|
112
|
+
await Promise.resolve();
|
|
113
|
+
|
|
114
|
+
expect(settled).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("killAll(引擎进程消亡)→ 逐 pid 取消各自挂起 dialog", async () => {
|
|
118
|
+
const queue = new DialogGlobalQueue();
|
|
119
|
+
registerActiveDialogQueue(queue);
|
|
120
|
+
const p1 = enqueuePending(queue, 201, "req-a");
|
|
121
|
+
const p2 = enqueuePending(queue, 202, "req-b");
|
|
122
|
+
|
|
123
|
+
const mirror = new SpawnedChildrenMirror();
|
|
124
|
+
mirror.recordSpawned(201, "rec-a");
|
|
125
|
+
mirror.recordSpawned(202, "rec-b");
|
|
126
|
+
mirror.killAll();
|
|
127
|
+
|
|
128
|
+
await expect(p1).resolves.toEqual({ cancelled: true });
|
|
129
|
+
await expect(p2).resolves.toEqual({ cancelled: true });
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("未登记队列(headless / 无 UI 通道宿主)→ exited 为 no-op,不抛", () => {
|
|
133
|
+
const mirror = new SpawnedChildrenMirror();
|
|
134
|
+
mirror.recordSpawned(301, "rec-3");
|
|
135
|
+
expect(() =>
|
|
136
|
+
mirror.recordStateChanged({ pid: 301, recordId: "rec-3", state: "exited", killed: true }),
|
|
137
|
+
).not.toThrow();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// UiRequest / UiResponse / UiRequestHandler / UiMethod 的 core 侧出口 =
|
|
8
8
|
// execution/dialog-queue.ts 的 SDK 再导出(W7 已切换,SSOT = SDK ui-types)——
|
|
9
9
|
// 本断言的 Core* 别名现在与 Sdk* 同源,保留断言形态防未来回退为本地定义后漂移。
|
|
10
|
+
// [H1 U5/U6] InteractAction / InteractResult 断言对已随 chat 域 interact 面退役删除。
|
|
10
11
|
|
|
11
12
|
import { describe, expect, it } from "vitest";
|
|
12
13
|
|
|
@@ -17,8 +18,6 @@ import type {
|
|
|
17
18
|
AgentOutcome as SdkAgentOutcome,
|
|
18
19
|
EngineCapabilities as SdkEngineCapabilities,
|
|
19
20
|
EngineHandleData as SdkEngineHandleData,
|
|
20
|
-
InteractAction as SdkInteractAction,
|
|
21
|
-
InteractResult as SdkInteractResult,
|
|
22
21
|
ProbeReport as SdkProbeReport,
|
|
23
22
|
SessionView as SdkSessionView,
|
|
24
23
|
UiRequest as SdkUiRequest,
|
|
@@ -37,8 +36,6 @@ import type {
|
|
|
37
36
|
AgentOutcome as CoreAgentOutcome,
|
|
38
37
|
EngineCapabilities as CoreEngineCapabilities,
|
|
39
38
|
EngineHandleData as CoreEngineHandleData,
|
|
40
|
-
InteractAction as CoreInteractAction,
|
|
41
|
-
InteractResult as CoreInteractResult,
|
|
42
39
|
ProbeReport as CoreProbeReport,
|
|
43
40
|
SessionView as CoreSessionView,
|
|
44
41
|
} from "../../types.ts";
|
|
@@ -54,8 +51,6 @@ type _CoreSdkAgentEvent = AssertMutuallyAssignable<CoreAgentEvent, SdkAgentEvent
|
|
|
54
51
|
type _CoreSdkAgentOutcome = AssertMutuallyAssignable<CoreAgentOutcome, SdkAgentOutcome>;
|
|
55
52
|
type _CoreSdkEngineCapabilities = AssertMutuallyAssignable<CoreEngineCapabilities, SdkEngineCapabilities>;
|
|
56
53
|
type _CoreSdkEngineHandleData = AssertMutuallyAssignable<CoreEngineHandleData, SdkEngineHandleData>;
|
|
57
|
-
type _CoreSdkInteractAction = AssertMutuallyAssignable<CoreInteractAction, SdkInteractAction>;
|
|
58
|
-
type _CoreSdkInteractResult = AssertMutuallyAssignable<CoreInteractResult, SdkInteractResult>;
|
|
59
54
|
type _CoreSdkProbeReport = AssertMutuallyAssignable<CoreProbeReport, SdkProbeReport>;
|
|
60
55
|
type _CoreSdkSessionView = AssertMutuallyAssignable<CoreSessionView, SdkSessionView>;
|
|
61
56
|
type _CoreSdkWorktreeHandle = AssertMutuallyAssignable<CoreWorktreeHandle, SdkWorktreeHandle>;
|
|
@@ -75,8 +70,6 @@ const _assertions: Array<true> = [
|
|
|
75
70
|
true as _CoreSdkAgentOutcome,
|
|
76
71
|
true as _CoreSdkEngineCapabilities,
|
|
77
72
|
true as _CoreSdkEngineHandleData,
|
|
78
|
-
true as _CoreSdkInteractAction,
|
|
79
|
-
true as _CoreSdkInteractResult,
|
|
80
73
|
true as _CoreSdkProbeReport,
|
|
81
74
|
true as _CoreSdkSessionView,
|
|
82
75
|
true as _CoreSdkWorktreeHandle,
|
|
@@ -89,7 +82,7 @@ const _assertions: Array<true> = [
|
|
|
89
82
|
|
|
90
83
|
describe("协议契约类型双向可赋值(编译期断言的运行时锚)", () => {
|
|
91
84
|
it("全部断言成立(漂移会在 tsc --noEmit 报 never → 本文件编译失败)", () => {
|
|
92
|
-
expect(_assertions).toHaveLength(
|
|
85
|
+
expect(_assertions).toHaveLength(12);
|
|
93
86
|
expect(_assertions.every((v) => v === true)).toBe(true);
|
|
94
87
|
});
|
|
95
88
|
});
|