@zhushanwen/pi-subagent-workflow 8.6.0 → 8.8.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 (364) hide show
  1. package/README.md +5 -11
  2. package/package.json +13 -20
  3. package/relay/relay.mjs +1 -1
  4. package/skills/workflow-script-format/SKILL.md +32 -13
  5. package/src/host/__tests__/pi-host.test.ts +235 -0
  6. package/src/host/pi-host.ts +163 -0
  7. package/src/index.ts +141 -138
  8. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  9. package/src/injectors/__tests__/engine-section-stability.test.ts +343 -0
  10. package/src/injectors/__tests__/model-list-injector.test.ts +18 -21
  11. package/src/injectors/__tests__/subagent-list-injector.test.ts +59 -19
  12. package/src/injectors/__tests__/workflow-list-injector.test.ts +31 -17
  13. package/src/injectors/engine-awareness.ts +119 -0
  14. package/src/injectors/model-list-injector.ts +15 -58
  15. package/src/injectors/subagent-list-injector.ts +56 -114
  16. package/src/injectors/workflow-list-injector.ts +37 -70
  17. package/src/interface/__tests__/detectors.test.ts +48 -37
  18. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +2 -2
  19. package/src/interface/__tests__/subagent-tool-prompt.test.ts +6 -3
  20. package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +216 -0
  21. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +17 -6
  22. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +38 -2
  23. package/src/interface/bg-notify-render.ts +7 -17
  24. package/src/interface/command-actions.ts +5 -15
  25. package/src/interface/commands.ts +4 -4
  26. package/src/interface/format.ts +17 -6
  27. package/src/interface/gui-mappers.ts +18 -22
  28. package/src/interface/helpers.ts +9 -130
  29. package/src/interface/list-component.ts +5 -5
  30. package/src/interface/list-shared.ts +3 -3
  31. package/src/interface/list-view.ts +3 -3
  32. package/src/interface/subagent-actions.ts +66 -439
  33. package/src/interface/subagent-tool-schema.ts +21 -17
  34. package/src/interface/subagent-tool.ts +12 -7
  35. package/src/interface/subagents.ts +4 -4
  36. package/src/interface/tool-render.ts +37 -15
  37. package/src/interface/tool-workflow-script.ts +34 -76
  38. package/src/interface/tool-workflow.ts +57 -94
  39. package/src/interface/views/WorkflowsView.ts +8 -16
  40. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  41. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  42. package/src/interface/views/detail-content.ts +4 -4
  43. package/src/interface/views/format.ts +23 -62
  44. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +137 -225
  45. package/agents/analyst.md +0 -61
  46. package/agents/coder.md +0 -70
  47. package/agents/debugger.md +0 -67
  48. package/agents/doc-reviewer.md +0 -50
  49. package/agents/explorer.md +0 -64
  50. package/agents/general-purpose.md +0 -32
  51. package/agents/orchestrator.md +0 -63
  52. package/agents/planner.md +0 -54
  53. package/agents/researcher.md +0 -65
  54. package/agents/reviewer.md +0 -74
  55. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  56. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  57. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  58. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  59. package/src/execution/__tests__/alive-store.test.ts +0 -147
  60. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  61. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  62. package/src/execution/__tests__/bg-notify-render.test.ts +0 -329
  63. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  64. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -601
  65. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  66. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  67. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  68. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  69. package/src/execution/__tests__/config.test.ts +0 -110
  70. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  71. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  72. package/src/execution/__tests__/delivery-methods.test.ts +0 -422
  73. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  74. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  75. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  76. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  77. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  78. package/src/execution/__tests__/execution-record.test.ts +0 -1394
  79. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +0 -171
  80. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  81. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  82. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -169
  83. package/src/execution/__tests__/format.test.ts +0 -458
  84. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  85. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  86. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  87. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  88. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -242
  89. package/src/execution/__tests__/host-mode.test.ts +0 -87
  90. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  91. package/src/execution/__tests__/index-session-start.test.ts +0 -672
  92. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  93. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -383
  94. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  95. package/src/execution/__tests__/list-component.test.ts +0 -401
  96. package/src/execution/__tests__/list-fields.test.ts +0 -140
  97. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  98. package/src/execution/__tests__/model-resolver.test.ts +0 -465
  99. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  100. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  101. package/src/execution/__tests__/notifier-flush.test.ts +0 -612
  102. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  103. package/src/execution/__tests__/notify-ledger.test.ts +0 -826
  104. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  105. package/src/execution/__tests__/output-collector.test.ts +0 -358
  106. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  107. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  108. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  109. package/src/execution/__tests__/record-store.test.ts +0 -1057
  110. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  111. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  112. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  113. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  114. package/src/execution/__tests__/relay-env.test.ts +0 -42
  115. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  116. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  117. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  118. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  119. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -687
  120. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  121. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  122. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  123. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -199
  124. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  125. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  126. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  127. package/src/execution/__tests__/session-pending.test.ts +0 -197
  128. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  129. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  130. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -350
  131. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  132. package/src/execution/__tests__/spawn-args.test.ts +0 -445
  133. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  134. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  135. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  136. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  137. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  138. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  139. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  140. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  141. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  142. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  143. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  144. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  145. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  146. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  147. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -599
  148. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  149. package/src/execution/__tests__/timeout-integration.test.ts +0 -615
  150. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  151. package/src/execution/__tests__/tool-action.test.ts +0 -476
  152. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  153. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  154. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  155. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  156. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  157. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  158. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  159. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  160. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  161. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  162. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  163. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  164. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  165. package/src/execution/agent-registry.ts +0 -199
  166. package/src/execution/agent-result-mapper.ts +0 -90
  167. package/src/execution/alive-store.ts +0 -92
  168. package/src/execution/argv-mirror.ts +0 -113
  169. package/src/execution/best-effort.ts +0 -38
  170. package/src/execution/channel-registry-access.ts +0 -144
  171. package/src/execution/concurrency-pool.ts +0 -116
  172. package/src/execution/config.ts +0 -96
  173. package/src/execution/dialog-queue.ts +0 -330
  174. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  175. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  176. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  177. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  178. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  179. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  180. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  181. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  182. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  183. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  184. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  185. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  186. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  187. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  188. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  189. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  190. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  191. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  192. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  193. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  194. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  195. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  196. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  197. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  198. package/src/execution/engine/common/data-dir.ts +0 -62
  199. package/src/execution/engine/common/errors.ts +0 -183
  200. package/src/execution/engine/common/event-journal.ts +0 -254
  201. package/src/execution/engine/common/journal-replay.ts +0 -62
  202. package/src/execution/engine/common/kill-chain.ts +0 -221
  203. package/src/execution/engine/common/nesting-guard.ts +0 -50
  204. package/src/execution/engine/common/persona-router.ts +0 -108
  205. package/src/execution/engine/common/pool-manager.ts +0 -226
  206. package/src/execution/engine/common/schema-emulation.ts +0 -189
  207. package/src/execution/engine/common/session-view-projection.ts +0 -51
  208. package/src/execution/engine/engine-discovery.ts +0 -65
  209. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  210. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  211. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  212. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  213. package/src/execution/engine/engines/pi/reader.ts +0 -48
  214. package/src/execution/engine/engines/pi/registration.ts +0 -35
  215. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  216. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  217. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  218. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  219. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  220. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  221. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  222. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  223. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  224. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  225. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  226. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  227. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  228. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  229. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  230. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  231. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -658
  232. package/src/execution/engine/host-task-spec.ts +0 -47
  233. package/src/execution/engine/model-prompt.ts +0 -59
  234. package/src/execution/engine/paths.ts +0 -42
  235. package/src/execution/engine/port.ts +0 -153
  236. package/src/execution/engine/registry.ts +0 -123
  237. package/src/execution/engine/routing.ts +0 -218
  238. package/src/execution/engine/types.ts +0 -309
  239. package/src/execution/execute-options-mapper.ts +0 -112
  240. package/src/execution/execution-record.ts +0 -965
  241. package/src/execution/finalize-record.ts +0 -251
  242. package/src/execution/finalized-marker.ts +0 -51
  243. package/src/execution/get-state-handshake.ts +0 -104
  244. package/src/execution/host-mode.ts +0 -56
  245. package/src/execution/idle-gc.ts +0 -47
  246. package/src/execution/lifecycle-manager.ts +0 -513
  247. package/src/execution/lifecycle-predicates.ts +0 -65
  248. package/src/execution/manifest-store.ts +0 -256
  249. package/src/execution/model-config-service.ts +0 -219
  250. package/src/execution/model-resolver.ts +0 -248
  251. package/src/execution/notifier.ts +0 -330
  252. package/src/execution/notify-ledger.ts +0 -580
  253. package/src/execution/output-collector.ts +0 -228
  254. package/src/execution/path-encoding.ts +0 -60
  255. package/src/execution/pi-invocation.ts +0 -120
  256. package/src/execution/record-entry.ts +0 -132
  257. package/src/execution/record-store.ts +0 -1234
  258. package/src/execution/relay-env.ts +0 -37
  259. package/src/execution/session-context-resolver.ts +0 -64
  260. package/src/execution/session-file-gc.ts +0 -120
  261. package/src/execution/session-pending.ts +0 -164
  262. package/src/execution/session-reconstructor.ts +0 -678
  263. package/src/execution/session-runner.ts +0 -1781
  264. package/src/execution/sessions-index.ts +0 -304
  265. package/src/execution/spawn-event-adapter.ts +0 -363
  266. package/src/execution/stdin-writer.ts +0 -198
  267. package/src/execution/stream-sink.ts +0 -126
  268. package/src/execution/subagent-service.ts +0 -2141
  269. package/src/execution/subprocess-agent-runner.ts +0 -317
  270. package/src/execution/temp-prompt.ts +0 -62
  271. package/src/execution/tombstone-store.ts +0 -72
  272. package/src/execution/turn-limiter.ts +0 -102
  273. package/src/execution/types.ts +0 -973
  274. package/src/execution/ui-channels.ts +0 -216
  275. package/src/execution/ui-interaction-model.ts +0 -48
  276. package/src/execution/ui-request-handler-factory.ts +0 -212
  277. package/src/execution/ui-request-observability.ts +0 -81
  278. package/src/execution/ui-request-queue.ts +0 -184
  279. package/src/execution/worktree-manager.ts +0 -688
  280. package/src/execution/worktree-registry.ts +0 -222
  281. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -349
  282. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -202
  283. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -152
  284. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  285. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  286. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -809
  287. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  288. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  289. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  290. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -451
  291. package/src/orchestration/__tests__/jsonl-run-store-corrupt-entry.test.ts +0 -150
  292. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  293. package/src/orchestration/__tests__/jsonl-run-store-retention.test.ts +0 -202
  294. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  295. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -600
  296. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  297. package/src/orchestration/__tests__/lifecycle.test.ts +0 -659
  298. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +0 -95
  299. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -2025
  300. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  301. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -201
  302. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  303. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +0 -368
  304. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  305. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  306. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -563
  307. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  308. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -129
  309. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  310. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  311. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  312. package/src/orchestration/agent-opts-resolver.ts +0 -174
  313. package/src/orchestration/args-validator.ts +0 -127
  314. package/src/orchestration/config-loader.ts +0 -315
  315. package/src/orchestration/error-recovery.ts +0 -1018
  316. package/src/orchestration/execute-agent-call.ts +0 -256
  317. package/src/orchestration/launcher.ts +0 -455
  318. package/src/orchestration/lifecycle.ts +0 -399
  319. package/src/orchestration/models/__tests__/budget.test.ts +0 -307
  320. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  321. package/src/orchestration/models/agent-call.ts +0 -83
  322. package/src/orchestration/models/budget.ts +0 -118
  323. package/src/orchestration/models/ports.ts +0 -164
  324. package/src/orchestration/models/run-runtime.ts +0 -104
  325. package/src/orchestration/models/run-spec.ts +0 -83
  326. package/src/orchestration/models/run-state.ts +0 -44
  327. package/src/orchestration/models/trace.ts +0 -183
  328. package/src/orchestration/models/types.ts +0 -301
  329. package/src/orchestration/models/workflow-run.ts +0 -254
  330. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  331. package/src/orchestration/models/workflow-script.ts +0 -117
  332. package/src/orchestration/script-lint.ts +0 -766
  333. package/src/orchestration/skill-discovery.ts +0 -115
  334. package/src/orchestration/worker-handle.ts +0 -115
  335. package/src/orchestration/worker-host.ts +0 -98
  336. package/src/orchestration/worker-script-builder.ts +0 -421
  337. package/src/orchestration/workflow-files.ts +0 -85
  338. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  339. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  340. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  341. package/src/shared/__tests__/model-ref.test.ts +0 -306
  342. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  343. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  344. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  345. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  346. package/src/shared/__tests__/timer-delay.test.ts +0 -61
  347. package/src/shared/agent-event.ts +0 -13
  348. package/src/shared/agent-ref.ts +0 -57
  349. package/src/shared/meta-parser.ts +0 -261
  350. package/src/shared/model-ref.ts +0 -286
  351. package/src/shared/resource-discovery.ts +0 -811
  352. package/src/shared/resource-meta.ts +0 -65
  353. package/src/shared/schema-env.ts +0 -44
  354. package/src/shared/schema-jsonify.ts +0 -58
  355. package/src/shared/timer-delay.ts +0 -54
  356. package/src/shared/xml-injection.ts +0 -35
  357. package/workflows/README.md +0 -81
  358. package/workflows/_shared/agent-refs.cjs +0 -40
  359. package/workflows/chain.js +0 -131
  360. package/workflows/map-reduce.js +0 -174
  361. package/workflows/parallel.js +0 -148
  362. package/workflows/review-fix-loop-utils.cjs +0 -1309
  363. package/workflows/review-fix-loop.js +0 -1404
  364. package/workflows/scatter-gather.js +0 -178
