@zhushanwen/subagent-core 0.6.0 → 0.7.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 (278) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-KXVBIVOU.js +16 -0
  3. package/dist/chunk-T2SYBW3I.js +24 -0
  4. package/dist/chunk-VURUAGMM.js +2305 -0
  5. package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
  6. package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
  7. package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
  8. package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
  9. package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
  10. package/dist/execution/engine/engine-discovery-scan.js +20 -0
  11. package/dist/execution/engine/paths.cjs +6 -23
  12. package/dist/execution/engine/paths.d.cts +1 -8
  13. package/dist/execution/engine/paths.d.ts +1 -8
  14. package/dist/execution/engine/paths.js +1 -1
  15. package/dist/execution/relay-env.cjs +12 -28
  16. package/dist/execution/relay-env.d.cts +1 -33
  17. package/dist/execution/relay-env.d.ts +1 -33
  18. package/dist/execution/relay-env.js +5 -3
  19. package/dist/index.cjs +14045 -16059
  20. package/dist/index.d.cts +1035 -1579
  21. package/dist/index.d.ts +1035 -1579
  22. package/dist/index.js +9838 -13800
  23. package/dist.bundle/index.cjs +28875 -30323
  24. package/package.json +20 -26
  25. package/src/__tests__/record-store-last-line.test.ts +16 -7
  26. package/src/core/error-message.ts +7 -6
  27. package/src/core/host-services.ts +8 -2
  28. package/src/core/notify-ports.ts +17 -2
  29. package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
  30. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
  31. package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
  32. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
  33. package/src/execution/__tests__/delivery-methods.test.ts +179 -237
  34. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
  35. package/src/execution/__tests__/execute-nesting.test.ts +20 -13
  36. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
  37. package/src/execution/__tests__/gc-timer.test.ts +7 -22
  38. package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
  39. package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
  40. package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
  41. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
  42. package/src/execution/__tests__/idle-gc.test.ts +200 -0
  43. package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
  44. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
  45. package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
  46. package/src/execution/__tests__/record-store.test.ts +60 -6
  47. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
  48. package/src/execution/__tests__/round-supervisor.test.ts +293 -0
  49. package/src/execution/__tests__/session-pending.test.ts +119 -17
  50. package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
  51. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
  52. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
  53. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
  54. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
  55. package/src/execution/__tests__/subagent-service.test.ts +2 -2
  56. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
  57. package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
  58. package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
  59. package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
  60. package/src/execution/agent-registry.ts +6 -2
  61. package/src/execution/dialog-queue.ts +6 -73
  62. package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
  63. package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
  64. package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
  65. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
  66. package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
  67. package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
  68. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
  69. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
  70. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
  71. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
  72. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
  73. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
  74. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
  75. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
  76. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
  77. package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
  78. package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
  79. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
  80. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
  81. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
  82. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
  83. package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
  84. package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
  85. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
  86. package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
  87. package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
  88. package/src/execution/engine/__tests__/registry.test.ts +206 -1
  89. package/src/execution/engine/__tests__/routing.test.ts +97 -0
  90. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
  91. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
  92. package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
  93. package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
  94. package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
  95. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
  96. package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
  97. package/src/execution/engine/client/client-options.ts +103 -0
  98. package/src/execution/engine/client/engine-client.ts +687 -0
  99. package/src/execution/engine/client/mirror.ts +167 -0
  100. package/src/execution/engine/client/pid-file.ts +392 -0
  101. package/src/execution/engine/client/reaper.ts +95 -0
  102. package/src/execution/engine/client/remote-engine.ts +441 -0
  103. package/src/execution/engine/client/reverse-router.ts +255 -0
  104. package/src/execution/engine/common/capability-gate.ts +109 -22
  105. package/src/execution/engine/common/errors.ts +9 -25
  106. package/src/execution/engine/common/event-journal.ts +4 -50
  107. package/src/execution/engine/common/journal-replay.ts +3 -1
  108. package/src/execution/engine/common/kill-chain.ts +18 -251
  109. package/src/execution/engine/common/nesting-guard.ts +18 -114
  110. package/src/execution/engine/common/pool-manager.ts +4 -0
  111. package/src/execution/engine/common/session-view-service.ts +56 -84
  112. package/src/execution/engine/config.ts +151 -0
  113. package/src/execution/engine/d8-compat.ts +346 -0
  114. package/src/execution/engine/engine-discovery-roots.ts +107 -0
  115. package/src/execution/engine/engine-discovery-scan.ts +269 -0
  116. package/src/execution/engine/engine-discovery.ts +30 -7
  117. package/src/execution/engine/engine-inspect-package.ts +282 -0
  118. package/src/execution/engine/engine-manifest.ts +244 -0
  119. package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
  120. package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
  121. package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
  122. package/src/execution/engine/host/host-bridge.ts +150 -0
  123. package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
  124. package/src/execution/engine/host/pi-host-binding.ts +91 -0
  125. package/src/execution/engine/host/spawned-children.ts +139 -0
  126. package/src/execution/engine/host-task-spec.ts +29 -2
  127. package/src/execution/engine/model-validation.ts +55 -2
  128. package/src/execution/engine/paths.ts +16 -41
  129. package/src/execution/engine/port.ts +48 -2
  130. package/src/execution/engine/registry.ts +153 -34
  131. package/src/execution/engine/routing.ts +225 -52
  132. package/src/execution/engine/types.ts +25 -200
  133. package/src/execution/finalize-record.ts +25 -0
  134. package/src/execution/idle-gc.ts +89 -14
  135. package/src/execution/lifecycle-manager.ts +10 -7
  136. package/src/execution/lifecycle-predicates.ts +10 -8
  137. package/src/execution/record-store.ts +29 -2
  138. package/src/execution/relay-env.ts +19 -36
  139. package/src/execution/round-supervisor/domain.ts +89 -0
  140. package/src/execution/round-supervisor/index.ts +33 -0
  141. package/src/execution/round-supervisor/notify-accounting.ts +87 -0
  142. package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
  143. package/src/execution/round-supervisor/service-binding.test.ts +661 -0
  144. package/src/execution/round-supervisor/service-binding.ts +273 -0
  145. package/src/execution/round-supervisor/supervisor.ts +426 -0
  146. package/src/execution/session-pending.ts +68 -74
  147. package/src/execution/settled-watchdog.ts +39 -0
  148. package/src/execution/subagent-service.ts +746 -400
  149. package/src/execution/subprocess-agent-runner.ts +16 -8
  150. package/src/execution/types.ts +41 -117
  151. package/src/execution/ui-channels.ts +13 -214
  152. package/src/execution/ui-request-handler-factory.ts +7 -4
  153. package/src/execution/ui-request-observability.ts +2 -2
  154. package/src/execution/workflow-state-root.ts +67 -0
  155. package/src/execution/worktree-git-ops.ts +5 -0
  156. package/src/execution/worktree-manager.ts +11 -1
  157. package/src/index.ts +55 -26
  158. package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
  159. package/src/orchestration/execute-agent-call.ts +1 -1
  160. package/src/orchestration/file-run-store.ts +144 -59
  161. package/src/orchestration/models/types.ts +43 -42
  162. package/src/shared/zcode-model-ref.ts +31 -0
  163. package/dist/chunk-43ONBFZX.js +0 -240
  164. package/dist/chunk-A4IVZWVX.js +0 -31
  165. package/dist/chunk-APZY4IME.js +0 -27
  166. package/dist/chunk-V3VHZ2VX.js +0 -59
  167. package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
  168. package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
  169. package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
  170. package/dist/execution/engine/engines/zcode/constants.js +0 -54
  171. package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
  172. package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
  173. package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
  174. package/dist/execution/engine/engines/zcode/reader.js +0 -9
  175. package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
  176. package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
  177. package/src/__tests__/session-runner.test.ts +0 -69
  178. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  179. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
  180. package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
  181. package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
  182. package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
  183. package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
  184. package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
  185. package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
  186. package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
  187. package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
  188. package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
  189. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
  190. package/src/execution/__tests__/output-collector.test.ts +0 -469
  191. package/src/execution/__tests__/pi-invocation.test.ts +0 -166
  192. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
  193. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  194. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
  195. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
  196. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
  197. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
  198. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  199. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  200. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  201. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
  202. package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
  203. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
  204. package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
  205. package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
  206. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
  207. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
  208. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
  209. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
  210. package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
  211. package/src/execution/__tests__/spawn-args.test.ts +0 -446
  212. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
  213. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  214. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
  215. package/src/execution/__tests__/spawned-children.test.ts +0 -236
  216. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  217. package/src/execution/__tests__/stdin-writer.test.ts +0 -464
  218. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
  219. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  220. package/src/execution/__tests__/timeout-integration.test.ts +0 -616
  221. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  222. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  223. package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
  224. package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
  225. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
  226. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  227. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
  228. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  229. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
  230. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  231. package/src/execution/engine/common/schema-emulation.ts +0 -189
  232. package/src/execution/engine/common/session-view-projection.ts +0 -51
  233. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
  234. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  235. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
  236. package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
  237. package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
  238. package/src/execution/engine/engines/pi/output-collector.ts +0 -317
  239. package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
  240. package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
  241. package/src/execution/engine/engines/pi/reader.ts +0 -59
  242. package/src/execution/engine/engines/pi/registration.ts +0 -37
  243. package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
  244. package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
  245. package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
  246. package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
  247. package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
  248. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
  249. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
  250. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
  251. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
  252. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
  253. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
  254. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  255. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
  256. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
  257. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
  258. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
  259. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
  260. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
  261. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
  262. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
  263. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
  264. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
  265. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
  266. package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
  267. package/src/execution/engine/engines/zcode/connection.ts +0 -598
  268. package/src/execution/engine/engines/zcode/constants.ts +0 -176
  269. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
  270. package/src/execution/engine/engines/zcode/parser.ts +0 -102
  271. package/src/execution/engine/engines/zcode/preparer.ts +0 -235
  272. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  273. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  274. package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
  275. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
  276. package/src/execution/round-settlement.ts +0 -93
  277. package/src/execution/ui-request-queue.ts +0 -215
  278. package/src/shared/schema-env.ts +0 -44
