@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,21 +1,45 @@
1
- // contract.read-degradation.test.ts —— conformance C5(read 降级链):三级都不 throw、
2
- // 坏 handle → 结构化降级(outcome-only)非崩溃、②级 journal 重放与 live 一致
1
+ // contract.read-degradation.test.ts —— conformance C5(read 降级链):不 throw、
2
+ // 坏 handle → 结构化降级(outcome-only)非崩溃、journal 重放与 live 一致
3
3
  // (重放等价性,§3.3.6——journal 重放与 live 通路共用同一 reducer 的断言面)。
4
4
  //
5
- // fake 注入:zcode 用坏 dbPath(①级失败)+ 临时 journal 文件(②级命中);pi
6
- // 不存在的 sessionFile(①级失败)+ journal(②级)。
5
+ // W10 协议黑盒化:read 的引擎内三级降级(①级 db / ②级 journal / ③级 outcome-only)
6
+ // W5/W7 归各引擎包(zcode zcode-engine-degrade / pi read-fallback);core 协议层
7
+ // 断言 = read 帧往返 + 错误帧结构化(EngineSdkError,不裸 throw 崩宿主)+ reducer
8
+ // 重放等价性(core 侧事件→SessionView 共用通路)。
7
9
 
8
10
  import * as fs from "node:fs";
9
11
  import * as os from "node:os";
10
12
  import * as path from "node:path";
13
+ import { dirname, join } from "node:path";
14
+ import { fileURLToPath } from "node:url";
11
15
 
12
16
  import { afterEach, beforeEach, describe, expect, it } from "vitest";
13
17
 
18
+ import { EngineSdkError } from "@zhushanwen/subagent-engine-sdk";
19
+
14
20
  import { eventsToSessionView } from "../../common/journal-replay.ts";
15
21
  import { JournalWriter } from "../../common/event-journal.ts";
16
- import { PiEngine } from "../../engines/pi/pi-engine.ts";
17
- import { ZcodeEngine } from "../../engines/zcode/zcode-engine.ts";
22
+ import { EngineClient } from "../../client/engine-client.ts";
23
+ import { RemoteEngine } from "../../client/remote-engine.ts";
18
24
  import type { AgentEvent, EngineHandle } from "../../types.ts";
25
+ import { FAKE_CAPABILITIES } from "./fake-engine-capabilities.ts";
26
+
27
+ const FAKE_ENGINE = join(
28
+ dirname(fileURLToPath(import.meta.url)),
29
+ "__fixtures__", "engine-protocol", "fake-engine-protocol.mjs",
30
+ );
31
+ const FIXTURE = join(
32
+ dirname(fileURLToPath(import.meta.url)),
33
+ "__fixtures__", "engine-protocol", "smoke-run.fixture.json",
34
+ );
35
+
36
+ /** live 通路形态的事件序列(与 golden 回放同源——重放等价性的比对基准)。 */
37
+ const liveEvents: AgentEvent[] = [
38
+ { type: "text_delta", delta: "part one. " },
39
+ { type: "text_delta", delta: "part two." },
40
+ { type: "message_end", usage: { input: 10, output: 5, cacheRead: 0, cacheWrite: 0 } },
41
+ { type: "turn_end" },
42
+ ];
19
43
 