@@ -0,0 +1,292 @@
1
+ // engine-awareness 单测([engine-awareness U3])
2
+ //
3
+ // 覆盖(设计 docs/design/subagent-engine-awareness-injection.md 验收挂钩 D1/D1b/D2/D3/D5):
4
+ // 1. normalizeEngineId:缺省/空白归一到 'pi'(单一权威源 registry.ts,直连导入)
5
+ // 2. buildEngineChangeNotice:§3.1 文案骨架、pi/非 pi 指路段分界、不含任何模型清单(D4)
6
+ // 3. runEngineAwarenessTurn 编排:
7
+ // - 变更触发 apply + 通知(D2 顺序硬约束:提交缓存先于通知先于记账)
8
+ // - applyRead 收到的参数与 readConfig 返回值引用相等(构造性同源,消灭双读分叉)
9
+ // - 无变更无事
10
+ // - 读失败保持 lastEngine 不动、不发通知(D5 态 3,防 torn write 伪通知)
11
+ // - ENOENT(absent)= 合法缺省 pi,正常触发变更(D5 态 2)
12
+ // - 首 turn lastEngine === undefined 静默基线化,无伪通知(D1b)
13
+ // - 通知消息形态(customType / display / details,D3+D8 对齐 notifier 约定)
14
+ // - 多 session 各自独立 lastEngine(per-session diff 基准互不干扰)
15
+
16
+ import { describe, expect, it } from "vitest";
17
+
18
+ import {
19
+ buildEngineChangeNotice,
20
+ ENGINE_CHANGE_CUSTOM_TYPE,
21
+ runEngineAwarenessTurn,
22
+ type EngineAwarenessDeps,
23
+ type EngineAwarenessOutcome,
24
+ } from "../engine-awareness";
25
+ import { normalizeEngineId } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
26
+ import type { GlobalConfigReadResult } from "@zhushanwen/subagent-core/execution/config.ts";
27
+
28
+ // ── 测试数据 ────────────────────────────────────────────
29
+
30
+ /** ok 态读取结果工厂(config 形状与 sanitize 输出一致)。 */
31
+ function okRead(defaultEngine: string | undefined): GlobalConfigReadResult {
32
+ return {
33
+ status: "ok",
34
+ config: { version: 1, maxConcurrent: 6, ...(defaultEngine !== undefined ? { defaultEngine } : {}) },
35
+ };
36
+ }
37
+
38
+ /** 调用序记录 + 发送捕获的 deps 工厂。 */
39
+ function makeDeps(overrides: Partial<EngineAwarenessDeps> = {}) {
40
+ const calls: string[] = [];
41
+ const sent: Array<{ customType: string; content: string; display: boolean; details?: unknown }> = [];
42
+ const applied: GlobalConfigReadResult[] = [];
43
+ let lastEngine: string | undefined;
44
+ const deps: EngineAwarenessDeps = {
45
+ readConfig: () => okRead(undefined),
46
+ applyRead: (read) => {
47
+ calls.push("apply");
48
+ applied.push(read);
49
+ },
50
+ sendMessage: (message) => {
51
+ calls.push("send");
52
+ sent.push(message);
53
+ },
54
+ getLastEngine: () => lastEngine,
55
+ setLastEngine: (engine) => {
56
+ calls.push(`set:${engine}`);
57
+ lastEngine = engine;
58
+ },
59
+ ...overrides,
60
+ };
61
+ return { deps, calls, sent, applied, getLast: () => lastEngine };
62
+ }
63
+
64
+ // ── normalizeEngineId ──────────────────────────────────
65
+
66
+ describe("normalizeEngineId", () => {
67
+ it("undefined 缺省归一到 'pi'", () => {
68
+ expect(normalizeEngineId(undefined)).toBe("pi");
69
+ });
70
+
71
+ it("空白字符串归一到 'pi'(sanitize 会拦非字符串,但防御空白透传)", () => {
72
+ expect(normalizeEngineId(" ")).toBe("pi");
73
+ });
74
+
75
+ it("非 pi 引擎透传", () => {
76
+ expect(normalizeEngineId("zcode")).toBe("zcode");
77
+ });
78
+ });
79
+
80
+ // ── buildEngineChangeNotice(D4 不含模型清单)────────────
81
+
82
+ describe("buildEngineChangeNotice", () => {
83
+ it("pi 目标:§3.1 文案骨架(zcode → pi),指向 <available_provider_models>", () => {
84
+ const content = buildEngineChangeNotice("zcode", "pi");
85
+ expect(content).toBe(
86
+ [
87
+ "Subagent default engine changed: zcode → pi (effective this turn).",
88
+ "Use pi-registry ids from <available_provider_models> for explicit models;",
89
+ "omit `model` to inherit. The <current_subagent_engine> section reflects the current state.",
90
+ ].join("\n"),
91
+ );
92
+ });
93
+
94
+ it("非 pi 目标:指向该引擎清单段 <available_<engine>_models>", () => {
95
+ const content = buildEngineChangeNotice("pi", "zcode");
96
+ expect(content).toContain("Subagent default engine changed: pi → zcode (effective this turn).");
97
+ expect(content).toContain("<available_zcode_models>");
98
+ expect(content).not.toContain("<available_provider_models>");
99
+ });
100
+
101
+ it("不含任何模型清单(无 <model> 标签 / 无 provider id 形态,D4)", () => {
102
+ for (const to of ["pi", "zcode"]) {
103
+ const content = buildEngineChangeNotice("zcode", to);
104
+ expect(content).not.toContain("<model>");
105
+ // provider id 形态:zai-coding-cn/glm-5.3、builtin:bigmodel-coding-plan/... 等
106
+ expect(content).not.toMatch(/[a-z][\w-]*\/[\w.-]+/i);
107
+ expect(content).not.toContain("builtin:");
108
+ }
109
+ });
110
+ });
111
+
112
+ // ── runEngineAwarenessTurn:变更编排 ─────────────────────
113
+
114
+ describe("runEngineAwarenessTurn", () => {
115
+ it("变更触发 apply + 通知 + 记账,顺序硬约束 apply → send → set(D2)", () => {
116
+ const read = okRead("pi");
117
+ const { deps, calls, sent, applied } = makeDeps({
118
+ readConfig: () => read,
119
+ getLastEngine: () => "zcode",
120
+ });
121
+ const result = runEngineAwarenessTurn(deps);
122
+ expect(result).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
123
+ expect(calls).toEqual(["apply", "send", "set:pi"]);
124
+ // 构造性同源:提交到路由缓存的就是本次读取返回的同一对象(非重读、非拷贝)
125
+ expect(applied[0]).toBe(read);
126
+ expect(sent).toHaveLength(1);
127
+ expect(sent[0].customType).toBe(ENGINE_CHANGE_CUSTOM_TYPE);
128
+ expect(sent[0].content).toContain("zcode → pi");
129
+ });
130
+
131
+ it("通知消息形态对齐 notifier 约定(display:true + details 携带 from/to,D3/D8)", () => {
132
+ const { deps, sent } = makeDeps({
133
+ readConfig: () => okRead("zcode"),
134
+ getLastEngine: () => "pi",
135
+ });
136
+ runEngineAwarenessTurn(deps);
137
+ expect(sent[0].display).toBe(true);
138
+ expect(sent[0].details).toEqual({ from: "pi", to: "zcode" });
139
+ });
140
+
141
+ it("无变更无事:零 apply 零通知零写入", () => {
142
+ const { deps, calls, sent } = makeDeps({
143
+ readConfig: () => okRead("zcode"),
144
+ getLastEngine: () => "zcode",
145
+ });
146
+ const result: EngineAwarenessOutcome = runEngineAwarenessTurn(deps);
147
+ expect(result).toEqual({ outcome: "unchanged", engine: "zcode" });
148
+ expect(calls).toEqual([]);
149
+ expect(sent).toHaveLength(0);
150
+ });
151
+
152
+ it("读失败保持 lastEngine 不动、不 apply 不通知(D5 态 3)", () => {
153
+ let lastEngine: string | undefined = "zcode";
154
+ const { deps, calls, sent } = makeDeps({
155
+ readConfig: () => ({ status: "failed", reason: "Unexpected token in JSON" }),
156
+ getLastEngine: () => lastEngine,
157
+ setLastEngine: (engine) => {
158
+ lastEngine = engine;
159
+ },
160
+ });
161
+ const result = runEngineAwarenessTurn(deps);
162
+ expect(result).toEqual({ outcome: "read-failed", reason: "Unexpected token in JSON" });
163
+ expect(calls).toEqual([]);
164
+ expect(sent).toHaveLength(0);
165
+ expect(lastEngine).toBe("zcode");
166
+ });
167
+
168
+ it("ENOENT(absent)= 合法缺省 pi:正常触发变更(D5 态 2)", () => {
169
+ const read: GlobalConfigReadResult = { status: "absent", config: { version: 1, maxConcurrent: 6 } };
170
+ const { deps, calls, sent, applied } = makeDeps({
171
+ readConfig: () => read,
172
+ getLastEngine: () => "zcode",
173
+ });
174
+ const result = runEngineAwarenessTurn(deps);
175
+ expect(result).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
176
+ expect(calls).toEqual(["apply", "send", "set:pi"]);
177
+ // absent 态同样构造性同源(删配置切回缺省也走同一次读取)
178
+ expect(applied[0]).toBe(read);
179
+ expect(sent[0].content).toContain("zcode → pi");
180
+ });
181
+
182
+ it("缺省 config(defaultEngine 字段缺失)diff 基准归一为 pi:zcode → 无字段 视为变更到 pi", () => {
183
+ const { deps, sent } = makeDeps({
184
+ readConfig: () => okRead(undefined),
185
+ getLastEngine: () => "zcode",
186
+ });
187
+ const result = runEngineAwarenessTurn(deps);
188
+ expect(result).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
189
+ expect(sent[0].content).toContain("zcode → pi");
190
+ });
191
+ });
192
+
193
+ // ── runEngineAwarenessTurn:D1b 基线化 ───────────────────
194
+
195
+ describe("runEngineAwarenessTurn lastEngine 基线化(D1b)", () => {
196
+ it("首 turn lastEngine === undefined:静默基线化为当前值,无伪通知,但 apply 先行(D1b 修订)", () => {
197
+ const read = okRead("zcode");
198
+ const { deps, calls, sent, applied, getLast } = makeDeps({
199
+ readConfig: () => read,
200
+ // session_start 初始化读失败 → lastEngine 未设置(undefined)
201
+ });
202
+ const result = runEngineAwarenessTurn(deps);
203
+ expect(result).toEqual({ outcome: "baseline", engine: "zcode" });
204
+ // D1b 修订:基线分支必须先提交读取结果把 Service 缓存对齐到刚读到的现值——
205
+ // 否则「session_start 读失败缓存回落 + 文件此后被修好」形态下,
206
+ // 缓存/路由/状态段永停旧值且永不通知
207
+ expect(calls).toEqual(["apply", "set:zcode"]);
208
+ // 构造性同源:基线分支提交的也是本次读取的同一对象
209
+ expect(applied[0]).toBe(read);
210
+ expect(sent).toHaveLength(0);
211
+ expect(getLast()).toBe("zcode");
212
+ });
213
+
214
+ it("基线化后再 turn 读到同值 → unchanged(基线生效,且 unchanged 仍零调用)", () => {
215
+ let lastEngine: string | undefined;
216
+ const { deps, calls, sent } = makeDeps({
217
+ readConfig: () => okRead("zcode"),
218
+ getLastEngine: () => lastEngine,
219
+ setLastEngine: (engine) => {
220
+ lastEngine = engine;
221
+ },
222
+ });
223
+ expect(runEngineAwarenessTurn(deps)).toEqual({ outcome: "baseline", engine: "zcode" });
224
+ expect(runEngineAwarenessTurn(deps)).toEqual({ outcome: "unchanged", engine: "zcode" });
225
+ // setLastEngine 被本用例 override(写局部变量、不记录 calls),故 calls 仅剩
226
+ // baseline 分支的 apply;unchanged 分支零调用
227
+ expect(calls).toEqual(["apply"]);
228
+ expect(sent).toHaveLength(0);
229
+ });
230
+
231
+ it("lastEngine === undefined 且读失败:连基线都不做,不写入不通知", () => {
232
+ const { deps, calls, sent, getLast } = makeDeps({
233
+ readConfig: () => ({ status: "failed", reason: "EACCES" }),
234
+ });
235
+ const result = runEngineAwarenessTurn(deps);
236
+ expect(result).toEqual({ outcome: "read-failed", reason: "EACCES" });
237
+ expect(calls).toEqual([]);
238
+ expect(sent).toHaveLength(0);
239
+ expect(getLast()).toBeUndefined();
240
+ });
241
+ });
242
+
243
+ // ── 多 session 独立性 ──────────────────────────────────
244
+
245
+ describe("runEngineAwarenessTurn 多 session 独立 lastEngine", () => {
246
+ /**
247
+ * 模拟 index.ts 装配形态:共享 per-session 状态 Map(sessionState),
248
+ * 每个 sid 一组 deps(getLastEngine/setLastEngine 绑定各自 sid)。
249
+ */
250
+ function makeMultiSessionState() {
251
+ const lastEngines = new Map<string, string | undefined>();
252
+ const notices: Array<{ sid: string; content: string }> = [];
253
+ function depsFor(sid: string, read: () => GlobalConfigReadResult): EngineAwarenessDeps {
254
+ return {
255
+ readConfig: read,
256
+ applyRead: () => {},
257
+ sendMessage: (message) => {
258
+ notices.push({ sid, content: message.content });
259
+ },
260
+ getLastEngine: () => lastEngines.get(sid),
261
+ setLastEngine: (engine) => {
262
+ lastEngines.set(sid, engine);
263
+ },
264
+ };
265
+ }
266
+ return { lastEngines, notices, depsFor };
267
+ }
268
+
269
+ it("session1 检测变更不污染 session2 的基线(各自独立边沿)", () => {
270
+ const { lastEngines, notices, depsFor } = makeMultiSessionState();
271
+ const readZcode = (): GlobalConfigReadResult => okRead("zcode");
272
+ const readPi = (): GlobalConfigReadResult => okRead("pi");
273
+
274
+ // 各自首 turn 基线化
275
+ const sid1 = depsFor("s1", readZcode);
276
+ const sid2 = depsFor("s2", readPi);
277
+ expect(runEngineAwarenessTurn(sid1)).toEqual({ outcome: "baseline", engine: "zcode" });
278
+ expect(runEngineAwarenessTurn(sid2)).toEqual({ outcome: "baseline", engine: "pi" });
279
+
280
+ // session1 的引擎切换(zcode → pi):session1 收通知,session2 无感知
281
+ const sid1After = depsFor("s1", readPi);
282
+ expect(runEngineAwarenessTurn(sid1After)).toEqual({ outcome: "changed", from: "zcode", to: "pi" });
283
+ // session2 再 turn:仍 unchanged,零通知
284
+ const sid2After = depsFor("s2", readPi);
285
+ expect(runEngineAwarenessTurn(sid2After)).toEqual({ outcome: "unchanged", engine: "pi" });
286
+
287
+ expect(notices).toHaveLength(1);
288
+ expect(notices[0].sid).toBe("s1");
289
+ expect(lastEngines.get("s1")).toBe("pi");
290
+ expect(lastEngines.get("s2")).toBe("pi");
291
+ });
292
+ });
@@ -0,0 +1,343 @@
1
+ // engine-section-stability.test.ts —— [engine-awareness U4 stability-guard]
2
+ //
3
+ // 字节稳定守护(设计 docs/design/subagent-engine-awareness-injection.md §3.3 D7 /
4
+ // §4 A8 的代码级前置):engine 段延续注入纪律——确定性渲染、链尾位置,引擎切换只断
5
+ // system prompt 尾部 cache 前缀(与 provider models 段变更同判,不新增 cache 破坏面,
6
+ // cache-probe 前缀指纹归因兼容)。
7
+ //
8
+ // 三层覆盖:
9
+ // 1. 段渲染确定性:pi / zcode / ghost 三形态多次独立调用逐字节相等(toBe 级);
10
+ // 2. 引擎切换只变尾部:provider models 段前缀不变,变化只发生在 engine 段位置(尾部);
11
+ // 3. 段序守护:engine 段恒位于 provider models 段之后(恒链尾)。双保险实现:
12
+ // a) handler 链模拟——真实导入 setupModelListInjector + 复刻 index.ts engine
13
+ // handler 的渲染拼装(检测编排部分归 engine-awareness.test.ts),模拟 pi 的
14
+ // before_agent_start 链式叠加语义;
15
+ // b) index.ts 源码级结构断言——锚定注册序与拼装形态。
16
+ //
17
+ // 源码级断言的脆弱性权衡(刻意为之):engine handler 是 index.ts 内联闭包(无法
18
+ // 导入),而 before_agent_start 多 handler 的段序由注册序唯一决定,「注册序」这一
19
+ // 事实只存在于 index.ts 源码中——源码结构断言是唯一能锚定它的方式。重构挪动注册
20
+ // 位置导致本断言失败属预期行为(提醒同步更新链模拟与守护锚点),不是误报。
21
+
22
+ import { readFileSync } from "node:fs";
23
+ import { fileURLToPath } from "node:url";
24
+
25
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
26
+
27
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
28
+
29
+ // C5①:formatModelList/ModelEntry 下沉 core barrel(setupModelListInjector 留 injector)
30
+ import { formatModelList, type ModelEntry } from "@zhushanwen/subagent-core";
31
+ import { MODEL_LIST_GUIDE, setupModelListInjector } from "../model-list-injector.ts";
32
+ import type { EnginePort } from "@zhushanwen/subagent-core/execution/engine/port.ts";
33
+ import { clearEngines, registerEngine } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
34
+ import {
35
+ buildEngineModelsPromptAppend,
36
+ buildSubagentEngineSection,
37
+ } from "@zhushanwen/subagent-core/execution/engine/model-prompt.ts";
38
+
39
+ // ── 测试数据 ────────────────────────────────────────────
40
+
41
+ /** pi registry 风格条目(provider models 段数据源;同时充当 ctx.modelRegistry 快照)。 */
42
+ const PROVIDER_ENTRIES: ModelEntry[] = [
43
+ {
44
+ provider: "zai-coding-cn",
45
+ id: "glm-5.3",
46
+ name: "GLM 5.3",
47
+ reasoning: true,
48
+ input: ["text"],
49
+ contextWindow: 200_000,
50
+ },
51
+ {
52
+ provider: "minimax-cn",
53
+ id: "MiniMax-M3",
54
+ name: "MiniMax M3",
55
+ reasoning: true,
56
+ input: ["text", "image"],
57
+ contextWindow: 1_000_000,
58
+ },
59
+ ];
60
+
61
+ /** zcode 引擎清单(v2 registry 风格 id)。 */
62
+ const ZCODE_MODELS: Array<{ id: string; name?: string }> = [
63
+ { id: "builtin:bigmodel-coding-plan/GLM-5.3", name: "GLM-5.3" },
64
+ { id: "builtin:bigmodel-coding-plan/GLM-5.3-Flash" },
65
+ ];
66
+
67
+ /** 模拟 pi 核心 system prompt(engine 追加段之外的全部内容)。 */
68
+ const BASE = "You are a coding agent.\n\n# System\nCore prompt body.";
69
+
70
+ /**
71
+ * engine handler 的追加段拼装(与 index.ts engine handler 同款:状态段 + 清单段、
72
+ * 空段剔除、\n\n 连接)。复刻保真由下方「段序守护」describe 的源码锚点断言保证。
73
+ */
74
+ function composeEngineAppend(defaultEngine: string | undefined): string {
75
+ return [buildSubagentEngineSection(defaultEngine), buildEngineModelsPromptAppend(defaultEngine)]
76
+ .filter((part) => part !== "")
77
+ .join("\n\n");
78
+ }
79
+
80
+ /** index.ts 尾部追加模板的分隔符(`${event.systemPrompt}\n\n${append}`)。 */
81
+ const APPEND_SEPARATOR = "\n\n";
82
+
83
+ /** 合成一个 turn 的 system prompt 尾部:BASE + provider models 段 + engine 追加段。 */
84
+ function composeTurn(defaultEngine: string | undefined): string {
85
+ const providerSection = formatModelList(PROVIDER_ENTRIES, { guide: MODEL_LIST_GUIDE });
86
+ const afterProvider = providerSection === "" ? BASE : BASE + providerSection;
87
+ const append = composeEngineAppend(defaultEngine);
88
+ return append === "" ? afterProvider : `${afterProvider}${APPEND_SEPARATOR}${append}`;
89
+ }
90
+
91
+ /** 最大公共前缀长度(逐码元比较;用于「分叉点不早于稳定头部」断言)。 */
92
+ function commonPrefixLength(a: string, b: string): number {
93
+ const n = Math.min(a.length, b.length);
94
+ let i = 0;
95
+ while (i < n && a[i] === b[i]) i++;
96
+ return i;
97
+ }
98
+
99
+ /** 最小 fake 引擎(照 model-prompt.test.ts 同款形态):仅 listModels 参与注入链。 */
100
+ function fakeEngine(id: string, models: Array<{ id: string; name?: string }>): EnginePort {
101
+ return {
102
+ id,
103
+ capabilities: () => ({ conversation: "unsupported", steer: "unsupported", sandbox: "none" }),
104
+ probe: async () => ({ ok: true, engineVersion: "test" }),
105
+ run: async () => {
106
+ throw new Error("not used in this test");
107
+ },
108
+ interact: async () => {
109
+ throw new Error("not used in this test");
110
+ },
111
+ read: async () => ({ engineId: id, turns: [], source: "outcome-only" }),
112
+ // 每次调用返回新数组实例(模拟 registry 每 turn 现值)——渲染必须与实例无关
113
+ listModels: () => models.map((m) => ({ ...m })),
114
+ };
115
+ }
116
+
117
+ beforeEach(() => {
118
+ clearEngines();
119
+ registerEngine("zcode", () => fakeEngine("zcode", ZCODE_MODELS));
120
+ });
121
+
122
+ afterEach(() => {
123
+ clearEngines();
124
+ });
125
+
126
+ // ── 1. 段渲染确定性(D7)────────────────────────────────
127
+
128
+ describe("段渲染确定性(D7:同输入恒同输出)", () => {
129
+ it("pi 形态:状态段多次独立调用逐字节相等;清单段恒空串", () => {
130
+ // pi 形态不依赖注册表(直接走缺省分支),registry 中有 zcode 不影响
131
+ const first = buildSubagentEngineSection("pi");
132
+ const second = buildSubagentEngineSection("pi");
133
+ const third = buildSubagentEngineSection("pi");
134
+ expect(first).toBe(second);
135
+ expect(second).toBe(third);
136
+ // 非空自检:确认 toBe 断言真的在比对段内容而非空串
137
+ expect(first).toContain("<current_subagent_engine>");
138
+ expect(buildEngineModelsPromptAppend("pi")).toBe("");
139
+ });
140
+
141
+ it("zcode 形态:状态段 + 清单段多次独立调用逐字节相等(listModels 每次返回新数组实例)", () => {
142
+ const s1 = buildSubagentEngineSection("zcode");
143
+ const s2 = buildSubagentEngineSection("zcode");
144
+ const m1 = buildEngineModelsPromptAppend("zcode");
145
+ const m2 = buildEngineModelsPromptAppend("zcode");
146
+ expect(s1).toBe(s2);
147
+ expect(m1).toBe(m2);
148
+ expect(m1).toContain("<available_zcode_models>");
149
+ expect(m1).toContain("<id>builtin:bigmodel-coding-plan/GLM-5.3</id>");
150
+ });
151
+
152
+ it("ghost 形态(未注册引擎):警告段多次独立调用逐字节相等", () => {
153
+ const first = buildSubagentEngineSection("ghost");
154
+ const second = buildSubagentEngineSection("ghost");
155
+ expect(first).toBe(second);
156
+ expect(first).toContain("engine 'ghost' is not registered");
157
+ expect(buildEngineModelsPromptAppend("ghost")).toBe("");
158
+ });
159
+
160
+ it("形态间干扰不产生状态残留:zcode → pi → zcode,两次 zcode 输出逐字节相等", () => {
161
+ const before = buildSubagentEngineSection("zcode");
162
+ // 穿插渲染其他形态(含清单段消费 listModels),再回到 zcode
163
+ buildSubagentEngineSection("pi");
164
+ buildEngineModelsPromptAppend("zcode");
165
+ buildSubagentEngineSection("ghost");
166
+ expect(buildSubagentEngineSection("zcode")).toBe(before);
167
+ });
168
+
169
+ it("注册表重建(clearEngines + 同 id 同清单重注册)后同形态输出不变", () => {
170
+ const firstSection = buildSubagentEngineSection("zcode");
171
+ const firstAppend = buildEngineModelsPromptAppend("zcode");
172
+ clearEngines();
173
+ registerEngine("zcode", () => fakeEngine("zcode", ZCODE_MODELS));
174
+ expect(buildSubagentEngineSection("zcode")).toBe(firstSection);
175
+ expect(buildEngineModelsPromptAppend("zcode")).toBe(firstAppend);
176
+ });
177
+
178
+ it("组合拼装(index.ts 同款:状态段 + 清单段 join)同引擎多次调用逐字节相等", () => {
179
+ for (const engine of ["zcode", "pi", "ghost"]) {
180
+ expect(composeEngineAppend(engine)).toBe(composeEngineAppend(engine));
181
+ }
182
+ });
183
+ });
184
+
185
+ // ── 2. 引擎切换只变尾部(A8 前置)───────────────────────
186
+
187
+ /**
188
+ * 断言「从 from 切到 to 时变化只发生在尾部 engine 段」:
189
+ * - provider models 段及其之前的头部逐字节不变(公共前缀 toBe 级断言);
190
+ * - 剥离尾部 engine 追加段后两串余部逐字节相等且恰为稳定头部;
191
+ * - 分叉点不早于稳定头部末尾(差异绝不侵入 provider models 段);
192
+ * - 切换后的引擎追加段恰居 prompt 尾部。
193
+ */
194
+ function expectOnlyTailDiffers(from: string | undefined, to: string | undefined): void {
195
+ const stableHead = BASE + formatModelList(PROVIDER_ENTRIES, { guide: MODEL_LIST_GUIDE });
196
+ const before = composeTurn(from);
197
+ const after = composeTurn(to);
198
+ const beforeAppend = composeEngineAppend(from);
199
+ const afterAppend = composeEngineAppend(to);
200
+
201
+ expect(before).not.toBe(after);
202
+ expect(before.startsWith(stableHead)).toBe(true);
203
+ expect(after.startsWith(stableHead)).toBe(true);
204
+ expect(before.slice(0, before.length - beforeAppend.length - APPEND_SEPARATOR.length)).toBe(
205
+ stableHead,
206
+ );
207
+ expect(after.slice(0, after.length - afterAppend.length - APPEND_SEPARATOR.length)).toBe(
208
+ stableHead,
209
+ );
210
+ expect(commonPrefixLength(before, after)).toBeGreaterThanOrEqual(stableHead.length);
211
+ expect(after.endsWith(afterAppend)).toBe(true);
212
+ }
213
+
214
+ describe("引擎切换只变尾部(A8 前置:变化只断尾部 cache 前缀)", () => {
215
+ it("zcode → pi:provider models 段前缀不变,变化只发生在 engine 段(尾部)", () => {
216
+ expectOnlyTailDiffers("zcode", "pi");
217
+ });
218
+
219
+ it("pi → zcode(反向切换):同款只变尾部", () => {
220
+ expectOnlyTailDiffers("pi", "zcode");
221
+ });
222
+
223
+ it("zcode → ghost(配置手误形态):G4 降级段同样只替换尾部,不破坏前缀", () => {
224
+ expectOnlyTailDiffers("zcode", "ghost");
225
+ });
226
+ });
227
+
228
+ // ── 3. 段序守护:engine 段恒链尾 ────────────────────────
229
+
230
+ /** 链模拟用 handler 形态(参数收窄到运行时实际消费的字段)。 */
231
+ type ChainHandler = (event: { systemPrompt: string }, ctx: unknown) => unknown;
232
+
233
+ /** fake ExtensionAPI:捕获 pi.on 注册的 handler(保持注册序)。 */
234
+ function capturePi(): { handlers: ChainHandler[]; api: ExtensionAPI } {
235
+ const handlers: ChainHandler[] = [];
236
+ const api = {
237
+ on: (_event: string, handler: (...args: unknown[]) => unknown) => {
238
+ // on 的 mock 签名参数为 unknown[],收窄到两参形态(pi 调用约定恒两参)
239
+ handlers.push(((event: unknown, ctx: unknown) => handler(event, ctx)) as ChainHandler);
240
+ },
241
+ } as unknown as ExtensionAPI;
242
+ return { handlers, api };
243
+ }
244
+
245
+ /** 模拟 pi 的 before_agent_start 串联语义:按注册序执行,前序返回的 systemPrompt 作为后序输入。 */
246
+ async function runHandlerChain(handlers: ChainHandler[], initialPrompt: string): Promise<string> {
247
+ let prompt = initialPrompt;
248
+ const ctx = { modelRegistry: { getAvailable: () => PROVIDER_ENTRIES } };
249
+ for (const handler of handlers) {
250
+ const result: unknown = await handler({ systemPrompt: prompt }, ctx);
251
+ // 运行时收窄:handler 返回 { systemPrompt: string } 时替换链上 prompt,否则保持
252
+ if (typeof result === "object" && result !== null && "systemPrompt" in result) {
253
+ const sp: unknown = (result as { systemPrompt: unknown }).systemPrompt;
254
+ if (typeof sp === "string") prompt = sp;
255
+ }
256
+ }
257
+ return prompt;
258
+ }
259
+
260
+ describe("段序守护:engine 段恒链尾(D7)", () => {
261
+ it("handler 链模拟:按 index.ts 注册序执行后,engine 段位于 provider models 段之后且居尾", async () => {
262
+ const { handlers, api } = capturePi();
263
+ // 真实导入 model list injector(provider models 段的生产渲染路径)
264
+ setupModelListInjector(api);
265
+ // 复刻 index.ts engine handler 的渲染拼装部分(检测编排部分归 engine-awareness.test.ts)
266
+ const defaultEngine = "zcode";
267
+ handlers.push((event) => {
268
+ const append = composeEngineAppend(defaultEngine);
269
+ return { systemPrompt: `${event.systemPrompt}${APPEND_SEPARATOR}${append}` };
270
+ });
271
+
272
+ const prompt = await runHandlerChain(handlers, BASE);
273
+
274
+ const providerIdx = prompt.indexOf("<available_provider_models>");
275
+ const statusIdx = prompt.indexOf("<current_subagent_engine>");
276
+ const zcodeListIdx = prompt.indexOf("<available_zcode_models>");
277
+ expect(providerIdx).toBeGreaterThanOrEqual(0);
278
+ // 段序硬约束:engine 状态段与引擎清单段都在 provider models 段之后
279
+ expect(statusIdx).toBeGreaterThan(providerIdx);
280
+ expect(zcodeListIdx).toBeGreaterThan(providerIdx);
281
+ // 恒链尾:engine 追加段是 prompt 的最后内容
282
+ expect(prompt.endsWith(`${APPEND_SEPARATOR}${composeEngineAppend(defaultEngine)}`)).toBe(true);
283
+ // model list handler 注入形态锚定(BASE + injection 无分隔,生产 handler 同款)
284
+ expect(prompt.startsWith(BASE + formatModelList(PROVIDER_ENTRIES, { guide: MODEL_LIST_GUIDE }))).toBe(true);
285
+ });
286
+
287
+ it("对照:注册序颠倒(engine 先、model list 后)时段序断言必失败——守护有判别力", async () => {
288
+ const { handlers, api } = capturePi();
289
+ const defaultEngine = "zcode";
290
+ handlers.push((event) => {
291
+ const append = composeEngineAppend(defaultEngine);
292
+ return { systemPrompt: `${event.systemPrompt}${APPEND_SEPARATOR}${append}` };
293
+ });
294
+ setupModelListInjector(api);
295
+
296
+ const prompt = await runHandlerChain(handlers, BASE);
297
+
298
+ // 颠倒后 engine 段跑到 provider models 段之前——证明上一条 it 的断言非平凡
299
+ const providerIdx = prompt.indexOf("<available_provider_models>");
300
+ const statusIdx = prompt.indexOf("<current_subagent_engine>");
301
+ expect(statusIdx).toBeGreaterThanOrEqual(0);
302
+ expect(statusIdx).toBeLessThan(providerIdx);
303
+ });
304
+
305
+ it("index.ts 源码级守护:engine handler 注册序在 model list injector 之后(段序由注册序唯一决定)", () => {
306
+ const src = readFileSync(fileURLToPath(new URL("../../index.ts", import.meta.url)), "utf8");
307
+ const lineOf = (needle: string): number => {
308
+ const idx = src.indexOf(needle);
309
+ if (idx < 0) throw new Error(`index.ts 中未找到守护锚点:${needle}`);
310
+ return src.slice(0, idx).split("\n").length;
311
+ };
312
+
313
+ // 注册序:subagents → workflows → provider models → engine handler(链尾,D7)。
314
+ // engine handler 是唯一的直接 pi.on("before_agent_start")(其余三段经 setup* 间接注册),
315
+ // 恰出现一次的断言让「链尾」语义精确成立;未来若新增直接注册需人工复核段序。
316
+ expect(src.split('pi.on("before_agent_start"')).toHaveLength(2);
317
+ const lineSubagents = lineOf("setupSubagentListInjector(pi);");
318
+ const lineWorkflows = lineOf("setupWorkflowListInjector(pi);");
319
+ const lineModels = lineOf("setupModelListInjector(pi);");
320
+ const lineEngineHandler = lineOf('pi.on("before_agent_start"');
321
+ expect(lineSubagents).toBeLessThan(lineWorkflows);
322
+ expect(lineWorkflows).toBeLessThan(lineModels);
323
+ expect(lineModels).toBeLessThan(lineEngineHandler);
324
+
325
+ // engine handler 内渲染调用序:状态段在清单段之前(append 内段序:状态段 → 清单段)。
326
+ // 两个调用在 index.ts 中位于同一行(数组字面量),行号无法区分先后——用字符偏移断言
327
+ const offsetStatusCall = src.indexOf("buildSubagentEngineSection(defaultEngine)");
328
+ const offsetModelsCall = src.indexOf("buildEngineModelsPromptAppend(defaultEngine)");
329
+ if (offsetStatusCall < 0 || offsetModelsCall < 0) {
330
+ throw new Error("index.ts 中未找到 engine 段渲染调用锚点");
331
+ }
332
+ expect(offsetStatusCall).toBeGreaterThan(src.indexOf('pi.on("before_agent_start"'));
333
+ expect(offsetStatusCall).toBeLessThan(offsetModelsCall);
334
+
335
+ // 链模拟复刻保真锚点:append 拼装形态(空段剔除 + \n\n 连接 + 尾部追加模板)。
336
+ // 这些锚点保证 composeEngineAppend 与 index.ts 生产拼装的一致性——生产形态
337
+ // 变化会先在这里红,提醒同步更新本文件全部复刻点。
338
+ expect(src).toContain('[buildSubagentEngineSection(defaultEngine), buildEngineModelsPromptAppend(defaultEngine)]');
339
+ expect(src).toContain('.filter((part) => part !== "")');
340
+ expect(src).toContain('.join("\\n\\n")');
341
+ expect(src).toContain("${event.systemPrompt}\\n\\n${append}");
342
+ });
343
+ });