@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
@@ -0,0 +1,318 @@
1
+ // src/execution/__tests__/record-origin.test.ts
2
+ //
3
+ // record 来源身份 origin/parentRunId(H2 W1,设计 subagent-workflow-record-unification
4
+ // §3.3 D1)持久化链 + 查询面 + 治理面负向规格。
5
+ //
6
+ // 锁四件事:
7
+ // 1. 持久化链往返保真:register 落盘(recordToSubagent → toSubagentRecordEntry →
8
+ // 真实 JSONL 序列化)→ scanLastRecordEntries 重建(collectLastRecordEntries →
9
+ // rebuildEntryRecord 真实路径)→ 两字段不丢。禁手工构造对象绕过 schema——entry
10
+ // data 一律来自 store.register 的真实落盘产物。
11
+ // 2. 缺省负向:无 origin 的存量 record 落盘产物不含 origin 键(零迁移),重建后
12
+ // origin === undefined(= "tool" 语义)。
13
+ // 3. 查询面:collectRecords 缺省过滤 origin==="workflow"(includeWorkflow 缺省
14
+ // false)、includeWorkflow:true 放行;collectRecordsByParentRunId 按 run id
15
+ // 精确列 record(内存 ∪ 磁盘重建口径,不过滤 origin)。
16
+ // 4. 治理面负向保证(D1⑥):recoverEntryOnlyOrphans / 重建投影不因 origin 过滤——
17
+ // workflow 来源的 entry-only 孤儿照样被终态化收敛,且终态 entry 保留 origin。
18
+
19
+ import * as fs from "node:fs";
20
+ import * as os from "node:os";
21
+ import * as path from "node:path";
22
+
23
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
24
+
25
+ const { loggerMock } = vi.hoisted(() => ({
26
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
27
+ }));
28
+ vi.mock("../../core/logger.ts", () => ({
29
+ getLogger: () => loggerMock,
30
+ }));
31
+
32
+ const { saveIndexMock } = vi.hoisted(() => ({
33
+ saveIndexMock: vi.fn(() => Promise.resolve()),
34
+ }));
35
+ vi.mock("../sessions-index.ts", async (importOriginal) => {
36
+ const actual = await importOriginal<typeof import("../sessions-index.ts")>();
37
+ return { ...actual, saveIndex: saveIndexMock };
38
+ });
39
+
40
+ import { createRecord } from "../execution-record.ts";
41
+ import { SUBAGENT_RECORD_CUSTOM_TYPE, toSubagentRecordEntry } from "../record-entry.ts";
42
+ import type { SubagentRecordEntryData } from "../record-entry.ts";
43
+ import { RecordStore } from "../record-store.ts";
44
+ import type { ExecutionRecord } from "../types.ts";
45
+
46
+ /** 构造 ExecutionRecord(base 默认 running one-shot,over 覆盖任意字段)。 */
47
+ function makeRecord(over: Partial<ExecutionRecord> = {}): ExecutionRecord {
48
+ const base = createRecord("bg-origin", {
49
+ agent: "worker",
50
+ model: "m",
51
+ mode: "background",
52
+ task: "t",
53
+ slug: "origin-test",
54
+ startedAt: 1000,
55
+ rootSessionId: "sess-origin",
56
+ // 对齐生产 register 路径(one-shot 显式 false)
57
+ chatMode: false,
58
+ });
59
+ return { ...base, ...over };
60
+ }
61
+
62
+ /** 捕获 register/archive/reportRecordTransition 落盘 entry 的 fake pi。 */
63
+ function makeCapturePi(captured: SubagentRecordEntryData[]): { appendEntry: (customType: string, data: unknown) => void } {
64
+ return {
65
+ appendEntry: (customType: string, data: unknown) => {
66
+ if (customType !== SUBAGENT_RECORD_CUSTOM_TYPE) return;
67
+ captured.push(data as SubagentRecordEntryData);
68
+ },
69
+ };
70
+ }
71
+
72
+ /** 把捕获的 entry data 写成主 session JSONL 行(pi appendEntry 的落盘产物形态)。 */
73
+ function writeMainSessionFile(filePath: string, entries: SubagentRecordEntryData[]): void {
74
+ const lines = entries.map((data) =>
75
+ JSON.stringify({
76
+ type: "custom",
77
+ id: `entry-${data.id}-${entries.indexOf(data)}`,
78
+ parentId: null,
79
+ timestamp: new Date().toISOString(),
80
+ customType: SUBAGENT_RECORD_CUSTOM_TYPE,
81
+ data,
82
+ }),
83
+ );
84
+ fs.writeFileSync(filePath, `${lines.join("\n")}\n`, "utf-8");
85
+ }
86
+
87
+ describe("record origin/parentRunId 持久化链(H2 W1)", () => {
88
+ let rootDir: string;
89
+ let tmpDir: string;
90
+
91
+ beforeEach(() => {
92
+ rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "record-origin-test-"));
93
+ tmpDir = path.join(rootDir, "sessions");
94
+ fs.mkdirSync(tmpDir);
95
+ });
96
+ afterEach(() => {
97
+ fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
98
+ });
99
+
100
+ it("entry 往返保真:origin=workflow + parentRunId 经真实落盘/重建后两字段不丢", () => {
101
+ const store = new RecordStore(tmpDir);
102
+ const captured: SubagentRecordEntryData[] = [];
103
+ store.setPi(makeCapturePi(captured));
104
+
105
+ const rec = makeRecord({ id: "wf-step-1", origin: "workflow", parentRunId: "wf-run-1" });
106
+ store.register(rec);
107
+ expect(captured).toHaveLength(1);
108
+ expect(captured[0].origin).toBe("workflow");
109
+ expect(captured[0].parentRunId).toBe("wf-run-1");
110
+
111
+ // 真实重建路径:落盘产物 → 主 session JSONL → scanLastRecordEntries
112
+ const mainFile = path.join(rootDir, "main.jsonl");
113
+ writeMainSessionFile(mainFile, captured);
114
+ const rebuilt = store.scanLastRecordEntries(mainFile);
115
+ const hit = rebuilt.find((r) => r.id === "wf-step-1");
116
+ expect(hit).toBeDefined();
117
+ expect(hit?.origin).toBe("workflow");
118
+ expect(hit?.parentRunId).toBe("wf-run-1");
119
+ });
120
+
121
+ it("缺省负向:存量 record(无 origin)落盘产物不含新键(零迁移),重建后 origin undefined(=tool 语义)", () => {
122
+ const store = new RecordStore(tmpDir);
123
+ const captured: SubagentRecordEntryData[] = [];
124
+ store.setPi(makeCapturePi(captured));
125
+
126
+ store.register(makeRecord({ id: "legacy-1" }));
127
+ expect(captured).toHaveLength(1);
128
+ // 零迁移:JSON 序列化产物不含 origin/parentRunId 键(undefined 自然缺省)。
129
+ // 必须断言序列化后形态——内存对象保留 undefined 键名(record-store.test.ts 同坑:
130
+ // 真实 JSONL 丢 undefined 值),键级/子串断言都会误判。
131
+ const persistedKeys = Object.keys(JSON.parse(JSON.stringify(captured[0])) as Record<string, unknown>);
132
+ expect(persistedKeys).not.toContain("origin");
133
+ expect(persistedKeys).not.toContain("parentRunId");
134
+
135
+ const mainFile = path.join(rootDir, "main.jsonl");
136
+ writeMainSessionFile(mainFile, captured);
137
+ const rebuilt = store.scanLastRecordEntries(mainFile);
138
+ const hit = rebuilt.find((r) => r.id === "legacy-1");
139
+ expect(hit).toBeDefined();
140
+ expect(hit?.origin).toBeUndefined();
141
+ expect(hit?.parentRunId).toBeUndefined();
142
+ });
143
+
144
+ it("重建投影不过滤(D1⑤):workflow 来源 record 经重建通路全量返回", () => {
145
+ const store = new RecordStore(tmpDir);
146
+ const captured: SubagentRecordEntryData[] = [];
147
+ store.setPi(makeCapturePi(captured));
148
+
149
+ store.register(makeRecord({ id: "wf-step-2", origin: "workflow", parentRunId: "wf-run-2" }));
150
+ const mainFile = path.join(rootDir, "main.jsonl");
151
+ writeMainSessionFile(mainFile, captured);
152
+ // scanLastRecordEntries 是恢复链(recoverOrphanRecords)与查询共享的重建投影——
153
+ // 此处必须返回 workflow record 全量(过滤只在查询消费面,不在重建投影)。
154
+ const rebuilt = store.scanLastRecordEntries(mainFile);
155
+ expect(rebuilt.map((r) => r.id)).toContain("wf-step-2");
156
+ });
157
+ });
158
+
159
+ describe("collectRecords includeWorkflow 查询面(H2 W1)", () => {
160
+ let rootDir: string;
161
+ let tmpDir: string;
162
+
163
+ beforeEach(() => {
164
+ rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "record-origin-query-"));
165
+ tmpDir = path.join(rootDir, "sessions");
166
+ fs.mkdirSync(tmpDir);
167
+ });
168
+ afterEach(() => {
169
+ fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
170
+ });
171
+
172
+ function makeStore(): RecordStore {
173
+ return new RecordStore(tmpDir);
174
+ }
175
+
176
+ it("缺省过滤 origin=workflow(tool record 可见),includeWorkflow:true 放行", () => {
177
+ const store = makeStore();
178
+ store.register(makeRecord({ id: "tool-1" }));
179
+ store.register(makeRecord({ id: "wf-1", origin: "workflow", parentRunId: "run-A" }));
180
+
181
+ // 缺省(includeWorkflow=false):workflow record 不可见
182
+ const def = store.collectRecords(50, "all", "sess-origin");
183
+ expect(def.map((r) => r.id)).toContain("tool-1");
184
+ expect(def.map((r) => r.id)).not.toContain("wf-1");
185
+
186
+ // includeWorkflow:true:全量可见
187
+ const all = store.collectRecords(50, "all", "sess-origin", true);
188
+ expect(all.map((r) => r.id)).toEqual(expect.arrayContaining(["tool-1", "wf-1"]));
189
+ });
190
+
191
+ it("origin 缺省(存量内存 record)与显式 tool 均保留(负向判定语义)", () => {
192
+ const store = makeStore();
193
+ store.register(makeRecord({ id: "legacy-no-origin" }));
194
+ store.register(makeRecord({ id: "explicit-tool", origin: "tool" }));
195
+ store.register(makeRecord({ id: "wf-hidden", origin: "workflow", parentRunId: "run-B" }));
196
+
197
+ const def = store.collectRecords(50, "all", "sess-origin");
198
+ expect(def.map((r) => r.id)).toEqual(expect.arrayContaining(["legacy-no-origin", "explicit-tool"]));
199
+ expect(def.map((r) => r.id)).not.toContain("wf-hidden");
200
+ });
201
+
202
+ it("statusFilter=running 与 includeWorkflow 组合:workflow running 同样被缺省滤除", () => {
203
+ const store = makeStore();
204
+ store.register(makeRecord({ id: "tool-run", status: "running" }));
205
+ store.register(
206
+ makeRecord({ id: "wf-run", status: "running", origin: "workflow", parentRunId: "run-C" }),
207
+ );
208
+
209
+ const running = store.collectRecords(50, "running", "sess-origin");
210
+ expect(running.map((r) => r.id)).toEqual(["tool-run"]);
211
+
212
+ const runningAll = store.collectRecords(50, "running", "sess-origin", true);
213
+ expect(runningAll.map((r) => r.id)).toEqual(expect.arrayContaining(["tool-run", "wf-run"]));
214
+ });
215
+
216
+ it("collectRecordsByParentRunId:按 run id 精确列 record,不过滤 origin(W2/W3 消费口径)", () => {
217
+ const store = makeStore();
218
+ store.register(makeRecord({ id: "tool-orphan" }));
219
+ store.register(makeRecord({ id: "wf-a1", origin: "workflow", parentRunId: "run-X", startedAt: 1000 }));
220
+ store.register(makeRecord({ id: "wf-a2", origin: "workflow", parentRunId: "run-X", startedAt: 2000 }));
221
+ store.register(makeRecord({ id: "wf-other", origin: "workflow", parentRunId: "run-Y" }));
222
+
223
+ const runX = store.collectRecordsByParentRunId("run-X", 50, "sess-origin");
224
+ expect(runX.map((r) => r.id).sort()).toEqual(["wf-a1", "wf-a2"]);
225
+
226
+ // 空结果:无归属 record / 不存在的 run id
227
+ expect(store.collectRecordsByParentRunId("run-NONE", 50, "sess-origin")).toEqual([]);
228
+ });
229
+
230
+ it("collectRecordsByParentRunId 遵循 rootSessionFilter(session 隔离口径同 collectRecords)", () => {
231
+ const store = makeStore();
232
+ store.register(makeRecord({ id: "wf-sessA", origin: "workflow", parentRunId: "run-S", rootSessionId: "sess-A" }));
233
+ store.register(makeRecord({ id: "wf-sessB", origin: "workflow", parentRunId: "run-S", rootSessionId: "sess-B" }));
234
+
235
+ expect(store.collectRecordsByParentRunId("run-S", 50, "sess-A").map((r) => r.id)).toEqual(["wf-sessA"]);
236
+ expect(store.collectRecordsByParentRunId("run-S", 50, "sess-B").map((r) => r.id)).toEqual(["wf-sessB"]);
237
+ // 不过滤 session 时两源全回
238
+ expect(store.collectRecordsByParentRunId("run-S", 50).map((r) => r.id).sort()).toEqual([
239
+ "wf-sessA",
240
+ "wf-sessB",
241
+ ]);
242
+ });
243
+ });
244
+
245
+ describe("治理面负向规格(D1⑥:恢复链对 workflow origin 全量可见,禁止过滤)", () => {
246
+ let rootDir: string;
247
+ let tmpDir: string;
248
+
249
+ beforeEach(() => {
250
+ rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "record-origin-govern-"));
251
+ tmpDir = path.join(rootDir, "sessions");
252
+ fs.mkdirSync(tmpDir);
253
+ });
254
+ afterEach(() => {
255
+ fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
256
+ });
257
+
258
+ it("recoverEntryOnlyOrphans:workflow origin 的 entry-only 孤儿照样终态化(closed+gc),终态 entry 保留 origin", () => {
259
+ const store = new RecordStore(tmpDir);
260
+ const captured: SubagentRecordEntryData[] = [];
261
+ store.setPi(makeCapturePi(captured));
262
+
263
+ // 末条 running + origin=workflow 的 entry 落主 session;无子文件锚(entry-only 孤儿)
264
+ const orphan = makeRecord({ id: "wf-orphan", origin: "workflow", parentRunId: "run-G" });
265
+ store.register(orphan);
266
+ const mainFile = path.join(rootDir, "main.jsonl");
267
+ writeMainSessionFile(mainFile, captured);
268
+
269
+ // 新 store(模拟重启后内存恒空)执行恢复链
270
+ const recovered: SubagentRecordEntryData[] = [];
271
+ const store2 = new RecordStore(tmpDir);
272
+ store2.setPi(makeCapturePi(recovered));
273
+ store2.recoverEntryOnlyOrphans(mainFile, "sess-origin");
274
+
275
+ // 治理面不过滤:workflow origin 孤儿照样被收敛终态(无文件判据 closed+gc+error)
276
+ const finalized = recovered.find((e) => e.id === "wf-orphan");
277
+ expect(finalized).toBeDefined();
278
+ expect(finalized?.status).toBe("closed");
279
+ expect(finalized?.closedReason).toBe("gc");
280
+ // 终态 entry 保留来源身份(origin/parentRunId 不因恢复链丢失)
281
+ expect(finalized?.origin).toBe("workflow");
282
+ expect(finalized?.parentRunId).toBe("run-G");
283
+ });
284
+
285
+ it("toSubagentRecordEntry schema 面:origin/parentRunId 白名单透传(写入侧单点)", () => {
286
+ // 直连 schema 投影的补充断言(往返用例已锁全链,此处锁字段白名单本身)。
287
+ // 入参形态 = recordToSubagent 投影产物(SubagentRecord),对齐 record-entry-collect.test.ts。
288
+ const subagentRecord = {
289
+ id: "wf-schema",
290
+ agent: "worker",
291
+ task: "t",
292
+ slug: "origin-test",
293
+ status: "running" as const,
294
+ mode: "background" as const,
295
+ startedAt: 1000,
296
+ rootSessionId: "sess-origin",
297
+ parentRecordId: undefined,
298
+ depth: 0,
299
+ endedAt: undefined,
300
+ turns: 1,
301
+ totalTokens: 0,
302
+ model: "m",
303
+ thinkingLevel: undefined,
304
+ eventLog: [],
305
+ displayItems: [],
306
+ origin: "workflow" as const,
307
+ parentRunId: "run-H",
308
+ };
309
+ const entry = toSubagentRecordEntry(subagentRecord);
310
+ expect(entry.origin).toBe("workflow");
311
+ expect(entry.parentRunId).toBe("run-H");
312
+
313
+ const { origin: _o, parentRunId: _p, ...legacyShape } = subagentRecord;
314
+ const legacy = toSubagentRecordEntry(legacyShape);
315
+ expect(legacy.origin).toBeUndefined();
316
+ expect(legacy.parentRunId).toBeUndefined();
317
+ });
318
+ });