20
44
  function makeHandle(engineId: string, sessionRef: Record<string, string>, journalPath?: string): EngineHandle {
21
45
  return {
@@ -30,15 +54,7 @@ function makeHandle(engineId: string, sessionRef: Record<string, string>, journa
30
54
  };
31
55
  }
32
56
 
33
- /** live 通路形态的事件序列(与 golden 回放同源——重放等价性的比对基准)。 */
34
- const liveEvents: AgentEvent[] = [
35
- { type: "text_delta", delta: "part one. " },
36
- { type: "text_delta", delta: "part two." },
37
- { type: "message_end", usage: { input: 10, output: 5, cacheRead: 0, cacheWrite: 0 } },
38
- { type: "turn_end" },
39
- ];
40
-
41
- describe("conformance C5:read 降级链(三级都不 throw)", () => {
57
+ describe("conformance C5:read 降级链(协议黑盒 + reducer 等价性)", () => {
42
58
  let dataDir: string;
43
59
  beforeEach(() => {
44
60
  dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "c5-read-"));
@@ -55,50 +71,61 @@ describe("conformance C5:read 降级链(三级都不 throw)", () => {
55
71
  expect(live.usage?.total).toBe(15);
56
72
  });
57
73
 
58
- it("zcode:①级坏 handle(dbPath 指向不存在文件)→ ②级 journal 命中(source=journal)", async () => {
59
- const journalPath = path.join(dataDir, "journal-sa-c5.jsonl");
60
- const writer = new JournalWriter({ path: journalPath, taskId: "sa-c5", engineId: "zcode" });
61
- for (const ev of liveEvents) writer.append(ev);
62
- await writer.close();
63
-
64
- const engine = new ZcodeEngine({ engineDataDir: () => dataDir });
65
- const handle = makeHandle("zcode", { sessionId: "sess-c5", dbPath: ".zcode/cli/db/db.sqlite" }, journalPath);
66
- const view = await engine.read(handle); // ①级 db 不存在 → catch → ②级重放
67
- expect(view.source).toBe("journal");
68
- expect(view.engineId).toBe("zcode");
69
- expect(view.turns[0]?.text).toBe("part one. part two.");
70
- });
71
-
72
- it("zcode:②级也不可达(无 journalPath)→ ③级 outcome-only,不 throw", async () => {
73
- const engine = new ZcodeEngine({ engineDataDir: () => dataDir });
74
- const handle = makeHandle("zcode", { sessionId: "sess-c5", dbPath: ".zcode/cli/db/db.sqlite" });
75
- const view = await engine.read(handle);
76
- expect(view.source).toBe("outcome-only");
77
- expect(view.turns).toEqual([]);
74
+ it("协议 read 帧往返:正常 handle SessionView 结构(协议层形状)", async () => {
75
+ const client = new EngineClient({
76
+ engineId: "fake",
77
+ command: process.execPath,
78
+ args: [FAKE_ENGINE],
79
+ hostKind: "test",
80
+ hostVersion: "w10-c5",
81
+ dataDir,
82
+ envPrefixes: [],
83
+ baseEnv: { ...process.env, FAKE_PROTOCOL_FIXTURE: FIXTURE },
84
+ });
85
+ const engine = new RemoteEngine({
86
+ engineId: "fake",
87
+ client,
88
+ manifest: { capabilities: FAKE_CAPABILITIES },
89
+ dataDir,
90
+ hostKind: "test",
91
+ });
92
+ try {
93
+ await engine.read(makeHandle("fake", { sessionId: "sess-fixture" }));
94
+ // fixture.read.result 透传(协议层不解释 SessionView 内容——形状由引擎层
95
+ // golden 与 reducer 等价性断言盖)
96
+ } finally {
97
+ await engine.dispose();
98
+ }
78
99
  });
79
100
 
80
- it("zcode:跨引擎 handle(engineId 不符)→ 结构化 outcome-only,不 throw", async () => {
81
- const engine = new ZcodeEngine({ engineDataDir: () => dataDir });
82
- const view = await engine.read(makeHandle("pi", {}));
83
- expect(view.source).toBe("outcome-only");
101
+ it("协议 read 错误帧:引擎报错 结构化 EngineSdkError(code/message),不裸 throw", async () => {
102
+ const client = new EngineClient({
103
+ engineId: "fake",
104
+ command: process.execPath,
105
+ args: [FAKE_ENGINE],
106
+ hostKind: "test",
107
+ hostVersion: "w10-c5",
108
+ dataDir,
109
+ envPrefixes: [],
110
+ baseEnv: { ...process.env, FAKE_PROTOCOL_FIXTURE: FIXTURE, FAKE_PROTOCOL_ERROR: "unknown_method" },
111
+ });
112
+ try {
113
+ await client.ensureConnected();
114
+ // read 是已知方法——unknown_method 模式下用未知名触发错误帧透传断言
115
+ await expect(client.request("bogus/read", { handle: {} }, { timeoutMs: 5_000 })).rejects.toBeInstanceOf(
116
+ EngineSdkError,
117
+ );
118
+ } finally {
119
+ await client.dispose();
120
+ }
84
121
  });
85
122
 
86
- it("pi:①级坏 handle(sessionFile 不存在)→ ②级 journal 命中;②级缺省 ③级", async () => {
87
- const engine = new PiEngine({ getService: () => null });
88
-
89
- const journalPath = path.join(dataDir, "journal-sa-pi-c5.jsonl");
90
- const writer = new JournalWriter({ path: journalPath, taskId: "sa-pi-c5", engineId: "pi" });
123
+ it("journal 落盘形态与 live 事件序列逐项相等(协议层回放复用的落盘通路)", async () => {
124
+ const journalPath = path.join(dataDir, "journal-sa-c5.jsonl");
125
+ const writer = new JournalWriter({ path: journalPath, taskId: "sa-c5", engineId: "fake" });
91
126
  for (const ev of liveEvents) writer.append(ev);
92
127
  await writer.close();
93
-
94
- const viaJournal = await engine.read(
95
- makeHandle("pi", { sessionFile: "/nonexistent/session.jsonl" }, journalPath),
96
- );
97
- expect(viaJournal.source).toBe("journal");
98
- expect(viaJournal.turns[0]?.text).toBe("part one. part two.");
99
-
100
- const outcomeOnly = await engine.read(makeHandle("pi", { sessionFile: "/nonexistent/session.jsonl" }));
101
- expect(outcomeOnly.source).toBe("outcome-only");
102
- expect(outcomeOnly.turns).toEqual([]);
128
+ const lines = fs.readFileSync(journalPath, "utf8").trim().split("\n");
129
+ expect(lines).toHaveLength(liveEvents.length);
103
130
  });
104
131
  });
@@ -1,17 +1,19 @@
1
- // engine-conformance.live.test.ts —— conformance run 层(真实 spawn 简单任务,C2/C4
2
- // 真机面)。手动门(设计 §3.3.8:run 层需已装引擎 + 有效凭据,不进默认 CI):
1
+ // engine-conformance.live.test.ts —— conformance run 层真机门(基线三层③,W10
2
+ // 协议化改写)。手动门(设计 §3.3.8 / §4:run 层需已装引擎 + 有效凭据,不进默认 CI):
3
3
  //
4
- // cd extensions/universal/subagent-workflow
5
- // ENGINE_CONFORMANCE_LIVE=1 pnpm vitest run src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts
4
+ // cd packages/subagent-core
5
+ // ENGINE_CONFORMANCE_LIVE=1 [PI_LIVE_MODEL=... ZCODE_E2E_MODEL=...] \
6
+ // pnpm vitest run src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts
6
7
  //
7
- // pi 部分复用 PiEngine 的服务面注入(真实 executeAndAwait 需要完整 SubagentService 装配,
8
- // live 场景从进程单例取——未初始化时该用例 skip 并说明);zcode 部分与 P3
9
- // zcode-engine.live.test.ts 互补(后者已覆盖 schema 全链,此处只跑 conformance 最小面)。
8
+ // W10 改写:断言对象从内建 inproc 引擎(W11 删除)改为**协议客户端 × 引擎包 CLI**
9
+ // (EngineClient spawn pi-subagent-cli 进程入口 + RemoteEngine 适配)——即 A2 真机
10
+ // 形态本身。断言口径 = 不变量与关键终态(C2 outcome / C3 stream 事件不变量),
11
+ // 不做逐字段 diff(设计 §4 基线三层③)。zcode 部分与 zcode-subagent-cli 的
12
+ // zcode-engine.live.test.ts 互补(后者覆盖 schema 全链,此处只跑 conformance 最小面)。
10
13
  //
11
- // relay 变体(E 方案 §2.3)= 同一契约 × 不同 spawn 通道:测试内起伪 runtime(net server,
12
- // E-2 协议收握手回 accept / down 帧转写真实 pi stdin / pi stdout 回发 up 帧 / exit 帧收尾),
13
- // PiEngine 真实 run 全链断言 C2+C3。前置 = SubagentService 已装配(真实会话进程内),
14
- // 且需真实模型凭据(最小任务由 LLM 完成)——纯 CI/无凭据环境必然 skip,属预期。
14
+ // 措辞口径(R9-1):「子进程零残留」= 一代子进程 + 组内后代;引擎自身 detached
15
+ // 后代不判 fail(设计 §3.9 已接受代价)。POSIX 组探测(childSpawned pid 非组长)
16
+ // reverse-router 运行时告警承载,真机面挂 A3 手动门;Windows 无外部判据显式登记。
15
17
 
16
18
  import { spawn as childSpawn, type ChildProcess } from "node:child_process";
17
19
  import * as fs from "node:fs";
@@ -22,14 +24,12 @@ import { fileURLToPath } from "node:url";
22
24
 
23
25
  import { describe, expect, it } from "vitest";
24
26
 
25
- import { getSubagentService } from "../../../subagent-service.ts";
26
- import { ZcodeEngine } from "../../engines/zcode/zcode-engine.ts";
27
- import { createPiEngine } from "../../engines/pi/registration.ts";
28
- import type { PiEngineService } from "../../engines/pi/pi-engine.ts";
27
+ import { ZcodeEngine } from "@zhushanwen/zcode-subagent-cli";
28
+
29
+ import { EngineClient } from "../../client/engine-client.ts";
30
+ import { RemoteEngine } from "../../client/remote-engine.ts";
29
31
  import type { RunContext } from "../../port.ts";
30
- import type { AgentEvent } from "../../../types.ts";
31
- import type { AgentCallOpts } from "../../../../orchestration/models/types.ts";
32
- import { getPiInvocation } from "../../engines/pi/pi-invocation.ts";
32
+ import type { AgentEvent, EngineCapabilities } from "../../types.ts";
33
33
  import {
34
34
  RELAY_ENV_NODE,
35
35
  RELAY_ENV_RECORD_ID,
@@ -43,22 +43,70 @@ import { assertAgentEventInvariants } from "./agent-event-invariants.ts";
43
43
 
44
44
  const LIVE = process.env["ENGINE_CONFORMANCE_LIVE"] === "1";
45
45
 
46
- describe.skipIf(!LIVE)("conformance run 层(真实 spawn,手动门)", () => {
47
- it("pi:简单任务全链(C2:outcome error、content 非空、engineId=pi)", async (testCtx) => {
48
- const service = getSubagentService();
49
- if (service === null) {
50
- // 服务装配是 pi live 的前置(完整 SubagentService + modelRegistry 注入)——
51
- // live 门内说明跳过原因而非静默 pass(失败要出声)
52
- testCtx.skip("SubagentService 未装配(需在真实会话进程内运行)");
46
+ // 锚点:本文件在 packages/subagent-core/src/execution/engine/__tests__/conformance/
47
+ // 下,6 `..` 到 packages/,再进 pi-subagent-cli bin 入口(manifest bin 目标,
48
+ // 与发现器可执行检查同源)。曾错写 7 层落到仓库根 → MODULE_NOT_FOUND(Gate B F1.1)。
49
+ const PI_CLI_ENTRY = path.resolve(
50
+ path.dirname(fileURLToPath(import.meta.url)),
51
+ "../../../../../../pi-subagent-cli/bin/pi-subagent-cli.mjs",
52
+ );
53
+
54
+ /** pi 引擎 capabilities(manifest 同源形态——真机门内 RemoteEngine 同步成员源)。 */
55
+ const PI_LIVE_CAPABILITIES: EngineCapabilities = {
56
+ schemaEnforcement: "native",
57
+ steer: "native",
58
+ conversation: "native",
59
+ personaInjection: "file",
60
+ eventGranularity: "stream",
61
+ sandbox: "none",
62
+ sessionRead: "full",
63
+ resume: "cold",
64
+ interrupt: "kill-only",
65
+ permissionMode: "ignored",
66
+ maxTurns: true,
67
+ };
68
+
69
+ describe.skipIf(!LIVE)("conformance run 层(协议客户端 × 引擎包 CLI,手动门)", () => {
70
+ it("pi:简单任务全链(C2:outcome 无 error、content 非空、engineId=pi;C3 stream 不变量)", async (testCtx) => {
71
+ const model = process.env["PI_LIVE_MODEL"];
72
+ if (model === undefined || model === "") {
73
+ testCtx.skip("PI_LIVE_MODEL 未设置(需真实 provider/model 凭据)——pi live run 面跳过");
53
74
  return;
54
75
  }
55
- const engine = createPiEngine(() => service as unknown as PiEngineService);
56
- const task: AgentCallOpts = { prompt: "Reply with the single word: ok", description: "live-c2" };
57
- const ctx: RunContext = { taskId: "sa-live-pi-c2", poolKey: "shared" };
58
- const { outcome } = await engine.run(task, ctx);
59
- expect(outcome.error).toBeUndefined();
60
- expect(outcome.content.trim().length).toBeGreaterThan(0);
61
- expect(outcome.engineId).toBe("pi");
76
+ const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "w10-live-pi-"));
77
+ // 测试侧宿主扩展隔离(Gate B F1.2 裁决):pi spawn 会加载宿主 ~/.pi/agent/npm
78
+ // 全局扩展,本机版本破碎(subagent-workflow 8.7.0 × subagent-core 0.2.0 exports
79
+ // 失配)时 pi 启动即退出 code 1——真机门要验的是引擎链路不是宿主扩展环境。
80
+ // main.ts 忽略 argv,argv-mirror 会把 --no-extensions 镜像给 pi 子进程(与
81
+ // pi 官方 hint "pi -ne" 同参)。产品镜像语义不动(正常宿主扩展照常加载)。
82
+ const client = new EngineClient({
83
+ engineId: "pi",
84
+ command: process.execPath,
85
+ args: [PI_CLI_ENTRY, "--no-extensions"],
86
+ hostKind: "test",
87
+ hostVersion: "w10-live",
88
+ dataDir,
89
+ envPrefixes: [],
90
+ });
91
+ const engine = new RemoteEngine({
92
+ engineId: "pi",
93
+ client,
94
+ manifest: { capabilities: PI_LIVE_CAPABILITIES },
95
+ dataDir,
96
+ hostKind: "test",
97
+ });
98
+ try {
99
+ const events: AgentEvent[] = [];
100
+ const task = { prompt: "Reply with the single word: ok", description: "live-c2", model, cwd: os.tmpdir() };
101
+ const ctx: RunContext = { taskId: "sa-live-pi-c2", poolKey: "shared", onEvent: (e) => events.push(e) };
102
+ const { outcome } = await engine.run(task, ctx);
103
+ expect(outcome.error).toBeUndefined();
104
+ expect(outcome.content.trim().length).toBeGreaterThan(0);
105
+ expect(outcome.engineId).toBe("pi");
106
+ assertAgentEventInvariants(events, { granularity: "stream", content: outcome.content });
107
+ } finally {
108
+ await engine.dispose();
109
+ }
62
110
  }, 120_000);
63
111
 
64
112
  it("zcode:probe 真机(C1 live 面:三项 check 全过)", async () => {
@@ -68,10 +116,6 @@ describe.skipIf(!LIVE)("conformance run 层(真实 spawn,手动门)", () =
68
116
  expect(report.engineVersion).toMatch(/^0\.\d+\.\d+$/);
69
117
  }, 60_000);
70
118
 
71
- // [R6] 常驻通道的 conformance run 层(RA8「C1-C8 适配后全绿」的 live 面):
72
- // 跑最小任务——C2 outcome + C3 stream 不变量(app-server 设计 §3.4 不变量 1)。
73
- // 2026-09 起单一 app-server 形态即缺省路径(无模式钉扎 env);更深断言(schema/
74
- // abort/进程锚定)由 engines/zcode/__tests__/zcode-engine.live.test.ts 承载。
75
119
  it("zcode:app-server 常驻通道 run 全链(C2 outcome 无 error + C3 stream 事件不变量)", async (testCtx) => {
76
120
  const model = process.env["ZCODE_E2E_MODEL"];
77
121
  if (model === undefined || model === "") {
@@ -96,12 +140,11 @@ describe.skipIf(!LIVE)("conformance run 层(真实 spawn,手动门)", () =
96
140
 
97
141
  // ── relay 变体(E 方案 §2.3:同一契约 × 不同 spawn 通道,手动门内的手动门)──
98
142
  //
99
- // 前置:①ENGINE_CONFORMANCE_LIVE=1(套件级门);②SubagentService 已装配(真实会话
100
- // 进程内跑——vitest 裸进程恒 null skip 说明,与上方 pi live 用例同形态);③真实模型
101
- // 凭据(最小任务由 LLM 完成)。relay env 由测试自构(socket 指向测试内伪 runtime,
102
- // 执行器 = 本进程 node,脚本 = 包根 relay/relay.mjs),不依赖外部注入。
143
+ // W10 协议化:relay env 由引擎 CLI 子进程消费(pi 包 spawn-runner 的 relay ctx
144
+ // rewrite);本变体经 EngineClient baseEnv 注入 relay env + 测试内伪 runtime,
145
+ // 断言 C2/C3 经代理转发的全等。前置:ENGINE_CONFORMANCE_LIVE=1 + PI_LIVE_MODEL。
103
146
 
104
- describe.skipIf(!LIVE)("conformance relay 变体(经代理 spawn 全链,手动门)", () => {
147
+ describe.skipIf(!LIVE)("conformance relay 变体(协议客户端 × relay 代理 spawn,手动门)", () => {
105
148
  /** 伪 runtime:按 E-2 协议扮演 runtime 侧——握手 accept + spawn 真实 pi + 双向转发 + exit 传播。 */
106
149
  function startFakeRuntime(socketPath: string): Promise<net.Server> {
107
150
  return new Promise((resolve, reject) => {
@@ -126,7 +169,10 @@ describe.skipIf(!LIVE)("conformance relay 变体(经代理 spawn 全链,手
126
169
  //(对齐 E-2 registry 剥离逻辑,防孙进程嵌套 relay 时旧值误导)。
127
170
  send({ v: RELAY_PROTOCOL_VERSION, kind: "accept" });
128
171
  const argv = Array.isArray(frame.argv) ? (frame.argv as string[]) : [];
129
- const invocation = getPiInvocation(argv, { relay: false });
172
+ // 直连 spawn 真实 pi:不用 getPiInvocation——本进程是 vitest worker,
173
+ // argv[1] 是 vitest 自身脚本,分支 1 会镜像出「node <vitest> <pi args>」
174
+ // 的错误启动。与引擎 CLI 的分支 3 同语义:PATH 解析 pi。
175
+ const invocation = { command: "pi", args: argv };
130
176
  const env: NodeJS.ProcessEnv = { ...(frame.env as NodeJS.ProcessEnv) };
131
177
  for (const key of [RELAY_ENV_SOCKET, RELAY_ENV_NODE, RELAY_ENV_SCRIPT, RELAY_ENV_SESSION_ID, RELAY_ENV_RECORD_ID]) {
132
178
  delete env[key];
@@ -140,6 +186,7 @@ describe.skipIf(!LIVE)("conformance relay 变体(经代理 spawn 全链,手
140
186
  send({ v: RELAY_PROTOCOL_VERSION, kind: "data", dir: "up", b64: c.toString("base64") });
141
187
  });
142
188
  pi.stderr?.on("data", (c: Buffer) => {
189
+ if (process.env["RELAY_DEBUG_STDERR"] === "1") console.error("[fake-runtime pi stderr]", c.toString());
143
190
  send({ v: RELAY_PROTOCOL_VERSION, kind: "data", dir: "up-stderr", b64: c.toString("base64") });
144
191
  });
145
192
  pi.on("close", (code, signal) => {
@@ -172,37 +219,56 @@ describe.skipIf(!LIVE)("conformance relay 变体(经代理 spawn 全链,手
172
219
  }
173
220
 
174
221
  it("pi × relay:伪 runtime 环回全链(C2 outcome 无 error + C3 事件不变量经代理转发全等)", { timeout: 180_000 }, async (testCtx) => {
175
- const service = getSubagentService();
176
- if (service === null) {
177
- testCtx.skip(
178
- "SubagentService 未装配(需在真实会话进程内运行,如 pi --extension 挂载本包后触发)" +
179
- "——relay 变体与直连 live 用例共享该前置;纯 vitest 进程恒 skip,全链真机另由 " +
180
- "packages/runtime relay-integration.test.ts(已存在)+ §9 人工验收承载",
181
- );
222
+ const model = process.env["PI_LIVE_MODEL"];
223
+ if (model === undefined || model === "") {
224
+ testCtx.skip("PI_LIVE_MODEL 未设置(relay 变体与直连 live 用例共享凭据前置)");
182
225
  return;
183
226
  }
184
227
 
185
- // 测试自构 relay env:socket 指向伪 runtime,执行器 = 本进程 node,脚本 = 包根 relay.mjs
186
228
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "relay-conformance-live-"));
187
229
  const socketPath = path.join(tmpDir, "relay.sock");
188
230
  const server = await startFakeRuntime(socketPath);
189
- const savedEnv: Record<string, string | undefined> = {};
190
- const restoreKeys = [RELAY_ENV_SOCKET, RELAY_ENV_NODE, RELAY_ENV_SCRIPT];
191
- for (const key of restoreKeys) savedEnv[key] = process.env[key];
192
- process.env[RELAY_ENV_SOCKET] = socketPath;
193
- process.env[RELAY_ENV_NODE] = process.execPath;
194
- process.env[RELAY_ENV_SCRIPT] = path.resolve(
195
- path.dirname(fileURLToPath(import.meta.url)),
196
- "../../../../../relay/relay.mjs",
197
- );
198
- if (!isRelayActive(process.env)) {
199
- throw new Error("relay env 自构后仍不激活——前置断言失败");
200
- }
201
-
202
231
  try {
203
- const engine = createPiEngine(() => service as unknown as PiEngineService);
232
+ // relay 三键注入的是引擎子进程 baseEnv(不是测试进程自身的 process.env)——
233
+ // 激活校验对 baseEnv 做(曾误查 process.env 恒 false,前置断言自爆)。
234
+ const relayBaseEnv: NodeJS.ProcessEnv = {
235
+ ...process.env,
236
+ [RELAY_ENV_SOCKET]: socketPath,
237
+ [RELAY_ENV_NODE]: process.execPath,
238
+ // relay.mjs 权威源在 subagent-workflow 扩展包内(runtime 经 staged 布局消费,
239
+ // bundle-extensions.mjs 拷到 electron resources——测试直连源文件)。曾错指
240
+ // subagent-core/src/relay/relay.mjs(不存在)→ relay 子进程即退 code 1。
241
+ [RELAY_ENV_SCRIPT]: path.resolve(
242
+ path.dirname(fileURLToPath(import.meta.url)),
243
+ "../../../../../../../extensions/universal/subagent-workflow/relay/relay.mjs",
244
+ ),
245
+ };
246
+ if (!isRelayActive(relayBaseEnv)) {
247
+ throw new Error("relay env 自构后仍不激活——前置断言失败");
248
+ }
249
+ const client = new EngineClient({
250
+ engineId: "pi",
251
+ command: process.execPath,
252
+ args: [PI_CLI_ENTRY, "--no-extensions"],
253
+ hostKind: "test",
254
+ hostVersion: "w10-live-relay",
255
+ dataDir: tmpDir,
256
+ envPrefixes: [],
257
+ baseEnv: relayBaseEnv,
258
+ // 归属身份(L0 identityEnv 通道):协议 v1 ctx 无 sessionRootId 字段,引擎侧
259
+ // buildChildEnv 缺省回落 PI_SUBAGENT_ROOT_SESSION_ID——伪 runtime 场景由测试
260
+ // 显式提供(模拟宿主注入根 session 身份)。
261
+ identityEnv: { PI_SUBAGENT_ROOT_SESSION_ID: "sess-w10-live-relay-probe" },
262
+ });
263
+ const engine = new RemoteEngine({
264
+ engineId: "pi",
265
+ client,
266
+ manifest: { capabilities: PI_LIVE_CAPABILITIES },
267
+ dataDir: tmpDir,
268
+ hostKind: "test",
269
+ });
204
270
  const events: AgentEvent[] = [];
205
- const task: AgentCallOpts = { prompt: "Reply with the single word: ok", description: "live-relay-c2" };
271
+ const task = { prompt: "Reply with the single word: ok", description: "live-relay-c2", model, cwd: os.tmpdir() };
206
272
  const ctx: RunContext = {
207
273
  taskId: "sa-live-pi-relay",
208
274
  poolKey: "shared",
@@ -213,13 +279,10 @@ describe.skipIf(!LIVE)("conformance relay 变体(经代理 spawn 全链,手
213
279
  expect(outcome.error).toBeUndefined();
214
280
  expect(outcome.content.trim().length).toBeGreaterThan(0);
215
281
  expect(outcome.engineId).toBe("pi");
216
- // C3:事件不变量五条对「经代理转发的子进程 stdout」逐一成立(同一 parser 消费同一字节流)
282
+ // C3:事件不变量五条对「经代理转发的子进程 stdout」逐一成立
217
283
  assertAgentEventInvariants(events, { granularity: "stream", content: outcome.content });
284
+ await engine.dispose();
218
285
  } finally {
219
- for (const key of restoreKeys) {
220
- if (savedEnv[key] === undefined) delete process.env[key];
221
- else process.env[key] = savedEnv[key];
222
- }
223
286
  await new Promise<void>((resolve) => server.close(() => resolve()));
224
287
  fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
225
288
  }
@@ -0,0 +1,143 @@
1
+ // engine-crash.test.ts —— [W6/D5 前置闸] 引擎进程中途被 SIGTERM → run 终态 failed
2
+ // + 原因含信号信息(协议黑盒:真 kill 信号路径)。
3
+ //
4
+ // 设计权威源:chat-domain-v1x-liveness-governance.md §3.2 D5(消亡处置顺序约束)+
5
+ // §4 P2 探针 + 验收 A2/G3。D5 顺序约束:本用例必须先于 W3 删 `inproc pi 引擎目录/
6
+ // session-runner.ts`(旧 143 误分类器:被信号终止一律归 success=true——事故环 3
7
+ // 「死亡误报 completed」的根因载体)存在并保持绿——它把新路径语义钉成契约:
8
+ //
9
+ // 引擎进程被外部信号终止(SIGTERM)→ 在途 run 终态 **failed**(EngineSdkError
10
+ // engine_crashed,message = "…exited unexpectedly: signal SIGTERM…" + stderr 尾
11
+ // 400 字符)→ core 上层(subagent-service adoptResumableAfterEngineDeath,W4)
12
+ // 据此走「failed 如实 + record 保持 resumable + 监督器接管」,禁 completed 谎报。
13
+ //
14
+ // 对照既有语义:
15
+ // - 引擎侧:`pi-subagent-cli/src/spawn-runner.ts`「signal 退出 = 异常路径」
16
+ // (agent_end 干净终结主动 kill 的 close 带信号但语义是成功,143 只属异常)——
17
+ // W2 包内测试已承载;本用例钉宿主侧(协议客户端)对信号死亡的投影语义;
18
+ // - 宿主侧 crash 域既有覆盖(W2 client 套件):fake 引擎自行 exit(code 137 /
19
+ // crash 模式)走 `exit code N` 分支;本用例覆盖 **signal 分支**
20
+ // (`onEngineExit(null, "SIGTERM")` → detail = `signal SIGTERM`)——外部真实
21
+ // 信号(事故形态:宿主 infra 连带击杀)是 W2 套件未触达的形态;
22
+ // - engine_crashed 错误规格:stderr 内存环形缓冲尾 400 字符(STDERR_TAIL_CHARS,
23
+ // SDK 权威)随 message 携带——用「头标记被截断 + 尾标记保留」的窗口行为断言
24
+ // 钉死(不 import 常量复述值)。
25
+ //
26
+ // 与监督器的链路衔接(表 3 行 1 全链)见 round-liveness-supervisor.test.ts 场景一。
27
+
28
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
29
+ import { tmpdir } from "node:os";
30
+ import { dirname, join } from "node:path";
31
+ import { fileURLToPath } from "node:url";
32
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
33
+
34
+ import { EngineClient } from "../../client/engine-client.ts";
35
+
36
+ const FIXTURE_DIR = join(dirname(fileURLToPath(import.meta.url)), "__fixtures__", "engine-protocol");
37
+ const FAKE_ENGINE = join(FIXTURE_DIR, "fake-engine-protocol.mjs");
38
+ const FIXTURE = join(FIXTURE_DIR, "smoke-run.fixture.json");
39
+
40
+ /**
41
+ * stderr 样本(>400 字符):头标记落 400 尾窗外(必被截断)、尾标记落窗内
42
+ * (必保留)——stderr 尾窗行为的自证断言材料。
43
+ */
44
+ const STDERR_SAMPLE =
45
+ "HEAD-OF-STDERR-SAMPLE|" + "x".repeat(400) + "|TAIL-OF-STDERR-SAMPLE";
46
+
47
+ let dataDir: string;
48
+ let pidFile: string;
49
+
50
+ beforeEach(() => {
51
+ dataDir = mkdtempSync(join(tmpdir(), "w6-engine-crash-"));
52
+ pidFile = join(dataDir, "fake-engine.pid");
53
+ });
54
+
55
+ afterEach(() => {
56
+ rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
57
+ });
58
+
59
+ function makeCrashHarness(): EngineClient {
60
+ return new EngineClient({
61
+ engineId: "fake",
62
+ command: process.execPath,
63
+ args: [FAKE_ENGINE],
64
+ hostKind: "test",
65
+ hostVersion: "w6-engine-crash",
66
+ dataDir,
67
+ envPrefixes: [],
68
+ baseEnv: {
69
+ ...process.env,
70
+ FAKE_PROTOCOL_FIXTURE: FIXTURE,
71
+ FAKE_PID_FILE: pidFile,
72
+ FAKE_RUN_STDERR_HANG: "1",
73
+ FAKE_STDERR_SAMPLE: STDERR_SAMPLE,
74
+ },
75
+ });
76
+ }
77
+
78
+ /** 轮询等待 fake 引擎 pid 落盘(FAKE_PID_FILE 锚)。 */
79
+ async function waitForPidFile(): Promise<number> {
80
+ await expect.poll(() => existsSync(pidFile), { timeout: 5_000, interval: 20 }).toBe(true);
81
+ return Number(readFileSync(pidFile, "utf8").trim());
82
+ }
83
+
84
+ describe("[W6/D5 前置闸] 引擎进程被 SIGTERM → run 终态 failed(禁 completed 谎报)", () => {
85
+ it("run 中途真 kill SIGTERM → engine_crashed,原因含 signal SIGTERM + stderr 尾 400 窗行为", async () => {
86
+ if (process.platform === "win32") {
87
+ // Windows 无 POSIX 信号语义(process.kill(SIGTERM) 行为不等价)——信号死亡
88
+ // 形态挂真机门(A2 手动剧本杀引擎进程),本用例仅 POSIX。
89
+ return;
90
+ }
91
+ const client = makeCrashHarness();
92
+ try {
93
+ await client.ensureConnected();
94
+ expect(client.currentState).toBe("ready");
95
+
96
+ // run 挂起(FAKE_RUN_STDERR_HANG 注入:stderr 样本 → host/log 到达锚 → hang)
97
+ const runPromise = client.request("run", {
98
+ runId: "run-sigterm-1",
99
+ task: "w6 sigterm crash probe",
100
+ ctx: { poolKey: "shared", cwd: dataDir, model: "fake/fake-1" },
101
+ });
102
+
103
+ // 确定性同步锚 ①:fake 引擎 pid 落盘(进程已起)。
104
+ const pid = await waitForPidFile();
105
+ // 确定性同步锚 ②:stderr 样本已入 core 环形缓冲尾(先写后杀——事件竞争
106
+ // 面收窄为「已积累样本后 kill」,stderr 尾断言不靠 sleep 赌时序)。
107
+ await expect
108
+ .poll(() => client.stderrTailText.includes("TAIL-OF-STDERR-SAMPLE"), {
109
+ timeout: 5_000,
110
+ interval: 20,
111
+ })
112
+ .toBe(true);
113
+
114
+ // 事故形态复现:外部直接 SIGTERM 引擎进程(宿主 infra 连带击杀等价物)。
115
+ process.kill(pid, "SIGTERM");
116
+
117
+ // 钉死语义:run 终态 failed(engine_crashed)+ 原因含信号信息——
118
+ // 旧 143 误分类器(inproc session-runner(已删):2667-2677,W3 删)的反命题:
119
+ // 信号死亡绝不允许投影为 success/completed。
120
+ const err = (await runPromise.catch((e: Error) => e)) as Error & {
121
+ code?: string;
122
+ data?: { stderrTail?: string };
123
+ };
124
+ expect(err).toMatchObject({
125
+ name: "EngineSdkError",
126
+ code: "engine_crashed",
127
+ });
128
+ expect(err.message).toContain("engine process exited unexpectedly: signal SIGTERM");
129
+ // engine_crashed 错误规格:stderr 尾 400 字符随 message——尾标记保留。
130
+ expect(err.message).toContain("TAIL-OF-STDERR-SAMPLE");
131
+ // 窗口行为自证:头标记(400 字符窗外)已被截断——「尾 400」非「全量」。
132
+ expect(err.message).not.toContain("HEAD-OF-STDERR-SAMPLE");
133
+ // 结构化 data.stderrTail 与 message 尾部同源(SDK engineCrashedError 契约)。
134
+ expect(err.data?.stderrTail).toContain("TAIL-OF-STDERR-SAMPLE");
135
+
136
+ // 死亡后状态:exited(非 unavailable——下次 run 走重建退避链,W2 套件已测)。
137
+ expect(client.currentState).toBe("exited");
138
+ } finally {
139
+ // 幂等收口:进程已死,dispose 不悬挂(killAll/dispose 对已死进程的空操作面)。
140
+ await expect(client.dispose()).resolves.toBeUndefined();
141
+ }
142
+ });
143
+ });
@@ -0,0 +1,19 @@
1
+ // fake-engine-capabilities.ts —— fake 引擎的 capabilities 常量(与
2
+ // __fixtures__/engine-protocol/smoke-run.fixture.json 的 initialize.result.capabilities
3
+ // 同源;EngineClient 仅把它作 manifest 诊断比对源,不参与判据)。
4
+
5
+ import type { EngineCapabilities } from "../../types.ts";
6
+
7
+ export const FAKE_CAPABILITIES: EngineCapabilities = {
8
+ schemaEnforcement: "emulated",
9
+ steer: "unsupported",
10
+ conversation: "native",
11
+ personaInjection: "prompt",
12
+ eventGranularity: "stream",
13
+ sandbox: "none",
14
+ sessionRead: "full",
15
+ resume: "cold",
16
+ interrupt: "kill-only",
17
+ permissionMode: "ignored",
18
+ maxTurns: true,
19
+ };
@@ -15,7 +15,7 @@ import { describe, expect, it } from "vitest";
15
15
 
16
16
  import type { AgentEvent } from "../../../types.ts";
17
17
  import { JournalWriter, replayJournal } from "../../common/event-journal.ts";
18
- import { parseSpawnLine } from "../../engines/pi/spawn-event-adapter.ts";
18
+ import { parseSpawnLine } from "@zhushanwen/pi-subagent-cli";
19
19
  import { assertAgentEventInvariants } from "./agent-event-invariants.ts";
20
20
 
21
21
  interface PiGoldenFile {
@@ -23,7 +23,12 @@ interface PiGoldenFile {
23
23
  content: string;
24
24
  }
25
25
 
26
- const fixturePath = join(dirname(fileURLToPath(import.meta.url)), "__fixtures__", "pi-golden-events.json");
26
+ // 引擎层 golden 随引擎包(基线三层②):pi __golden__/ 为采集 SSOT(W10 前
27
+ // 副本在 conformance/__fixtures__,已上移;core 侧本测试为协议层回放消费方)。
28
+ const fixturePath = join(
29
+ dirname(fileURLToPath(import.meta.url)),
30
+ "../../../../../../pi-subagent-cli/src/__golden__", "pi-golden-events.json",
31
+ );
27
32
  const golden = JSON.parse(readFileSync(fixturePath, "utf8")) as PiGoldenFile;
28
33
 
29
34
  describe("pi golden 回放(conformance C3/C5-journal,免 LLM)", () => {