@zhushanwen/subagent-core 0.7.1 → 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.
Files changed (176) hide show
  1. package/dist/{chunk-VURUAGMM.js → chunk-LZJOEDGQ.js} +603 -154
  2. package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
  3. package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
  4. package/dist/execution/engine/engine-discovery-scan.cjs +325 -154
  5. package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
  6. package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
  7. package/dist/execution/engine/engine-discovery-scan.js +1 -1
  8. package/dist/execution/relay-env.js +5 -4
  9. package/dist/index.cjs +9799 -7954
  10. package/dist/index.d.cts +1896 -1360
  11. package/dist/index.d.ts +1896 -1360
  12. package/dist/index.js +8952 -7513
  13. package/dist.bundle/index.cjs +7897 -6054
  14. package/package.json +5 -5
  15. package/src/__tests__/manifest-store.test.ts +16 -15
  16. package/src/__tests__/record-store-last-line.test.ts +12 -0
  17. package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
  18. package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
  19. package/src/core/logger.ts +1 -1
  20. package/src/execution/__tests__/alive-store.test.ts +117 -1
  21. package/src/execution/__tests__/batch-finalized.test.ts +206 -0
  22. package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
  23. package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
  24. package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
  25. package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
  26. package/src/execution/__tests__/delivery-methods.test.ts +90 -176
  27. package/src/execution/__tests__/dialog-queue.test.ts +70 -3
  28. package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
  29. package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
  30. package/src/execution/__tests__/execute-nesting.test.ts +8 -5
  31. package/src/execution/__tests__/execution-record.test.ts +8 -148
  32. package/src/execution/__tests__/finalize-record.test.ts +295 -129
  33. package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
  34. package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
  35. package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
  36. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
  37. package/src/execution/__tests__/idle-gc.test.ts +60 -0
  38. package/src/execution/__tests__/inflight-production-wiring.test.ts +223 -0
  39. package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
  40. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
  41. package/src/execution/__tests__/model-config-service.test.ts +111 -0
  42. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
  43. package/src/execution/__tests__/nested-visibility.test.ts +13 -1
  44. package/src/execution/__tests__/notify-ledger.test.ts +42 -1
  45. package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
  46. package/src/execution/__tests__/record-binding.test.ts +613 -0
  47. package/src/execution/__tests__/record-origin.test.ts +318 -0
  48. package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
  49. package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
  50. package/src/execution/__tests__/record-store.test.ts +72 -54
  51. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
  52. package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
  53. package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
  54. package/src/execution/__tests__/run-orchestration.test.ts +199 -0
  55. package/src/execution/__tests__/session-file-gc.test.ts +35 -4
  56. package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
  57. package/src/execution/__tests__/state-marker.test.ts +288 -0
  58. package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
  59. package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
  60. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
  61. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
  62. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
  63. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
  64. package/src/execution/__tests__/subagent-service.test.ts +17 -215
  65. package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
  66. package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
  67. package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
  68. package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
  69. package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
  70. package/src/execution/alive-store.ts +37 -28
  71. package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
  72. package/src/execution/config.ts +1 -1
  73. package/src/execution/conversation-continuation.ts +541 -0
  74. package/src/execution/dialog-queue.ts +60 -12
  75. package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
  76. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
  77. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
  78. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
  79. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
  80. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
  81. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +55 -53
  82. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
  83. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
  84. package/src/execution/engine/__tests__/inflight-snapshot.test.ts +180 -0
  85. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
  86. package/src/execution/engine/__tests__/registry.test.ts +0 -1
  87. package/src/execution/engine/__tests__/routing.test.ts +0 -1
  88. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
  89. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
  90. package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
  91. package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
  92. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
  93. package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
  94. package/src/execution/engine/client/engine-client.ts +111 -26
  95. package/src/execution/engine/client/mirror.ts +9 -0
  96. package/src/execution/engine/client/remote-engine.ts +52 -34
  97. package/src/execution/engine/client/reverse-router.ts +6 -27
  98. package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
  99. package/src/execution/engine/common/capability-gate.ts +27 -0
  100. package/src/execution/engine/common/data-dir.ts +3 -2
  101. package/src/execution/engine/common/errors.ts +1 -1
  102. package/src/execution/engine/common/journal-wiring.ts +18 -9
  103. package/src/execution/engine/common/run-signals.ts +83 -0
  104. package/src/execution/engine/d8-compat.ts +1 -1
  105. package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
  106. package/src/execution/engine/host/host-bridge.ts +26 -41
  107. package/src/execution/engine/host/pi-host-binding.ts +5 -5
  108. package/src/execution/engine/host/spawned-children.ts +1 -1
  109. package/src/execution/engine/inflight-snapshot.ts +92 -0
  110. package/src/execution/engine/port.ts +52 -54
  111. package/src/execution/engine/types.ts +11 -14
  112. package/src/execution/execution-record.ts +15 -47
  113. package/src/execution/finalize-record.ts +94 -197
  114. package/src/execution/idle-gc.ts +9 -3
  115. package/src/execution/lifecycle-manager.ts +25 -361
  116. package/src/execution/lifecycle-predicates.ts +22 -19
  117. package/src/execution/manifest-store.ts +40 -42
  118. package/src/execution/model-config-service.ts +1 -1
  119. package/src/execution/notifier.ts +39 -1
  120. package/src/execution/notify-host.ts +13 -4
  121. package/src/execution/path-encoding.ts +6 -0
  122. package/src/execution/record-entry.ts +20 -2
  123. package/src/execution/record-store.ts +925 -160
  124. package/src/execution/relay-env.ts +8 -3
  125. package/src/execution/round-supervisor/domain.ts +17 -5
  126. package/src/execution/round-supervisor/service-binding.test.ts +89 -53
  127. package/src/execution/round-supervisor/service-binding.ts +71 -21
  128. package/src/execution/round-supervisor/supervisor.ts +23 -9
  129. package/src/execution/service/record-access.ts +547 -0
  130. package/src/execution/service/record-lifecycle.ts +542 -0
  131. package/src/execution/service/run-orchestration.ts +1647 -0
  132. package/src/execution/service/service-bootstrap.ts +111 -0
  133. package/src/execution/service/service-constants.ts +27 -0
  134. package/src/execution/service/session-baselines.ts +401 -0
  135. package/src/execution/service/sync-collect-domain.ts +442 -0
  136. package/src/execution/service/workflow-dispatch.ts +567 -0
  137. package/src/execution/session-file-gc.ts +11 -8
  138. package/src/execution/session-reconstructor.ts +44 -1
  139. package/src/execution/sessions-index.ts +20 -3
  140. package/src/execution/settled-watchdog.ts +86 -43
  141. package/src/execution/state-marker.ts +516 -0
  142. package/src/execution/stream-sink.ts +1 -1
  143. package/src/execution/subagent-actions-core.ts +53 -31
  144. package/src/execution/subagent-service.ts +643 -2955
  145. package/src/execution/subprocess-agent-runner.ts +46 -323
  146. package/src/execution/types.ts +60 -43
  147. package/src/execution/ui-request-handler-factory.ts +5 -0
  148. package/src/execution/worktree-manager.ts +1 -1
  149. package/src/execution/worktree-registry.ts +12 -8
  150. package/src/index.ts +16 -5
  151. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
  152. package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
  153. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
  154. package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
  155. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
  156. package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
  157. package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
  158. package/src/orchestration/execute-agent-call.ts +1 -1
  159. package/src/orchestration/file-run-store.ts +1 -1
  160. package/src/orchestration/models/ports.ts +20 -13
  161. package/src/orchestration/models/types.ts +11 -13
  162. package/src/orchestration/run-snapshot.ts +18 -22
  163. package/src/orchestration/worker-message-pump.ts +33 -57
  164. package/src/shared/atomic-write.ts +10 -8
  165. package/dist/chunk-T2SYBW3I.js +0 -24
  166. package/dist/engine-discovery-scan-ocpM8FmI.d.cts +0 -1608
  167. package/dist/engine-discovery-scan-ocpM8FmI.d.ts +0 -1608
  168. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
  169. package/src/execution/__tests__/finalized-marker.test.ts +0 -104
  170. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  171. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
  172. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
  173. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  174. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
  175. package/src/execution/finalized-marker.ts +0 -70
  176. 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("../finalized-marker.ts", () => ({
92
- writeFinalized: vi.fn(),
93
- readFinalized: vi.fn(() => false),
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
- recoverTmpFiles = vi.fn(async () => []);
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 "../relay-env.ts";
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) + 领域对象", async () => {
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", true);
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", false);
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:externalInstance pid marker → another-process 文案", async () => {
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", externalInstance: { pid: 4321, id: "p-1", startedAt: 5 } })),
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
  });
@@ -29,7 +29,6 @@ vi.mock("../alive-store.ts", () => ({
29
29
  removeAliveMarker: vi.fn(),
30
30
  readAliveMarker: vi.fn(() => undefined),
31
31
  isProcessAlive: vi.fn(() => false),
32
- ALIVE_SOFT_TIMEOUT_MS: 3_600_000,
33
32
  findForeignLiveInstance: foreignLiveSpy,
34
33
  }));
35
34
 
@@ -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
- const kickOffChatRound = (
111
- service as unknown as Record<string, (...args: unknown[]) => void>
112
- )["kickOffChatRound"];
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.call(service, record, { task: "t", slug: "gate" }, identity, undefined, 1000);
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
- const kickOffChatRound = (
145
- service as unknown as Record<string, (...args: unknown[]) => void>
146
- )["kickOffChatRound"];
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.call(service, record, { task: "t", slug: "gate" }, identity, undefined, 1000);
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
- * ExecutionNestingContext),注入经 setBaseline 测试面。 */
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
- sessionRootId: string | null;
60
- execNesting: { setBaseline(state: { recordId: string | undefined; depth: number } | null): void };
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 = internals.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
  });