@@ -1,6 +1,6 @@
1
1
  // golden-replay.zcode.test.ts —— zcode 引擎 golden 回放层(conformance 免 LLM 默认
2
2
  // CI 层)。语料 = app-server NDJSON 帧序列(附录 A.2 任务生命周期帧序;采集 SSOT
3
- // = engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json)——双副本 diff
3
+ // = @zhushanwen/zcode-subagent-cli src/__golden__/zcode-golden-appserver.json)——双副本 diff
4
4
  // 校验(fixture 与 golden-sample.ts 内嵌副本一致,防漂移:更新样本必须同步两处,
5
5
  // 探针的干跑校验消费内嵌副本,两处不一致 = 探针在测旧契约)+ 帧形态不变量断言
6
6
  // (A8「golden 帧序列语料 diff 通过」的本仓段)。
@@ -15,13 +15,14 @@ import { fileURLToPath } from "node:url";
15
15
 
16
16
  import { describe, expect, it } from "vitest";
17
17
 
18
- import { ZCODE_APPSERVER_GOLDEN } from "../../engines/zcode/golden-sample.ts";
18
+ // 基线三层②(W10):引擎层 golden 随引擎包——内嵌副本经 zcode 包公共导出面
19
+ // 消费,fixture SSOT = zcode 包 src/__golden__/(core 不再深路径 import 内建副本)。
20
+ import { ZCODE_APPSERVER_GOLDEN } from "@zhushanwen/zcode-subagent-cli";
19
21
 
