@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
|
@@ -88,9 +88,12 @@ vi.mock("../alive-store.ts", async (importOriginal) => {
|
|
|
88
88
|
};
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
vi.mock("../
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
vi.mock("../state-marker.ts", () => ({
|
|
92
|
+
writeFinalizedState: vi.fn(),
|
|
93
|
+
writeCancelledState: vi.fn(),
|
|
94
|
+
readStateMarker: vi.fn(() => undefined),
|
|
95
|
+
statStateStamp: vi.fn(() => null),
|
|
96
|
+
STATE_SIDECAR_EXT: ".state",
|
|
94
97
|
}));
|
|
95
98
|
|
|
96
99
|
vi.mock("../manifest-store.ts", () => {
|
|
@@ -98,7 +101,7 @@ vi.mock("../manifest-store.ts", () => {
|
|
|
98
101
|
writeManifest = vi.fn(async () => {});
|
|
99
102
|
readManifest = vi.fn(async () => null);
|
|
100
103
|
listAllSync = vi.fn(() => []);
|
|
101
|
-
|
|
104
|
+
sweepTmpFiles = vi.fn(async () => 0);
|
|
102
105
|
}
|
|
103
106
|
return { ManifestStore: vi.fn(function (_recordsDir: string) { return new FakeManifestStore(); }) };
|
|
104
107
|
});
|
|
@@ -107,7 +110,7 @@ import { spawn } from "node:child_process";
|
|
|
107
110
|
|
|
108
111
|
import { ModelConfigService } from "../model-config-service.ts";
|
|
109
112
|
import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
|
|
110
|
-
import { RELAY_ENV_NODE, RELAY_ENV_SCRIPT, RELAY_ENV_SOCKET } from "
|
|
113
|
+
import { RELAY_ENV_NODE, RELAY_ENV_SCRIPT, RELAY_ENV_SOCKET } from "@zhushanwen/subagent-engine-sdk";
|
|
111
114
|
import { SubagentService } from "../subagent-service.ts";
|
|
112
115
|
import { createBackgroundStream } from "../stream-sink.ts";
|
|
113
116
|
import type { ExtensionMode } from "../host-mode.ts";
|
|
@@ -18,7 +18,10 @@
|
|
|
18
18
|
// 读 Date.now()——本文件用 fake timers 固定时钟(now = 探针首跑实值 1788189209000),
|
|
19
19
|
// 使快照期望值与实测值逐字可比。
|
|
20
20
|
|
|
21
|
-
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
|
|
21
|
+
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
22
|
+
import * as fs from "node:fs";
|
|
23
|
+
import * as os from "node:os";
|
|
24
|
+
import * as path from "node:path";
|
|
22
25
|
|
|
23
26
|
import {
|
|
24
27
|
BG_MESSAGE,
|
|
@@ -38,6 +41,7 @@ import {
|
|
|
38
41
|
wrapForkFromPrompt,
|
|
39
42
|
} from "../subagent-actions-core.ts";
|
|
40
43
|
import { ResurrectDeniedError } from "../types.ts";
|
|
44
|
+
import { writeAliveMarker } from "../alive-store.ts";
|
|
41
45
|
import type {
|
|
42
46
|
ExecutionHandle,
|
|
43
47
|
ExecutionRecord,
|
|
@@ -49,6 +53,11 @@ import type { SubagentService } from "../subagent-service.ts";
|
|
|
49
53
|
// ── 时钟固定(duration 快照确定性,见文件头)──
|
|
50
54
|
const FROZEN_NOW = 1788189209000;
|
|
51
55
|
|
|
56
|
+
/** [U1/A4] 「异进程且存活」的确定性模拟 pid:1 号进程(launchd/init)必然存在且非
|
|
57
|
+
* 本测试进程——kill(1, 0) 对普通用户返回 EPERM,isProcessAlive 按「存在但无权限」
|
|
58
|
+
* 保守判活(self-pid 排除后不能再以本进程 pid 模拟异进程实例,同 cold-lookup.test.ts)。 */
|
|
59
|
+
const FOREIGN_LIVE_PID = 1;
|
|
60
|
+
|
|
52
61
|
beforeAll(() => {
|
|
53
62
|
vi.useFakeTimers({ now: FROZEN_NOW });
|
|
54
63
|
});
|
|
@@ -144,6 +153,9 @@ function makeService(over: Record<string, unknown> = {}): SubagentService {
|
|
|
144
153
|
getRecordForAction: vi.fn(),
|
|
145
154
|
closeSubagent: vi.fn(),
|
|
146
155
|
deliverChatMessage: vi.fn(),
|
|
156
|
+
// [H1 U2 / D5 双写点①] messageHandler 升级 gate 判据读 service.canUpgradeToConversation
|
|
157
|
+
//(stub 缺省放行 = pi 默认引擎语义;gate 拒绝面专项见 conversation-continuation.test.ts)。
|
|
158
|
+
canUpgradeToConversation: vi.fn(() => true),
|
|
147
159
|
// [U2] startHandler 缺省 collect 解析读真实 config(偏差#3 接线):stub 缺省 async
|
|
148
160
|
//(本文件不测 collect 语义,专项见 start-collect-guard.test.ts)。
|
|
149
161
|
getCollectSyncDefault: vi.fn(() => "async" as const),
|
|
@@ -152,6 +164,9 @@ function makeService(over: Record<string, unknown> = {}): SubagentService {
|
|
|
152
164
|
return {
|
|
153
165
|
execute: m.execute,
|
|
154
166
|
cancel: m.cancel,
|
|
167
|
+
// [H1 U2 / D5 双写点①] messageHandler 升级 gate 判据经平铺访问器(真实 service 为
|
|
168
|
+
// 平铺方法,非 queries/chatActions 聚合面成员),stub 须同构挂载。
|
|
169
|
+
canUpgradeToConversation: m.canUpgradeToConversation,
|
|
155
170
|
// [U2 偏差#3 接线] startHandler 经平铺访问器读 config 缺省 collect(真实 service
|
|
156
171
|
// 为平铺方法 subagent-service.ts:1786,非 queries 聚合面成员),stub 须同构挂载。
|
|
157
172
|
getCollectSyncDefault: m.getCollectSyncDefault,
|
|
@@ -433,7 +448,7 @@ describe("⛔4 listHandler(limit 夹紧 + 过滤 + enrich,快照 = pi-sw 实
|
|
|
433
448
|
],
|
|
434
449
|
},
|
|
435
450
|
});
|
|
436
|
-
expect(collectRecords).toHaveBeenCalledWith(20, "all");
|
|
451
|
+
expect(collectRecords).toHaveBeenCalledWith(20, "all", false);
|
|
437
452
|
// light record(getFullRecord undefined)回退原样投影
|
|
438
453
|
expect(getFullRecord).toHaveBeenCalledWith("bg-2");
|
|
439
454
|
});
|
|
@@ -441,17 +456,17 @@ describe("⛔4 listHandler(limit 夹紧 + 过滤 + enrich,快照 = pi-sw 实
|
|
|
441
456
|
it("缺省 → collectRecords(20,'running')", () => {
|
|
442
457
|
const collectRecords = vi.fn(() => [] as SubagentRecord[]);
|
|
443
458
|
listHandler(makeService({ collectRecords }), undefined);
|
|
444
|
-
expect(collectRecords).toHaveBeenCalledWith(20, "running");
|
|
459
|
+
expect(collectRecords).toHaveBeenCalledWith(20, "running", false);
|
|
445
460
|
});
|
|
446
461
|
|
|
447
462
|
it("limit 夹紧:500 → 100 上限;0 → 1 下限(非默认值)", () => {
|
|
448
463
|
const high = vi.fn(() => [] as SubagentRecord[]);
|
|
449
464
|
listHandler(makeService({ collectRecords: high }), { includeFinished: true, limit: 500 });
|
|
450
|
-
expect(high).toHaveBeenCalledWith(100, "all");
|
|
465
|
+
expect(high).toHaveBeenCalledWith(100, "all", false);
|
|
451
466
|
|
|
452
467
|
const low = vi.fn(() => [] as SubagentRecord[]);
|
|
453
468
|
listHandler(makeService({ collectRecords: low }), { includeFinished: true, limit: 0 });
|
|
454
|
-
expect(low).toHaveBeenCalledWith(1, "all");
|
|
469
|
+
expect(low).toHaveBeenCalledWith(1, "all", false);
|
|
455
470
|
|
|
456
471
|
expect(DEFAULT_LIST_LIMIT).toBe(20);
|
|
457
472
|
expect(MAX_LIST_LIMIT).toBe(100);
|
|
@@ -473,7 +488,7 @@ describe("⛔4 cancelHandler(守卫 + 归属判定 + CAS 失败映射,快照
|
|
|
473
488
|
expect(await errOf(() => cancelHandler(makeService(), { subagentId: "bg-x" }))).toEqual({
|
|
474
489
|
errorName: "Error",
|
|
475
490
|
message:
|
|
476
|
-
'No subagent record with id "bg-x". It may have finished — use action:\'list\' with includeFinished:true to verify.',
|
|
491
|
+
'No subagent record with id "bg-x". It may have finished — use action:\'list\' with includeFinished:true to verify (add includeWorkflow:true to also see workflow-dispatched subagents).',
|
|
477
492
|
});
|
|
478
493
|
});
|
|
479
494
|
|
|
@@ -505,7 +520,7 @@ describe("⛔4 cancelHandler(守卫 + 归属判定 + CAS 失败映射,快照
|
|
|
505
520
|
).toEqual({
|
|
506
521
|
errorName: "Error",
|
|
507
522
|
message:
|
|
508
|
-
'No subagent record with id "bg-9". It may have finished — use action:\'list\' with includeFinished:true to verify.',
|
|
523
|
+
'No subagent record with id "bg-9". It may have finished — use action:\'list\' with includeFinished:true to verify (add includeWorkflow:true to also see workflow-dispatched subagents).',
|
|
509
524
|
});
|
|
510
525
|
});
|
|
511
526
|
|
|
@@ -613,7 +628,7 @@ describe("⛔4 messageHandler(守卫 + upgrade + 投递,快照 = pi-sw 实
|
|
|
613
628
|
});
|
|
614
629
|
});
|
|
615
630
|
|
|
616
|
-
it("chatMode record → deliverChatMessage(text trim + interrupt
|
|
631
|
+
it("chatMode record → deliverChatMessage(text trim) + 领域对象([H1 U6] interrupt 退役)", async () => {
|
|
617
632
|
const deliverChatMessage = vi.fn(async () => {});
|
|
618
633
|
const chatRecord = makeExecRecord({ id: "bg-1", chatMode: true, slug: "src-slug" });
|
|
619
634
|
const r = await messageHandler(
|
|
@@ -626,7 +641,7 @@ describe("⛔4 messageHandler(守卫 + upgrade + 投递,快照 = pi-sw 实
|
|
|
626
641
|
slug: "src-slug",
|
|
627
642
|
response: { delivered: true },
|
|
628
643
|
});
|
|
629
|
-
expect(deliverChatMessage).toHaveBeenCalledWith(chatRecord, "go on"
|
|
644
|
+
expect(deliverChatMessage).toHaveBeenCalledWith(chatRecord, "go on");
|
|
630
645
|
});
|
|
631
646
|
|
|
632
647
|
it("one-shot upgrade:非 chatMode running record 收 message → 置位 chatMode 后投递", async () => {
|
|
@@ -637,7 +652,27 @@ describe("⛔4 messageHandler(守卫 + upgrade + 投递,快照 = pi-sw 实
|
|
|
637
652
|
{ subagentId: "bg-1", text: "hi" },
|
|
638
653
|
);
|
|
639
654
|
expect(upgradeRec.chatMode).toBe(true);
|
|
640
|
-
expect(deliverChatMessage).toHaveBeenCalledWith(upgradeRec, "hi"
|
|
655
|
+
expect(deliverChatMessage).toHaveBeenCalledWith(upgradeRec, "hi");
|
|
656
|
+
});
|
|
657
|
+
|
|
658
|
+
it("[H1 U2 / D5 双写点①] one-shot upgrade gate 拒绝:unsupported 引擎(canUpgradeToConversation=false)→ 硬拒 + fork/重派指引,chatMode 不置位", async () => {
|
|
659
|
+
const deliverChatMessage = vi.fn(async () => {});
|
|
660
|
+
const zcodeRec = makeExecRecord({ id: "bg-z", chatMode: false, status: "running", engine: "zcode" });
|
|
661
|
+
const err = await errOf(() =>
|
|
662
|
+
messageHandler(
|
|
663
|
+
makeService({
|
|
664
|
+
getRecordForAction: vi.fn(() => zcodeRec),
|
|
665
|
+
deliverChatMessage,
|
|
666
|
+
canUpgradeToConversation: vi.fn(() => false),
|
|
667
|
+
}),
|
|
668
|
+
{ subagentId: "bg-z", text: "hi" },
|
|
669
|
+
),
|
|
670
|
+
);
|
|
671
|
+
// 文案 = engineConversationUpgradeUnsupportedError 单源(错误码前缀 + 拒绝依据)
|
|
672
|
+
expect(err.errorName).toBe("EngineError");
|
|
673
|
+
expect(err.message).toContain("cannot be upgraded to a resumable conversation");
|
|
674
|
+
expect(zcodeRec.chatMode).toBe(false);
|
|
675
|
+
expect(deliverChatMessage).not.toHaveBeenCalled();
|
|
641
676
|
});
|
|
642
677
|
|
|
643
678
|
it("getRecordForAction 拒绝 + 无终态快照 → 原错误透传(文案最准原则)", async () => {
|
|
@@ -694,14 +729,14 @@ describe("⛔4 messageHandler(守卫 + upgrade + 投递,快照 = pi-sw 实
|
|
|
694
729
|
message:
|
|
695
730
|
"subagent bg-1 was deliberately closed by user (closedReason: user-close) — " +
|
|
696
731
|
"it cannot be messaged or resumed; nothing can reattach to it. " +
|
|
697
|
-
"Recovery: start a new subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.",
|
|
732
|
+
"Recovery: start a new subagent (action:'start'); use action:'list' with includeFinished:true to review its final output (add includeWorkflow:true to also see workflow-dispatched subagents).",
|
|
698
733
|
});
|
|
699
734
|
expect(await errOf(() => messageHandler(mkSvc("cancelled"), { subagentId: "bg-1", text: "hi" }))).toEqual({
|
|
700
735
|
errorName: "Error",
|
|
701
736
|
message:
|
|
702
737
|
"subagent bg-1 was deliberately closed by user (closedReason: cancelled) — " +
|
|
703
738
|
"it cannot be messaged or resumed; nothing can reattach to it. " +
|
|
704
|
-
"Recovery: start a new subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.",
|
|
739
|
+
"Recovery: start a new subagent (action:'start'); use action:'list' with includeFinished:true to review its final output (add includeWorkflow:true to also see workflow-dispatched subagents).",
|
|
705
740
|
});
|
|
706
741
|
});
|
|
707
742
|
|
|
@@ -800,6 +835,16 @@ describe("⛔4 closeHandler(force 语义透传,快照 = pi-sw 实测)", ()
|
|
|
800
835
|
// forkFromHandler(守卫链 1–6)
|
|
801
836
|
// ============================================================
|
|
802
837
|
describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快照 = pi-sw 实测)", () => {
|
|
838
|
+
// [U4b/E2] 双宿主探针 fixture 目录:守卫 3 现查探针读真实 .alive 侧车(不再读
|
|
839
|
+
// rec.externalInstance 缓存字段),用临时目录落盘驱动,自建自删。
|
|
840
|
+
let forkDir: string;
|
|
841
|
+
beforeEach(() => {
|
|
842
|
+
forkDir = fs.mkdtempSync(path.join(os.tmpdir(), "actions-core-fork-"));
|
|
843
|
+
});
|
|
844
|
+
afterEach(() => {
|
|
845
|
+
fs.rmSync(forkDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
|
846
|
+
});
|
|
847
|
+
|
|
803
848
|
function makeForkService(source: SubagentRecord | undefined, execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
|
|
804
849
|
mode: "background",
|
|
805
850
|
subagentId: "bg-new-1",
|
|
@@ -838,15 +883,21 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
|
|
|
838
883
|
errorName: "Error",
|
|
839
884
|
message:
|
|
840
885
|
'No subagent record with id "bg-404". It may never have existed or been garbage-collected — ' +
|
|
841
|
-
"use action:'list' with includeFinished:true to verify the id.",
|
|
886
|
+
"use action:'list' with includeFinished:true to verify the id (add includeWorkflow:true to also see workflow-dispatched subagents).",
|
|
842
887
|
});
|
|
843
888
|
});
|
|
844
889
|
|
|
845
|
-
it("守卫 3
|
|
890
|
+
it("守卫 3:异进程活实例(.alive 恒活外部 pid)→ another-process 文案(双宿主形态)", async () => {
|
|
891
|
+
// [U4b/E2] 双宿主形态:宿主 A 持有中的 record(.alive = A 进程的活 pid 声明)被
|
|
892
|
+
// 宿主 B 冷查重建,B fork-from 该源时守卫 3 现查探针命中 → 拒绝(源仍在异进程
|
|
893
|
+
// 运行,接续会读到半截历史)。[U1/A4] self-pid 排除后「异进程」不能用本测试进程
|
|
894
|
+
// pid 模拟,改恒活外部 pid 1(launchd/init:kill(1,0) → EPERM → isProcessAlive 判活)。
|
|
895
|
+
const sessionFile = path.join(forkDir, "sess-foreign.jsonl");
|
|
896
|
+
writeAliveMarker(sessionFile, { pid: FOREIGN_LIVE_PID, id: "bg-1", startedAt: 5 });
|
|
846
897
|
expect(
|
|
847
898
|
await errOf(() =>
|
|
848
899
|
forkFromHandler(
|
|
849
|
-
makeForkService(makeRec({ id: "bg-1",
|
|
900
|
+
makeForkService(makeRec({ id: "bg-1", status: "running", sessionFile })),
|
|
850
901
|
{ sourceSubagentId: "bg-1" },
|
|
851
902
|
),
|
|
852
903
|
),
|
|
@@ -885,7 +936,7 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
|
|
|
885
936
|
message:
|
|
886
937
|
`subagent bg-1 was deliberately closed by user (closedReason: ${closedReason}) — ` +
|
|
887
938
|
"deliberately-closed records cannot be resumed or branched from; nothing can reattach to them. " +
|
|
888
|
-
"Recovery: start a fresh subagent (action:'start'); use action:'list' with includeFinished:true to review its final output.",
|
|
939
|
+
"Recovery: start a fresh subagent (action:'start'); use action:'list' with includeFinished:true to review its final output (add includeWorkflow:true to also see workflow-dispatched subagents).",
|
|
889
940
|
});
|
|
890
941
|
}
|
|
891
942
|
});
|
|
@@ -90,7 +90,7 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
90
90
|
});
|
|
91
91
|
|
|
92
92
|
it("kickOffChatRound 应答回注不注入 parent-new closed records(notify 门白名单)", async () => {
|
|
93
|
-
const { agentDir, service, pi } = setup();
|
|
93
|
+
const { agentDir, service, store, pi } = setup();
|
|
94
94
|
clearEngines();
|
|
95
95
|
const fake = registerFakePiEngine();
|
|
96
96
|
const record = createRecord("sa-gate-new", {
|
|
@@ -103,19 +103,28 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
103
103
|
rootSessionId: "root-session",
|
|
104
104
|
controller: new AbortController(),
|
|
105
105
|
});
|
|
106
|
-
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的 kickOffChatRound
|
|
106
|
+
// 模拟 disposeAllRecords 先行编排性关闭后,迟到的 kickOffChatRound 应答回注。
|
|
107
|
+
// [H4/U5 适配] 终态形态对齐生产:disposeAllRecords 走 markFinalized(status
|
|
108
|
+
// closed + endedAt + archive)——只设 closedReason 不设 status 会让 settle 路径
|
|
109
|
+
// tryTransition(running→closed) 抢成赢家覆写 user-close,门判据随之漂移;record
|
|
110
|
+
// 补 register 进 store 对齐「store 外 record 无编排性关闭可达」的生产形态。
|
|
111
|
+
record.status = "closed";
|
|
112
|
+
record.endedAt = Date.now();
|
|
107
113
|
record.closedReason = "parent-new";
|
|
114
|
+
store.register(record);
|
|
108
115
|
// [W3] 轮次编排入口 kickOffChatRound(私有,bracket 调用先例)——协议 run 发起后
|
|
109
116
|
// 挂起,编排性关闭先行,再模拟引擎应答(迟到回注被门拦)。
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
// [R4 深绑改写] kickOffChatRound 本体已迁 RunOrchestration 聚合——bracket 路径
|
|
118
|
+
// 改经聚合实例(断言对象与强度不变;this 绑定 = 聚合实例,即方法真实宿主)。
|
|
119
|
+
const orchestration = (
|
|
120
|
+
service as unknown as { runOrchestration: { kickOffChatRound: (...args: unknown[]) => void } }
|
|
121
|
+
).runOrchestration;
|
|
113
122
|
const identity = {
|
|
114
123
|
agent: "general-purpose",
|
|
115
124
|
agentConfig: undefined,
|
|
116
125
|
resolved: { model: { id: "model", name: "Model", provider: "test", reasoning: false }, thinkingLevel: undefined },
|
|
117
126
|
};
|
|
118
|
-
kickOffChatRound
|
|
127
|
+
orchestration.kickOffChatRound(record, { task: "t", slug: "gate" }, identity, undefined, 1000);
|
|
119
128
|
await vi.waitFor(() => expect(fake.runs.length).toBe(1));
|
|
120
129
|
fake.runs[0]!.settle({ content: "late round text" });
|
|
121
130
|
await Promise.resolve();
|
|
@@ -127,7 +136,7 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
127
136
|
});
|
|
128
137
|
|
|
129
138
|
it("kickOffChatRound 应答回注对真实失败关闭(gc)仍通知", async () => {
|
|
130
|
-
const { agentDir, service, pi } = setup();
|
|
139
|
+
const { agentDir, service, store, pi } = setup();
|
|
131
140
|
clearEngines();
|
|
132
141
|
const fake = registerFakePiEngine();
|
|
133
142
|
const record = createRecord("sa-gate-gc", {
|
|
@@ -140,16 +149,23 @@ describe("T4① notify gate closedReason whitelist", () => {
|
|
|
140
149
|
rootSessionId: "root-session",
|
|
141
150
|
controller: new AbortController(),
|
|
142
151
|
});
|
|
152
|
+
// 同上 [H4/U5 适配]:失败终态化(closed/gc/endedAt)+ register 进 store 的完整
|
|
153
|
+
// 生产形态——迟到回注按 CAS 输家路径走,门判据读真实 gc 关闭原因。
|
|
154
|
+
record.status = "closed";
|
|
155
|
+
record.endedAt = Date.now();
|
|
143
156
|
record.closedReason = "gc";
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
157
|
+
store.register(record);
|
|
158
|
+
// [R4 深绑改写] kickOffChatRound 本体已迁 RunOrchestration 聚合——bracket 路径
|
|
159
|
+
// 改经聚合实例(断言对象与强度不变;this 绑定 = 聚合实例,即方法真实宿主)。
|
|
160
|
+
const orchestration = (
|
|
161
|
+
service as unknown as { runOrchestration: { kickOffChatRound: (...args: unknown[]) => void } }
|
|
162
|
+
).runOrchestration;
|
|
147
163
|
const identity = {
|
|
148
164
|
agent: "general-purpose",
|
|
149
165
|
agentConfig: undefined,
|
|
150
166
|
resolved: { model: { id: "model", name: "Model", provider: "test", reasoning: false }, thinkingLevel: undefined },
|
|
151
167
|
};
|
|
152
|
-
kickOffChatRound
|
|
168
|
+
orchestration.kickOffChatRound(record, { task: "t", slug: "gate" }, identity, undefined, 1000);
|
|
153
169
|
await vi.waitFor(() => expect(fake.runs.length).toBe(1));
|
|
154
170
|
fake.runs[0]!.settle({ content: "round" });
|
|
155
171
|
await vi.waitFor(() => expect(pi.sendMessage).toHaveBeenCalled());
|
|
@@ -53,11 +53,16 @@ function makePi(): PiLike {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/** 暴露私有字段供测试注入身份。[D3-⑤] 基线住进 execNesting(公共层
|
|
56
|
-
*
|
|
56
|
+
* ExecutionNestingContext),注入经 setBaseline 测试面。
|
|
57
|
+
* [R1 深绑改写·清单③ SI 形态] sessionRootId/execNesting 随域 #2 聚合迁入
|
|
58
|
+
* SessionBaselines——ServiceInternals 重定义为壳 baselines 聚合路径(store 仍为壳
|
|
59
|
+
* #1 留壳字段,路径不变);断言对象与强度不变,路径对齐终态结构。 */
|
|
57
60
|
interface ServiceInternals {
|
|
58
61
|
store: RecordStore;
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
baselines: {
|
|
63
|
+
sessionRootId: string | null;
|
|
64
|
+
execNesting: { setBaseline(state: { recordId: string | undefined; depth: number } | null): void };
|
|
65
|
+
};
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
/** 构造一个 chatMode idle record(message/close action 的典型目标态)。 */
|
|
@@ -97,7 +102,8 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
97
102
|
service.initSession({ pi: makePi(), sessionId: "root-session" });
|
|
98
103
|
internals = service as unknown as ServiceInternals;
|
|
99
104
|
store = internals.store;
|
|
100
|
-
sessionRootId
|
|
105
|
+
// [R1 深绑改写] sessionRootId 经 baselines 聚合路径读取(原 internals.sessionRootId)。
|
|
106
|
+
sessionRootId = internals.baselines.sessionRootId!;
|
|
101
107
|
});
|
|
102
108
|
|
|
103
109
|
afterEach(() => {
|
|
@@ -110,7 +116,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
110
116
|
it("身份=sa-A(直接父)→ 放行操作孙级 record B(parentRecordId=sa-A)", () => {
|
|
111
117
|
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
112
118
|
store.register(recordB);
|
|
113
|
-
internals.execNesting.setBaseline(PARENT_A);
|
|
119
|
+
internals.baselines.execNesting.setBaseline(PARENT_A);
|
|
114
120
|
|
|
115
121
|
expect(service.chatActions.getRecordForAction("sa-B")).toBe(recordB);
|
|
116
122
|
});
|
|
@@ -120,7 +126,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
120
126
|
it("身份=undefined(主进程)拒绝孙级 record B,错误含 'direct parent' + 'parent=sa-A'", () => {
|
|
121
127
|
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
122
128
|
store.register(recordB);
|
|
123
|
-
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
129
|
+
internals.baselines.execNesting.setBaseline(MAIN_PROCESS);
|
|
124
130
|
|
|
125
131
|
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
126
132
|
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
@@ -129,7 +135,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
129
135
|
it("身份=undefined(主进程)放行顶层 record(parentRecordId=undefined 视为根层)", () => {
|
|
130
136
|
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "running" });
|
|
131
137
|
store.register(topRecord);
|
|
132
|
-
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
138
|
+
internals.baselines.execNesting.setBaseline(MAIN_PROCESS);
|
|
133
139
|
|
|
134
140
|
expect(service.chatActions.getRecordForAction("sa-top")).toBe(topRecord);
|
|
135
141
|
});
|
|
@@ -139,7 +145,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
139
145
|
it("身份=sa-B(兄弟进程)拒绝孙级 record B(parentRecordId=sa-A ≠ baseline sa-B)", () => {
|
|
140
146
|
const recordB = makeRecord("sa-B", sessionRootId, { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
141
147
|
store.register(recordB);
|
|
142
|
-
internals.execNesting.setBaseline(SIBLING_B);
|
|
148
|
+
internals.baselines.execNesting.setBaseline(SIBLING_B);
|
|
143
149
|
|
|
144
150
|
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/direct parent/);
|
|
145
151
|
expect(() => service.chatActions.getRecordForAction("sa-B")).toThrow(/see \/subagents list, parent=sa-A/);
|
|
@@ -150,7 +156,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
150
156
|
it("子进程(baseline=sa-A)拒绝顶层 record(身份缺省视为根层,仅主进程可操作)", () => {
|
|
151
157
|
const topRecord = makeRecord("sa-top", sessionRootId, { parentRecordId: undefined, depth: 0, status: "running" });
|
|
152
158
|
store.register(topRecord);
|
|
153
|
-
internals.execNesting.setBaseline(PARENT_A);
|
|
159
|
+
internals.baselines.execNesting.setBaseline(PARENT_A);
|
|
154
160
|
|
|
155
161
|
expect(() => service.chatActions.getRecordForAction("sa-top")).toThrow(/direct parent/);
|
|
156
162
|
});
|
|
@@ -158,7 +164,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
158
164
|
it("P7 场景:主进程拒绝曾孙 record C(parentRecordId=sa-B),错误含 baseline=root + parent=sa-B", () => {
|
|
159
165
|
const recordC = makeRecord("sa-C", sessionRootId, { parentRecordId: "sa-B", depth: 3, status: "running" });
|
|
160
166
|
store.register(recordC);
|
|
161
|
-
internals.execNesting.setBaseline(MAIN_PROCESS);
|
|
167
|
+
internals.baselines.execNesting.setBaseline(MAIN_PROCESS);
|
|
162
168
|
|
|
163
169
|
const fn = () => service.chatActions.getRecordForAction("sa-C");
|
|
164
170
|
expect(fn).toThrow(/direct parent/);
|
|
@@ -170,7 +176,7 @@ describe("[v4 A-5 / P7] getRecordForAction 直接父校验", () => {
|
|
|
170
176
|
// 跨 session 树的 record:rootSessionId 不匹配 → 走首个 throw(not found or not owned)
|
|
171
177
|
const foreignRecord = makeRecord("sa-foreign", "other-session", { parentRecordId: "sa-A", depth: 2, status: "running" });
|
|
172
178
|
store.register(foreignRecord);
|
|
173
|
-
internals.execNesting.setBaseline(PARENT_A);
|
|
179
|
+
internals.baselines.execNesting.setBaseline(PARENT_A);
|
|
174
180
|
|
|
175
181
|
expect(() => service.chatActions.getRecordForAction("sa-foreign")).toThrow(/not found or not owned/);
|
|
176
182
|
});
|