20
- // conformance 目录(engine/__tests__/conformance)→ engine 根的相对定位
22
+ // conformance 目录(engine/__tests__/conformance)→ 引擎包 __golden__ 的相对定位
21
23
  const appserverFixturePath = join(
22
24
  dirname(fileURLToPath(import.meta.url)),
23
- "..", "..", // conformance → __tests__ → engine
24
- "engines", "zcode", "__tests__", "__fixtures__", "zcode-golden-appserver.json",
25
+ "../../../../../../zcode-subagent-cli/src/__golden__", "zcode-golden-appserver.json",
25
26
  );
26
27
 
27
28
  interface AppServerGoldenFile {
@@ -0,0 +1,398 @@
1
+ // protocol-blackbox.test.ts —— W10 协议黑盒套件(基线三层①的自动层)。
2
+ //
3
+ // 覆盖(impl-plan §2.10 / 设计 §4 探针挂钩①):
4
+ // - 10 正向方法(initialize/probe/run/cancel/interact/read/listModels/
5
+ // validateModel/dispose/ping)× fake 引擎 CLI 往返;
6
+ // - 8 反向通道(host/log、host/askUser、host/permission、host/streamDelta、
7
+ // host/poolResolved、host/handleReady、host/childSpawned、host/childStateChanged)
8
+ // 的 core 侧到达断言;
9
+ // - 错误帧:run 失败帧 / 未知方法帧(码原样透传)/ initialize 版本越界
10
+ // (engine_protocol_mismatch → 客户端直接不可用,不重建);
11
+ // - fixture 回放结构等价:事件类型序列 / 顺序 / seq 单调 / 字段白名单逐字段比对
12
+ // (排除 text_delta 文本内容等不可复现字段——设计 §4 基线三层①)+ journal 往返
13
+ // 保真 + assertAgentEventInvariants;
14
+ // - POSIX 组探测(R9-4/A3 前提):childSpawned 上报的 pid 做 kill(-pid, 0),
15
+ // 不在同组即告警——本套件验证探测通路;Windows 无外部判据,仅 SDK 层保证
16
+ // (显式登记,见文件末 describe 与 deviations)。
17
+ //
18
+ // 措辞统一(R9-1):「子进程零残留」一律指一代子进程 + 组内后代;引擎自身
19
+ // detached 后代不判 fail(设计 §3.9 已接受代价)。
20
+
21
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
22
+ import { tmpdir } from "node:os";
23
+ import { dirname, join } from "node:path";
24
+ import { fileURLToPath } from "node:url";
25
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
26
+
27
+ import { EngineClient } from "../../client/engine-client.ts";
28
+ import type { MirrorChangeEvent } from "../../client/mirror.ts";
29
+ import { JournalWriter, replayJournal } from "../../common/event-journal.ts";
30
+ import type { AgentEvent } from "../../../types.ts";
31
+ import { assertAgentEventInvariants } from "./agent-event-invariants.ts";
32
+
33
+ const FIXTURE_DIR = join(dirname(fileURLToPath(import.meta.url)), "__fixtures__", "engine-protocol");
34
+ const FAKE_ENGINE = join(FIXTURE_DIR, "fake-engine-protocol.mjs");
35
+ const FIXTURE = join(FIXTURE_DIR, "smoke-run.fixture.json");
36
+
37
+ /** fixture 的白名单可比形态(record:engine-fixtures 产物 schema v1)。 */
38
+ interface EngineProtocolFixture {
39
+ schemaVersion: number;
40
+ engineId: string;
41
+ protocolVersion: number;
42
+ run: {
43
+ params: { runId: string; ctx: Record<string, unknown> };
44
+ result: { handle: Record<string, unknown>; outcome: Record<string, unknown> };
45
+ };
46
+ events: Array<{ type: string }>;
47
+ }
48
+
49
+ const fixture: EngineProtocolFixture = JSON.parse(readFileSync(FIXTURE, "utf8"));
50
+ if (fixture.schemaVersion !== 1) {
51
+ throw new Error(`unsupported fixture schemaVersion ${String(fixture.schemaVersion)}`);
52
+ }
53
+
54
+ /** AgentEvent 字段白名单(结构等价比对的逐字段面;text_delta.delta 内容显式排除)。 */
55
+ const FIELD_WHITELIST: Record<string, readonly string[]> = {
56
+ text_delta: ["type", "delta"],
57
+ message_end: ["type", "usage"],
58
+ turn_end: ["type", "usage"],
59
+ error: ["type", "message"],
60
+ tool_start: ["type", "toolName", "input"],
61
+ tool_end: ["type", "toolName", "output", "isError"],
62
+ };
63
+
64
+ let dataDir: string;
65
+
66
+ beforeEach(() => {
67
+ dataDir = mkdtempSync(join(tmpdir(), "w10-engine-protocol-"));
68
+ });
69
+
70
+ afterEach(() => {
71
+ rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
72
+ });
73
+
74
+ interface Harness {
75
+ client: EngineClient;
76
+ events: AgentEvent[];
77
+ mirrorEvents: MirrorChangeEvent[];
78
+ logs: Array<{ level: string; message: string }>;
79
+ askUserRequests: unknown[];
80
+ permissionRequests: unknown[];
81
+ streamDeltas: string[];
82
+ poolResolved: string[];
83
+ handleReady: Array<{ sessionRef: Record<string, string>; poolKey: string }>;
84
+ }
85
+
86
+ function makeHarness(extraEnv: Record<string, string> = {}, routeRunId = fixture.run.params.runId): Harness {
87
+ const h: Harness = {
88
+ client: undefined as unknown as EngineClient,
89
+ events: [],
90
+ mirrorEvents: [],
91
+ logs: [],
92
+ askUserRequests: [],
93
+ permissionRequests: [],
94
+ streamDeltas: [],
95
+ poolResolved: [],
96
+ handleReady: [],
97
+ };
98
+ h.client = new EngineClient({
99
+ engineId: fixture.engineId,
100
+ command: process.execPath,
101
+ args: [FAKE_ENGINE],
102
+ hostKind: "test",
103
+ hostVersion: "w10-protocol-blackbox",
104
+ dataDir,
105
+ envPrefixes: [],
106
+ log: (p) => h.logs.push({ level: p.level, message: p.message }),
107
+ uiRequestHandler: async (req) => {
108
+ h.askUserRequests.push(req);
109
+ return { value: "option-a" };
110
+ },
111
+ permissionHandler: async (params) => {
112
+ h.permissionRequests.push(params);
113
+ return { approved: true };
114
+ },
115
+ onMirrorChanged: (event) => h.mirrorEvents.push(event),
116
+ baseEnv: {
117
+ ...process.env,
118
+ FAKE_PROTOCOL_FIXTURE: FIXTURE,
119
+ ...extraEnv,
120
+ },
121
+ });
122
+ const unregister = h.client.registerRunRoute(routeRunId, {
123
+ onEvent: (event) => {
124
+ h.events.push(event as AgentEvent);
125
+ },
126
+ onStreamDelta: (delta) => {
127
+ h.streamDeltas.push(delta);
128
+ },
129
+ onPoolResolved: (poolKey) => {
130
+ h.poolResolved.push(poolKey);
131
+ },
132
+ onHandleReady: (partial) => {
133
+ h.handleReady.push(partial);
134
+ },
135
+ });
136
+ void unregister;
137
+ return h;
138
+ }
139
+
140
+ describe("协议黑盒:10 正向方法 × fake 引擎回放", () => {
141
+ it("initialize/probe/listModels/validateModel/interact/read/ping 往返(fixture 白名单比对)", async () => {
142
+ const h = makeHarness();
143
+ try {
144
+ await h.client.ensureConnected();
145
+ expect(h.client.currentState).toBe("ready");
146
+
147
+ const probe = (await h.client.request("probe", {}, { timeoutMs: 5_000 })) as {
148
+ ok: boolean; checks: Array<{ name: string; ok: boolean }>;
149
+ };
150
+ expect(probe.ok).toBe(true);
151
+ expect(probe.checks.every((c) => typeof c.name === "string" && typeof c.ok === "boolean")).toBe(true);
152
+
153
+ const models = (await h.client.request("listModels", {}, { timeoutMs: 5_000 })) as {
154
+ models: Array<{ id: string }>;
155
+ };
156
+ expect(models.models.map((m) => m.id)).toEqual(["fake-1"]);
157
+
158
+ const validated = (await h.client.request(
159
+ "validateModel",
160
+ { modelRef: "fake/fake-1" },
161
+ { timeoutMs: 5_000 },
162
+ )) as { canonicalRef: string };
163
+ expect(validated.canonicalRef).toBe("fake/fake-1");
164
+
165
+ const interact = (await h.client.request(
166
+ "interact",
167
+ { handle: fixture.run.result.handle, action: { kind: "message", text: "hi" } },
168
+ { timeoutMs: 5_000 },
169
+ )) as { accepted: boolean };
170
+ expect(interact.accepted).toBe(true);
171
+
172
+ const view = (await h.client.request(
173
+ "read",
174
+ { handle: fixture.run.result.handle, dataDir },
175
+ { timeoutMs: 5_000 },
176
+ )) as { entries: unknown[] };
177
+ expect(Array.isArray(view.entries)).toBe(true);
178
+
179
+ await h.client.ping(); // 诊断面:不 throw 即通
180
+ } finally {
181
+ await h.client.dispose();
182
+ }
183
+ });
184
+
185
+ it("run:终态应答 handle+outcome 与 fixture 白名单逐字段一致", async () => {
186
+ const h = makeHarness();
187
+ try {
188
+ await h.client.ensureConnected();
189
+ const result = (await h.client.request("run", fixture.run.params)) as {
190
+ handle: Record<string, unknown>;
191
+ outcome: Record<string, unknown>;
192
+ };
193
+ expect(result.handle).toEqual(fixture.run.result.handle);
194
+ expect(result.outcome).toEqual(fixture.run.result.outcome);
195
+ } finally {
196
+ await h.client.dispose();
197
+ }
198
+ });
199
+
200
+ it("cancel:受理应答(收敛语义由 W2 client 套件的 3s 窗口用例覆盖,此处断言帧往返)", async () => {
201
+ const h = makeHarness();
202
+ try {
203
+ await h.client.ensureConnected();
204
+ const ack = (await h.client.request(
205
+ "cancel",
206
+ { runId: "run-smoke-1", reason: "test" },
207
+ { timeoutMs: 5_000 },
208
+ )) as { ok: boolean };
209
+ expect(ack.ok).toBe(true);
210
+ } finally {
211
+ await h.client.dispose();
212
+ }
213
+ });
214
+
215
+ it("dispose:幂等收口(应答 ok + 进程退出)", async () => {
216
+ const h = makeHarness();
217
+ await h.client.ensureConnected();
218
+ const r = (await h.client.request("dispose", {}, { timeoutMs: 5_000 })) as { ok: boolean };
219
+ expect(r.ok).toBe(true);
220
+ await h.client.dispose();
221
+ await expect(h.client.dispose()).resolves.toBeUndefined();
222
+ });
223
+ });
224
+
225
+ describe("协议黑盒:8 反向通道 core 侧到达", () => {
226
+ it("run 期间全部 8 通道到达并被正确路由", async () => {
227
+ const h = makeHarness();
228
+ try {
229
+ await h.client.ensureConnected();
230
+ await h.client.request("run", fixture.run.params);
231
+
232
+ // host/log
233
+ expect(h.logs.some((l) => l.message.includes("run started"))).toBe(true);
234
+ // host/streamDelta
235
+ expect(h.streamDeltas).toEqual(["Hello", " world"]);
236
+ // host/poolResolved
237
+ expect(h.poolResolved).toEqual(["shared"]);
238
+ // host/handleReady
239
+ expect(h.handleReady).toEqual([{ sessionRef: { sessionId: "sess-fixture" }, poolKey: "shared" }]);
240
+ // host/childSpawned + host/childStateChanged(镜像落项 + 状态更新)
241
+ const spawned = h.mirrorEvents.filter((e) => e.reason === "childSpawned");
242
+ expect(spawned).toHaveLength(1);
243
+ expect(typeof spawned[0]?.pid).toBe("number");
244
+ const stateChanged = h.mirrorEvents.filter((e) => e.reason === "childStateChanged");
245
+ expect(stateChanged).toHaveLength(1);
246
+ expect(stateChanged[0]?.recordId).toBe(fixture.run.params.runId);
247
+ // host/askUser + host/permission(交互面 ack 两阶段)
248
+ expect(h.askUserRequests).toHaveLength(1);
249
+ expect(h.permissionRequests).toHaveLength(1);
250
+ } finally {
251
+ await h.client.dispose();
252
+ }
253
+ });
254
+ });
255
+
256
+ describe("协议黑盒:错误帧", () => {
257
+ it("run 失败帧 → EngineSdkError(code/message/recovery 透传)", async () => {
258
+ const h = makeHarness({ FAKE_PROTOCOL_ERROR: "run_failed" });
259
+ try {
260
+ await h.client.ensureConnected();
261
+ await expect(h.client.request("run", fixture.run.params)).rejects.toMatchObject({
262
+ code: "engine_run_failed",
263
+ message: expect.stringContaining("scripted run failure"),
264
+ });
265
+ } finally {
266
+ await h.client.dispose();
267
+ }
268
+ });
269
+
270
+ it("未知方法帧 → 错误码原样透传(帧形态权威,非码表枚举)", async () => {
271
+ const h = makeHarness({ FAKE_PROTOCOL_ERROR: "unknown_method" });
272
+ try {
273
+ await h.client.ensureConnected();
274
+ await expect(h.client.request("bogus/method", {}, { timeoutMs: 5_000 })).rejects.toMatchObject({
275
+ code: "engine_method_unknown",
276
+ });
277
+ } finally {
278
+ await h.client.dispose();
279
+ }
280
+ });
281
+
282
+ it("initialize 版本越界 → engine_protocol_mismatch + 客户端直接不可用(不重建)", async () => {
283
+ const h = makeHarness({ FAKE_PROTOCOL_VERSION: "99" });
284
+ try {
285
+ await expect(h.client.ensureConnected()).rejects.toMatchObject({ code: "engine_protocol_mismatch" });
286
+ expect(h.client.currentState).toBe("unavailable");
287
+ } finally {
288
+ await h.client.dispose();
289
+ }
290
+ });
291
+ });
292
+
293
+ describe("协议黑盒:fixture 回放结构等价(基线三层①)", () => {
294
+ it("事件类型序列/顺序/seq 单调/字段白名单(排除 text_delta 文本内容)", async () => {
295
+ const h = makeHarness();
296
+ try {
297
+ await h.client.ensureConnected();
298
+ const result = (await h.client.request("run", fixture.run.params)) as {
299
+ outcome: { content: string };
300
+ };
301
+
302
+ // ① 类型序列 + 顺序(与 fixture.events 白名单形态逐项一致)
303
+ expect(h.events.map((e) => e.type)).toEqual(fixture.events.map((e) => e.type));
304
+
305
+ // ② seq 单调(协议通知帧的 seq 字段经 fake 引擎按 fixture 播放)
306
+ // ——事件流本身不透传 seq,单调性由通知帧序承担;此处断言到达顺序稳定。
307
+
308
+ // ③ 字段白名单:每条事件的实际键 ⊆ 白名单
309
+ for (const event of h.events) {
310
+ const whitelist = FIELD_WHITELIST[event.type];
311
+ expect(whitelist, `missing whitelist for event type ${event.type}`).toBeDefined();
312
+ for (const key of Object.keys(event)) {
313
+ expect(whitelist).toContain(key);
314
+ }
315
+ }
316
+
317
+ // ④ 排除项自证:text_delta 文本内容参与流式拼接不变量(3a),但内容本身
318
+ // 不做逐字段 diff(不可复现字段——设计 §4)
319
+ assertAgentEventInvariants(h.events, { granularity: "stream", content: result.outcome.content });
320
+
321
+ // ⑤ journal 往返保真(回放层事件落 journal 后重读等价)
322
+ const journalPath = join(dataDir, "blackbox-journal.jsonl");
323
+ const writer = new JournalWriter({
324
+ path: journalPath,
325
+ taskId: "w10-blackbox",
326
+ engineId: fixture.engineId,
327
+ });
328
+ for (const event of h.events) writer.append(event);
329
+ await writer.close();
330
+ const replayed = replayJournal(journalPath) as AgentEvent[];
331
+ expect(replayed.map((e) => e.type)).toEqual(h.events.map((e) => e.type));
332
+ expect(JSON.stringify(replayed)).toBe(JSON.stringify(h.events));
333
+ } finally {
334
+ await h.client.dispose();
335
+ }
336
+ });
337
+
338
+ it("录制产物回放校验:recorded.fixture.json(record:engine-fixtures 产物)结构不变量", async () => {
339
+ // 录制/复跑闭环(设计 §4):录制脚本产物 schemaVersion 1 + 脱敏不变量 +
340
+ // 事件通知 seq 单调 + 反向通道覆盖。真机引擎录制时产物替换本文件即可把同一
341
+ // 套结构断言升格为该引擎的协议层基线(不逐字段 diff 正文)。
342
+ const recorded = JSON.parse(
343
+ readFileSync(join(FIXTURE_DIR, "recorded.fixture.json"), "utf8"),
344
+ ) as {
345
+ schemaVersion: number;
346
+ wire: {
347
+ engineToHost: Array<{ method?: string; params?: { seq?: number; runId?: string } }>;
348
+ hostToEngine: Array<{ id: number | string; method: string }>;
349
+ };
350
+ events: Array<{ type: string }>;
351
+ };
352
+ expect(recorded.schemaVersion).toBe(1);
353
+
354
+ // 事件通知 seq 单调(同 runId)
355
+ const seqs = recorded.wire.engineToHost
356
+ .filter((f) => f.method === "event" && typeof f.params?.seq === "number")
357
+ .map((f) => f.params!.seq!);
358
+ expect(seqs).toEqual([...seqs].sort((a, b) => a - b));
359
+
360
+ // 反向通道覆盖(录制期间 8 通道全部上线)
361
+ const reverse = new Set(
362
+ recorded.wire.engineToHost
363
+ .filter((f) => typeof (f as { id?: string }).id === "string")
364
+ .map((f) => (f as { method: string }).method),
365
+ );
366
+ for (const ch of [
367
+ "host/log", "host/askUser", "host/permission", "host/streamDelta",
368
+ "host/poolResolved", "host/handleReady", "host/childSpawned", "host/childStateChanged",
369
+ ]) {
370
+ expect(reverse.has(ch)).toBe(true);
371
+ }
372
+
373
+ // 结构白名单形态与 curated fixture 同源
374
+ expect(recorded.events).toEqual(fixture.events);
375
+ });
376
+
377
+ it("POSIX 组探测:childSpawned 上报 pid 非组长(在引擎收割组内;A3 前提;Windows 显式跳过)", async () => {
378
+ if (process.platform === "win32") {
379
+ // Windows 无外部组判据(无 kill(-pid,0) 等价物)——仅 SDK 层 spawnEngineChild
380
+ // 的 detached/stdio 规约保证,真机面挂 A3 手动门(taskkill /T /F 形态)。
381
+ return;
382
+ }
383
+ const h = makeHarness();
384
+ try {
385
+ await h.client.ensureConnected();
386
+ await h.client.request("run", fixture.run.params);
387
+ const spawned = h.mirrorEvents.find((e) => e.reason === "childSpawned");
388
+ expect(spawned?.pid).toBeDefined();
389
+ // 预期形态(reverse-router 组探测同款语义):任务子进程与引擎同组(收割 =
390
+ // 引擎组级 kill(-enginePid))→ 该 pid 非组长 → kill(-pid,0) ESRCH。
391
+ // kill(-pid,0) 成功 = 自成进程组 = 不在收割组内 = 告警面(R9-1 限定:一代
392
+ // 子进程 + 组内后代;引擎自身 detached 后代不判 fail,设计 §3.9 已接受代价)。
393
+ expect(() => process.kill(-spawned!.pid!, 0)).toThrow();
394
+ } finally {
395
+ await h.client.dispose();
396
+ }
397
+ });
398
+ });
@@ -0,0 +1,221 @@
1
+ // registry-fork-filter.test.ts —— [W6] 翻档 × fork 继承残留的读侧过滤契约 +
2
+ // bash 跨 session 可见性显式断言(R4 一刀语义钉住)+ 偏差 #4 探针(goal 守卫口径)。
3
+ //
4
+ // 设计权威源:chat-domain-v1x-liveness-governance.md §3.2 D4(翻档三连带——读侧
5
+ // 过滤三口 + PENDING_LIFECYCLE 翻 process 档)+ 修订记录 v5-⑤(bash 跨 session
6
+ // 可见性一刀钉成显式选择)+ 验收 A9② + impl-plan §5 偏差 #4(W4 交接:goal 守卫
7
+ // 消费点未传基准,W6 实测裁决是否需 goal 侧两行传参)。
8
+ //
9
+ // 被测函数 = `extensions/universal/pending-notifications/src/state.ts` 的导出纯函数
10
+ // (零依赖、不触 Pi 运行时——文件头注自证;经相对路径源码消费,subagent-core 无该
11
+ // workspace 依赖声明,不加 phantom dep)。分工:
12
+ // - 过滤函数本体 / rebuild 投影的两口径行为:本套件钉语义(conformance 视角);
13
+ // - 过滤①②③的**接线**(goal/subagent-workflow/pending-notifications 三消费点
14
+ // 透传 currentSessionId):W4 包内测试已承载(pi-pending-notifications
15
+ // __tests__ :523/:532、subagent-workflow pi-host.test :199-223);
16
+ // - 后代判定口(读侧过滤②)的差集口径与①共用同一函数——本套件①的断言即其
17
+ // 语义核心;接线透传由上述 W4 测试守护。
18
+ //
19
+ // 探针结论(偏差 #4,实测证据 = 「goal 守卫口径」describe):**有虚增**——fork 后
20
+ // 父 session 仍活跃的后台任务残留使 goal 守卫现状口径(无基准)幻 defer;确需的
21
+ // 两行传参改动点列于该 describe 注释,留给主会话裁决(本单元不改 goal——红线)。
22
+
23
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
24
+ import { tmpdir } from "node:os";
25
+ import { join } from "node:path";
26
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
27
+
28
+ // 相对路径源码消费(7 层上溯到仓库根):state.ts 是零依赖纯函数(不触 Pi 运行时),
29
+ // 无需为探针/契约测试给 subagent-core 添加 workspace 依赖声明。
30
+ import {
31
+ PENDING_LIFECYCLE,
32
+ PENDING_TTL_MS,
33
+ countActiveFromEntries,
34
+ createRegistry,
35
+ rebuildFromEntries,
36
+ } from "../../../../../../../extensions/universal/pending-notifications/src/state.ts";
37
+
38
+ import { runReconcileSweep } from "../../../round-supervisor/index.ts";
39
+
40
+ /** pending:register entry 的落盘形态(对齐 pending-notifications 写入侧契约)。 */
41
+ function registerEntry(
42
+ id: string,
43
+ type: "subagent" | "workflow" | "bash",
44
+ sessionId: string,
45
+ overrides: { expiresAt?: number } = {},
46
+ ): { customType: string; data: Record<string, unknown> } {
47
+ return {
48
+ customType: "pending:register",
49
+ data: {
50
+ id,
51
+ type,
52
+ name: id,
53
+ registeredAt: 1_000,
54
+ sessionId,
55
+ // bash 真实落盘无 expiresAt 键(写入侧省略,D16);subagent/workflow 翻
56
+ // process 档后同理——overrides 仅用于 TTL 对照面。
57
+ ...(overrides.expiresAt !== undefined ? { expiresAt: overrides.expiresAt } : {}),
58
+ },
59
+ };
60
+ }
61
+
62
+ /** 语义对照面的基准时刻(远超旧 1h TTL——钉「无 TTL 清理」而非「窗口内未到期」假绿)。 */
63
+ const NOW = 10 * PENDING_TTL_MS + 1;
64
+
65
+ let tmpDirs: string[] = [];
66
+
67
+ afterEach(() => {
68
+ for (const dir of tmpDirs) rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
69
+ tmpDirs = [];
70
+ });
71
+
72
+ describe("[W6/D4 翻档] PENDING_LIFECYCLE 三类型全 process 档(跨时长无 TTL 清理的判据锚)", () => {
73
+ it("subagent/workflow 翻 process 档(bash 原生 process 档)——翻档事实钉住", () => {
74
+ expect(PENDING_LIFECYCLE).toEqual({ subagent: "process", workflow: "process", bash: "process" });
75
+ });
76
+
77
+ it("翻档后跨时长(远超旧 1h TTL)无 TTL 清理:三类型 register 均不进 expiredToFlush", () => {
78
+ const registry = createRegistry();
79
+ const entries = [
80
+ registerEntry("bg-1", "subagent", "sess-root", { expiresAt: 1_000 + 1 }), // 远过去
81
+ registerEntry("wf-1", "workflow", "sess-root", { expiresAt: 1_000 + 1 }),
82
+ registerEntry("bt-1", "bash", "sess-root"), // 真实落盘形态:无 expiresAt 键
83
+ ];
84
+ const result = rebuildFromEntries(registry, entries, "sess-root", NOW);
85
+ // 旧 session 档语义(1h TTL + 跨 session 补注销)曾把长任务/跨重启注册静默
86
+ // 清除 → 守卫失明(事故环 4 放大器);翻档后三类型全部续存。
87
+ expect(result.expiredToFlush).toEqual([]);
88
+ expect(result.activeIds).toEqual(["bg-1", "wf-1", "bt-1"]);
89
+ });
90
+ });
91
+
92
+ describe("[W6/D4 读侧过滤①] fork 继承残留 × countActiveFromEntries 基准", () => {
93
+ it("带基准(currentSessionId)→ 父 session 残留不入差集、本 session 注册计入", () => {
94
+ const entries = [
95
+ registerEntry("parent-bg", "subagent", "sess-parent"),
96
+ registerEntry("own-bg", "subagent", "sess-child"),
97
+ registerEntry("own-bt", "bash", "sess-child"),
98
+ ];
99
+ const result = countActiveFromEntries(entries, { currentSessionId: "sess-child" });
100
+ expect(result.ids).toEqual(["own-bg", "own-bt"]);
101
+ });
102
+
103
+ it("缺 sessionId 的旧形态条目视为本 session(宁放行不误杀——防误逐活跃计数)", () => {
104
+ const entries = [{ customType: "pending:register", data: { id: "legacy-bg", type: "subagent" } }];
105
+ expect(countActiveFromEntries(entries, { currentSessionId: "sess-child" }).ids).toEqual(["legacy-bg"]);
106
+ });
107
+ });
108
+
109
+ describe("[W6/R4 一刀] bash 跨 session 可见性显式断言(钉成显式选择的语义变更)", () => {
110
+ it("读侧过滤一刀后:子 session 的 goal 不再为父 session 的 bash 任务 defer(带基准口径)", () => {
111
+ // fork 继承:子 session entries 复制父 session 的 bash 后台任务注册
112
+ // (process 档跨 session 存活——U4 补注销对翻档类型不再中性化,残留永久留存)。
113
+ const childEntries = [
114
+ registerEntry("parent-bt", "bash", "sess-parent"),
115
+ registerEntry("child-bt", "bash", "sess-child"),
116
+ ];
117
+ // 设计选择(R4):按当前 session 过滤——父 session 的 bash 任务不再让子
118
+ // session 的 goal 守卫 defer(子 session 有自己的等待域)。
119
+ const scoped = countActiveFromEntries(childEntries, { currentSessionId: "sess-child" });
120
+ expect(scoped.ids).toEqual(["child-bt"]);
121
+ expect(scoped.count).toBe(1); // 只数本 session 的任务
122
+ });
123
+
124
+ it("旧行为显式登记:无基准口径(goal 守卫现状)父 bash 残留仍入计数(= defer 驱动)", () => {
125
+ const childEntries = [registerEntry("parent-bt", "bash", "sess-parent")];
126
+ // 显式钉住「一刀的对照面」:不传基准 = 不过滤 = 子 session goal 仍为父 bash
127
+ // 任务 defer。这是读侧过滤一刀**改变的旧行为**——保留此断言防止有人误以为
128
+ // 过滤是缺省语义(偏差 #4:goal 消费点现状即此口径,见探针结论)。
129
+ expect(countActiveFromEntries(childEntries).count).toBe(1);
130
+ });
131
+ });
132
+
133
+ describe("[W6/D4 读侧过滤③] registry rebuild 投影(pending_notifications 工具读侧)", () => {
134
+ it("跨 session 残留不入 registry、不补 unregister entry(跳过不补注销——落盘收口归 core sweep)", () => {
135
+ const registry = createRegistry();
136
+ const entries = [
137
+ registerEntry("parent-bg", "subagent", "sess-parent"),
138
+ registerEntry("own-bg", "subagent", "sess-child"),
139
+ ];
140
+ const result = rebuildFromEntries(registry, entries, "sess-child", NOW);
141
+ // 工具投影面(count/list)不虚报继承残留(A9②:count/list 不含继承残留)。
142
+ expect(result.activeIds).toEqual(["own-bg"]);
143
+ expect(registry.operations.has("parent-bg")).toBe(false);
144
+ // 「跳过不补注销」:残留 entry 留在 session 文件(读侧①③口各自兜住差集消费
145
+ // 方),禁止 rebuild 写侧补发——跨 session 写达域缺口由 core 对账 sweep 收口。
146
+ expect(result.expiredToFlush).toEqual([]);
147
+ });
148
+ });
149
+
150
+ describe("[W6/偏差 #4 探针] goal 守卫口径(无基准)× fork 残留——实测结论", () => {
151
+ // 偏差 #4(impl-plan §5,W4 交接):W4 读侧过滤①的 currentSessionId 为可选参数,
152
+ // goal 守卫消费点未传基准。本探针在 fork 场景实测该口径是否被父 session 注册
153
+ // 残留虚增,结论二选一回写报告:
154
+ // (a) 无影响(其他机制覆盖)→ 偏差 #4 关闭,goal 零改动;
155
+ // (b) 有虚增 → 列出确需的两行传参改动点(不改 goal——红线),留主会话裁决。
156
+ //
157
+ // **实测结论:(b) 有虚增。**
158
+ // 证据链:goal 守卫两处消费点现状均无基准调用——
159
+ // `extensions/universal/goal/src/adapters/event-handlers/agent-end.ts:198`
160
+ // `const pendingOps = countActiveFromEntries(entries);`(defer 分支判据
161
+ // = `pendingOps.count > 0`)
162
+ // `extensions/universal/goal/src/adapters/event-handlers/agent-end.ts:294`
163
+ // `if (countActiveFromEntries(ctx.sessionManager.getEntries()).count > 0) return;`
164
+ // (backoff 延迟发射前守卫)
165
+ // 下方用例 1 证明该口径在 fork 残留下 count>0 → defer 命中(幻 defer);
166
+ // 用例 2 证明仅当父任务 record 已终态(core sweep 补注销落盘)才归零——
167
+ // 「父任务仍活跃」窗口内无任何机制覆盖(W4 翻档后 U4 不再中性化)。
168
+ //
169
+ // 确需的两行改动点(主会话裁决后由 goal 侧单元执行):
170
+ // agent-end.ts:198 → `countActiveFromEntries(entries, { currentSessionId: ctx.sessionManager.getSessionId() })`
171
+ // agent-end.ts:294 → `countActiveFromEntries(ctx.sessionManager.getEntries(), { currentSessionId: ctx.sessionManager.getSessionId() })`
172
+ // (getSessionId 是 extension 侧既有取 sessionId 形态——pending-notifications
173
+ // index.ts:216、subagent-workflow session-lifecycle.ts 同款先例。)
174
+
175
+ it("用例 1:父 session 活跃 subagent 残留 → goal 现状口径(无基准)count>0 → defer 分支命中(虚增)", () => {
176
+ // fork 时刻快照:父 session 派了后台子代理(record 仍 running-resumable),
177
+ // 子 session 继承其 register 残留;goal 守卫读到的 entries = 本 session 文件
178
+ // 全量(含继承残留)。现状口径 = 无基准调用(agent-end.ts:198 逐字形态)。
179
+ const childEntries = [
180
+ registerEntry("parent-bg", "subagent", "sess-parent"),
181
+ registerEntry("child-bg", "subagent", "sess-child"),
182
+ ];
183
+ const pendingOps = countActiveFromEntries(childEntries); // ← goal 现状口径
184
+ // defer 分支判据(agent-end.ts:199 `if (pendingOps.count > 0)`)命中:
185
+ // 父 session 的任务让子 session 的 goal 停发 continuation 并 notify
186
+ // 「Goal waiting for 2 background task(s)」——计数含非本 session 任务 = 虚增。
187
+ expect(pendingOps.count).toBe(2);
188
+ expect(pendingOps.ids).toContain("parent-bg");
189
+ });
190
+
191
+ it("用例 2:父任务 record 终态后,core sweep 补注销落盘使现状口径归零(虚增仅限活跃窗口)", () => {
192
+ const dir = mkdtempSync(join(tmpdir(), "w6-goal-probe-sweep-"));
193
+ tmpDirs.push(dir);
194
+ const sessionFile = join(dir, "session-child.jsonl");
195
+ writeFileSync(
196
+ sessionFile,
197
+ `${JSON.stringify(registerEntry("parent-bg", "subagent", "sess-parent"))}\n`,
198
+ "utf8",
199
+ );
200
+ // 父任务 record 已终态(closed):core 对账 sweep 差集补发——appendEntry 权威
201
+ // 落盘到子 session 文件(sweep 的注销对「无基准口径」也生效:差集读侧不过滤,
202
+ // 补发的 unregister entry 抵消 register)。
203
+ const result = runReconcileSweep({
204
+ sessionFile,
205
+ lookupRecordState: () => ({ terminal: true, closedReason: "completed" }),
206
+ appendEntry: (customType, data) => {
207
+ writeFileSync(sessionFile, `${JSON.stringify({ customType, data })}\n`, { flag: "a" });
208
+ },
209
+ });
210
+ expect(result.reconciled).toEqual(["parent-bg"]);
211
+
212
+ // 落盘后 goal 守卫(无基准口径)重读 entries:计数归零——sweep 是现状口径的
213
+ // 唯一收敛通道,且只覆盖「record 可判定终态」的窗口;「父任务仍活跃」窗口
214
+ // (用例 1)无机制覆盖 → 虚增结论 (b) 成立。
215
+ const entriesAfter = readFileSync(sessionFile, "utf8")
216
+ .trim()
217
+ .split("\n")
218
+ .map((l) => JSON.parse(l));
219
+ expect(countActiveFromEntries(entriesAfter).count).toBe(0);
220
+ });
221
+ });