@zhushanwen/pi-subagent-workflow 8.5.0 → 8.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 (336) hide show
  1. package/package.json +13 -16
  2. package/relay/relay.mjs +1 -1
  3. package/src/host/__tests__/pi-host.test.ts +214 -0
  4. package/src/host/pi-host.ts +109 -0
  5. package/src/index.ts +142 -32
  6. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  7. package/src/injectors/__tests__/engine-section-stability.test.ts +341 -0
  8. package/src/injectors/__tests__/subagent-list-injector.test.ts +5 -5
  9. package/src/injectors/__tests__/workflow-list-injector.test.ts +5 -5
  10. package/src/injectors/engine-awareness.ts +123 -0
  11. package/src/injectors/model-list-injector.ts +1 -1
  12. package/src/injectors/subagent-list-injector.ts +8 -8
  13. package/src/injectors/workflow-list-injector.ts +8 -8
  14. package/src/interface/__tests__/detectors.test.ts +3 -3
  15. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +157 -0
  16. package/src/interface/__tests__/subagent-tool-prompt.test.ts +12 -0
  17. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +2 -2
  18. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
  19. package/src/interface/bg-notify-render.ts +34 -13
  20. package/src/interface/commands.ts +3 -3
  21. package/src/interface/format.ts +2 -2
  22. package/src/interface/helpers.ts +3 -3
  23. package/src/interface/list-component.ts +4 -4
  24. package/src/interface/list-shared.ts +2 -2
  25. package/src/interface/list-view.ts +2 -2
  26. package/src/interface/subagent-actions.ts +288 -19
  27. package/src/interface/subagent-tool-schema.ts +163 -0
  28. package/src/interface/subagent-tool.ts +65 -129
  29. package/src/interface/subagents.ts +5 -5
  30. package/src/interface/tool-render.ts +34 -2
  31. package/src/interface/tool-workflow-script.ts +4 -4
  32. package/src/interface/tool-workflow.ts +6 -6
  33. package/src/interface/views/WorkflowsView.ts +5 -5
  34. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  35. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  36. package/src/interface/views/detail-content.ts +4 -4
  37. package/src/interface/views/format.ts +4 -4
  38. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +132 -18
  39. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  40. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  41. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  42. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  43. package/src/execution/__tests__/alive-store.test.ts +0 -147
  44. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  45. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  46. package/src/execution/__tests__/bg-notify-render.test.ts +0 -256
  47. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  48. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -597
  49. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  50. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  51. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  52. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  53. package/src/execution/__tests__/config.test.ts +0 -110
  54. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  55. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  56. package/src/execution/__tests__/delivery-methods.test.ts +0 -385
  57. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  58. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  59. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  60. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  61. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -189
  62. package/src/execution/__tests__/execution-record.test.ts +0 -1284
  63. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  64. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  65. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -138
  66. package/src/execution/__tests__/format.test.ts +0 -458
  67. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  68. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  69. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  70. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  71. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -238
  72. package/src/execution/__tests__/host-mode.test.ts +0 -87
  73. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  74. package/src/execution/__tests__/index-session-start.test.ts +0 -593
  75. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  76. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -337
  77. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  78. package/src/execution/__tests__/list-component.test.ts +0 -401
  79. package/src/execution/__tests__/list-fields.test.ts +0 -109
  80. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  81. package/src/execution/__tests__/model-resolver.test.ts +0 -413
  82. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  83. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  84. package/src/execution/__tests__/notifier-flush.test.ts +0 -574
  85. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  86. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  87. package/src/execution/__tests__/output-collector.test.ts +0 -61
  88. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  89. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  90. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  91. package/src/execution/__tests__/record-store.test.ts +0 -1057
  92. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  93. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  94. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  95. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  96. package/src/execution/__tests__/relay-env.test.ts +0 -42
  97. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  98. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -644
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  105. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  106. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  107. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  108. package/src/execution/__tests__/session-pending.test.ts +0 -197
  109. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  110. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -311
  112. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  113. package/src/execution/__tests__/spawn-args.test.ts +0 -434
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  117. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  118. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  119. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  120. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  122. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  124. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  125. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  126. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  127. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -506
  128. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  129. package/src/execution/__tests__/timeout-integration.test.ts +0 -397
  130. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  131. package/src/execution/__tests__/tool-action.test.ts +0 -385
  132. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  133. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  134. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  135. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  136. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  137. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  138. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  139. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  140. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  141. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  142. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  143. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  144. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  145. package/src/execution/agent-registry.ts +0 -193
  146. package/src/execution/agent-result-mapper.ts +0 -90
  147. package/src/execution/alive-store.ts +0 -92
  148. package/src/execution/argv-mirror.ts +0 -109
  149. package/src/execution/best-effort.ts +0 -38
  150. package/src/execution/channel-registry-access.ts +0 -144
  151. package/src/execution/concurrency-pool.ts +0 -116
  152. package/src/execution/config.ts +0 -96
  153. package/src/execution/dialog-queue.ts +0 -330
  154. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  155. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  156. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  158. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  159. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  160. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  161. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  162. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  163. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  164. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  165. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  166. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  167. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  168. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  169. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  170. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  171. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  172. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  173. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  174. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  175. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  176. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  177. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  178. package/src/execution/engine/common/data-dir.ts +0 -62
  179. package/src/execution/engine/common/errors.ts +0 -183
  180. package/src/execution/engine/common/event-journal.ts +0 -254
  181. package/src/execution/engine/common/journal-replay.ts +0 -62
  182. package/src/execution/engine/common/kill-chain.ts +0 -221
  183. package/src/execution/engine/common/nesting-guard.ts +0 -50
  184. package/src/execution/engine/common/persona-router.ts +0 -108
  185. package/src/execution/engine/common/pool-manager.ts +0 -226
  186. package/src/execution/engine/common/schema-emulation.ts +0 -189
  187. package/src/execution/engine/common/session-view-projection.ts +0 -51
  188. package/src/execution/engine/engine-discovery.ts +0 -65
  189. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  190. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  191. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  192. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  193. package/src/execution/engine/engines/pi/reader.ts +0 -48
  194. package/src/execution/engine/engines/pi/registration.ts +0 -35
  195. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  197. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  198. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  199. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  200. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  201. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -567
  204. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  205. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  206. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  207. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  208. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  209. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  210. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  211. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -648
  212. package/src/execution/engine/host-task-spec.ts +0 -47
  213. package/src/execution/engine/model-prompt.ts +0 -59
  214. package/src/execution/engine/paths.ts +0 -42
  215. package/src/execution/engine/port.ts +0 -153
  216. package/src/execution/engine/registry.ts +0 -123
  217. package/src/execution/engine/routing.ts +0 -218
  218. package/src/execution/engine/types.ts +0 -304
  219. package/src/execution/execute-options-mapper.ts +0 -111
  220. package/src/execution/execution-record.ts +0 -906
  221. package/src/execution/finalize-record.ts +0 -251
  222. package/src/execution/finalized-marker.ts +0 -51
  223. package/src/execution/get-state-handshake.ts +0 -104
  224. package/src/execution/host-mode.ts +0 -56
  225. package/src/execution/idle-gc.ts +0 -47
  226. package/src/execution/lifecycle-manager.ts +0 -491
  227. package/src/execution/lifecycle-predicates.ts +0 -65
  228. package/src/execution/manifest-store.ts +0 -256
  229. package/src/execution/model-config-service.ts +0 -204
  230. package/src/execution/model-resolver.ts +0 -276
  231. package/src/execution/notifier.ts +0 -260
  232. package/src/execution/output-collector.ts +0 -88
  233. package/src/execution/path-encoding.ts +0 -60
  234. package/src/execution/pi-invocation.ts +0 -120
  235. package/src/execution/record-entry.ts +0 -132
  236. package/src/execution/record-store.ts +0 -1234
  237. package/src/execution/relay-env.ts +0 -37
  238. package/src/execution/session-context-resolver.ts +0 -64
  239. package/src/execution/session-file-gc.ts +0 -120
  240. package/src/execution/session-pending.ts +0 -164
  241. package/src/execution/session-reconstructor.ts +0 -678
  242. package/src/execution/session-runner.ts +0 -1548
  243. package/src/execution/sessions-index.ts +0 -304
  244. package/src/execution/spawn-event-adapter.ts +0 -363
  245. package/src/execution/stdin-writer.ts +0 -198
  246. package/src/execution/stream-sink.ts +0 -126
  247. package/src/execution/subagent-service.ts +0 -2119
  248. package/src/execution/subprocess-agent-runner.ts +0 -303
  249. package/src/execution/temp-prompt.ts +0 -62
  250. package/src/execution/tombstone-store.ts +0 -72
  251. package/src/execution/turn-limiter.ts +0 -102
  252. package/src/execution/types.ts +0 -910
  253. package/src/execution/ui-channels.ts +0 -216
  254. package/src/execution/ui-interaction-model.ts +0 -48
  255. package/src/execution/ui-request-handler-factory.ts +0 -212
  256. package/src/execution/ui-request-observability.ts +0 -81
  257. package/src/execution/ui-request-queue.ts +0 -174
  258. package/src/execution/worktree-manager.ts +0 -688
  259. package/src/execution/worktree-registry.ts +0 -222
  260. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -336
  261. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -208
  262. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -157
  263. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  264. package/src/orchestration/__tests__/concurrency-gate.test.ts +0 -125
  265. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  266. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -724
  267. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  268. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  269. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  270. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -314
  271. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  272. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  273. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -277
  274. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  275. package/src/orchestration/__tests__/lifecycle.test.ts +0 -625
  276. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -1971
  277. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  278. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -157
  279. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  280. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  281. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  282. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -520
  283. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  284. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -110
  285. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  286. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  287. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  288. package/src/orchestration/agent-opts-resolver.ts +0 -93
  289. package/src/orchestration/args-validator.ts +0 -127
  290. package/src/orchestration/concurrency-gate.ts +0 -69
  291. package/src/orchestration/config-loader.ts +0 -315
  292. package/src/orchestration/error-recovery.ts +0 -862
  293. package/src/orchestration/execute-agent-call.ts +0 -217
  294. package/src/orchestration/launcher.ts +0 -410
  295. package/src/orchestration/lifecycle.ts +0 -396
  296. package/src/orchestration/models/__tests__/budget.test.ts +0 -367
  297. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  298. package/src/orchestration/models/agent-call.ts +0 -83
  299. package/src/orchestration/models/budget.ts +0 -148
  300. package/src/orchestration/models/ports.ts +0 -164
  301. package/src/orchestration/models/run-runtime.ts +0 -89
  302. package/src/orchestration/models/run-spec.ts +0 -83
  303. package/src/orchestration/models/run-state.ts +0 -44
  304. package/src/orchestration/models/trace.ts +0 -183
  305. package/src/orchestration/models/types.ts +0 -292
  306. package/src/orchestration/models/workflow-run.ts +0 -254
  307. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  308. package/src/orchestration/models/workflow-script.ts +0 -117
  309. package/src/orchestration/script-lint.ts +0 -766
  310. package/src/orchestration/skill-discovery.ts +0 -92
  311. package/src/orchestration/worker-handle.ts +0 -115
  312. package/src/orchestration/worker-host.ts +0 -98
  313. package/src/orchestration/worker-script-builder.ts +0 -408
  314. package/src/orchestration/workflow-files.ts +0 -85
  315. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  316. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  317. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  318. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  319. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  320. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  321. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  322. package/src/shared/agent-event.ts +0 -13
  323. package/src/shared/agent-ref.ts +0 -57
  324. package/src/shared/meta-parser.ts +0 -261
  325. package/src/shared/resource-discovery.ts +0 -811
  326. package/src/shared/resource-meta.ts +0 -65
  327. package/src/shared/schema-jsonify.ts +0 -56
  328. package/src/shared/xml-injection.ts +0 -35
  329. package/workflows/README.md +0 -81
  330. package/workflows/_shared/agent-refs.cjs +0 -40
  331. package/workflows/chain.js +0 -131
  332. package/workflows/map-reduce.js +0 -174
  333. package/workflows/parallel.js +0 -148
  334. package/workflows/review-fix-loop-utils.cjs +0 -1307
  335. package/workflows/review-fix-loop.js +0 -1396
  336. 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
+ normalizeEngineId,
22
+ runEngineAwarenessTurn,
23
+ type EngineAwarenessDeps,
24
+ type EngineAwarenessOutcome,
25
+ } from "../engine-awareness";
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,341 @@
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
+ import { formatModelList, setupModelListInjector, type ModelEntry } from "../model-list-injector.ts";
30
+ import type { EnginePort } from "@zhushanwen/subagent-core/execution/engine/port.ts";
31
+ import { clearEngines, registerEngine } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
32
+ import {
33
+ buildEngineModelsPromptAppend,
34
+ buildSubagentEngineSection,
35
+ } from "@zhushanwen/subagent-core/execution/engine/model-prompt.ts";
36
+
37
+ // ── 测试数据 ────────────────────────────────────────────
38
+
39
+ /** pi registry 风格条目(provider models 段数据源;同时充当 ctx.modelRegistry 快照)。 */
40
+ const PROVIDER_ENTRIES: ModelEntry[] = [
41
+ {
42
+ provider: "zai-coding-cn",
43
+ id: "glm-5.3",
44
+ name: "GLM 5.3",
45
+ reasoning: true,
46
+ input: ["text"],
47
+ contextWindow: 200_000,
48
+ },
49
+ {
50
+ provider: "minimax-cn",
51
+ id: "MiniMax-M3",
52
+ name: "MiniMax M3",
53
+ reasoning: true,
54
+ input: ["text", "image"],
55
+ contextWindow: 1_000_000,
56
+ },
57
+ ];
58
+
59
+ /** zcode 引擎清单(v2 registry 风格 id)。 */
60
+ const ZCODE_MODELS: Array<{ id: string; name?: string }> = [
61
+ { id: "builtin:bigmodel-coding-plan/GLM-5.3", name: "GLM-5.3" },
62
+ { id: "builtin:bigmodel-coding-plan/GLM-5.3-Flash" },
63
+ ];
64
+
65
+ /** 模拟 pi 核心 system prompt(engine 追加段之外的全部内容)。 */
66
+ const BASE = "You are a coding agent.\n\n# System\nCore prompt body.";
67
+
68
+ /**
69
+ * engine handler 的追加段拼装(与 index.ts engine handler 同款:状态段 + 清单段、
70
+ * 空段剔除、\n\n 连接)。复刻保真由下方「段序守护」describe 的源码锚点断言保证。
71
+ */
72
+ function composeEngineAppend(defaultEngine: string | undefined): string {
73
+ return [buildSubagentEngineSection(defaultEngine), buildEngineModelsPromptAppend(defaultEngine)]
74
+ .filter((part) => part !== "")
75
+ .join("\n\n");
76
+ }
77
+
78
+ /** index.ts 尾部追加模板的分隔符(`${event.systemPrompt}\n\n${append}`)。 */
79
+ const APPEND_SEPARATOR = "\n\n";
80
+
81
+ /** 合成一个 turn 的 system prompt 尾部:BASE + provider models 段 + engine 追加段。 */
82
+ function composeTurn(defaultEngine: string | undefined): string {
83
+ const providerSection = formatModelList(PROVIDER_ENTRIES);
84
+ const afterProvider = providerSection === "" ? BASE : BASE + providerSection;
85
+ const append = composeEngineAppend(defaultEngine);
86
+ return append === "" ? afterProvider : `${afterProvider}${APPEND_SEPARATOR}${append}`;
87
+ }
88
+
89
+ /** 最大公共前缀长度(逐码元比较;用于「分叉点不早于稳定头部」断言)。 */
90
+ function commonPrefixLength(a: string, b: string): number {
91
+ const n = Math.min(a.length, b.length);
92
+ let i = 0;
93
+ while (i < n && a[i] === b[i]) i++;
94
+ return i;
95
+ }
96
+
97
+ /** 最小 fake 引擎(照 model-prompt.test.ts 同款形态):仅 listModels 参与注入链。 */
98
+ function fakeEngine(id: string, models: Array<{ id: string; name?: string }>): EnginePort {
99
+ return {
100
+ id,
101
+ capabilities: () => ({ conversation: "unsupported", steer: "unsupported", sandbox: "none" }),
102
+ probe: async () => ({ ok: true, engineVersion: "test" }),
103
+ run: async () => {
104
+ throw new Error("not used in this test");
105
+ },
106
+ interact: async () => {
107
+ throw new Error("not used in this test");
108
+ },
109
+ read: async () => ({ engineId: id, turns: [], source: "outcome-only" }),
110
+ // 每次调用返回新数组实例(模拟 registry 每 turn 现值)——渲染必须与实例无关
111
+ listModels: () => models.map((m) => ({ ...m })),
112
+ };
113
+ }
114
+
115
+ beforeEach(() => {
116
+ clearEngines();
117
+ registerEngine("zcode", () => fakeEngine("zcode", ZCODE_MODELS));
118
+ });
119
+
120
+ afterEach(() => {
121
+ clearEngines();
122
+ });
123
+
124
+ // ── 1. 段渲染确定性(D7)────────────────────────────────
125
+
126
+ describe("段渲染确定性(D7:同输入恒同输出)", () => {
127
+ it("pi 形态:状态段多次独立调用逐字节相等;清单段恒空串", () => {
128
+ // pi 形态不依赖注册表(直接走缺省分支),registry 中有 zcode 不影响
129
+ const first = buildSubagentEngineSection("pi");
130
+ const second = buildSubagentEngineSection("pi");
131
+ const third = buildSubagentEngineSection("pi");
132
+ expect(first).toBe(second);
133
+ expect(second).toBe(third);
134
+ // 非空自检:确认 toBe 断言真的在比对段内容而非空串
135
+ expect(first).toContain("<current_subagent_engine>");
136
+ expect(buildEngineModelsPromptAppend("pi")).toBe("");
137
+ });
138
+
139
+ it("zcode 形态:状态段 + 清单段多次独立调用逐字节相等(listModels 每次返回新数组实例)", () => {
140
+ const s1 = buildSubagentEngineSection("zcode");
141
+ const s2 = buildSubagentEngineSection("zcode");
142
+ const m1 = buildEngineModelsPromptAppend("zcode");
143
+ const m2 = buildEngineModelsPromptAppend("zcode");
144
+ expect(s1).toBe(s2);
145
+ expect(m1).toBe(m2);
146
+ expect(m1).toContain("<available_zcode_models>");
147
+ expect(m1).toContain("<id>builtin:bigmodel-coding-plan/GLM-5.3</id>");
148
+ });
149
+
150
+ it("ghost 形态(未注册引擎):警告段多次独立调用逐字节相等", () => {
151
+ const first = buildSubagentEngineSection("ghost");
152
+ const second = buildSubagentEngineSection("ghost");
153
+ expect(first).toBe(second);
154
+ expect(first).toContain("engine 'ghost' is not registered");
155
+ expect(buildEngineModelsPromptAppend("ghost")).toBe("");
156
+ });
157
+
158
+ it("形态间干扰不产生状态残留:zcode → pi → zcode,两次 zcode 输出逐字节相等", () => {
159
+ const before = buildSubagentEngineSection("zcode");
160
+ // 穿插渲染其他形态(含清单段消费 listModels),再回到 zcode
161
+ buildSubagentEngineSection("pi");
162
+ buildEngineModelsPromptAppend("zcode");
163
+ buildSubagentEngineSection("ghost");
164
+ expect(buildSubagentEngineSection("zcode")).toBe(before);
165
+ });
166
+
167
+ it("注册表重建(clearEngines + 同 id 同清单重注册)后同形态输出不变", () => {
168
+ const firstSection = buildSubagentEngineSection("zcode");
169
+ const firstAppend = buildEngineModelsPromptAppend("zcode");
170
+ clearEngines();
171
+ registerEngine("zcode", () => fakeEngine("zcode", ZCODE_MODELS));
172
+ expect(buildSubagentEngineSection("zcode")).toBe(firstSection);
173
+ expect(buildEngineModelsPromptAppend("zcode")).toBe(firstAppend);
174
+ });
175
+
176
+ it("组合拼装(index.ts 同款:状态段 + 清单段 join)同引擎多次调用逐字节相等", () => {
177
+ for (const engine of ["zcode", "pi", "ghost"]) {
178
+ expect(composeEngineAppend(engine)).toBe(composeEngineAppend(engine));
179
+ }
180
+ });
181
+ });
182
+
183
+ // ── 2. 引擎切换只变尾部(A8 前置)───────────────────────
184
+
185
+ /**
186
+ * 断言「从 from 切到 to 时变化只发生在尾部 engine 段」:
187
+ * - provider models 段及其之前的头部逐字节不变(公共前缀 toBe 级断言);
188
+ * - 剥离尾部 engine 追加段后两串余部逐字节相等且恰为稳定头部;
189
+ * - 分叉点不早于稳定头部末尾(差异绝不侵入 provider models 段);
190
+ * - 切换后的引擎追加段恰居 prompt 尾部。
191
+ */
192
+ function expectOnlyTailDiffers(from: string | undefined, to: string | undefined): void {
193
+ const stableHead = BASE + formatModelList(PROVIDER_ENTRIES);
194
+ const before = composeTurn(from);
195
+ const after = composeTurn(to);
196
+ const beforeAppend = composeEngineAppend(from);
197
+ const afterAppend = composeEngineAppend(to);
198
+
199
+ expect(before).not.toBe(after);
200
+ expect(before.startsWith(stableHead)).toBe(true);
201
+ expect(after.startsWith(stableHead)).toBe(true);
202
+ expect(before.slice(0, before.length - beforeAppend.length - APPEND_SEPARATOR.length)).toBe(
203
+ stableHead,
204
+ );
205
+ expect(after.slice(0, after.length - afterAppend.length - APPEND_SEPARATOR.length)).toBe(
206
+ stableHead,
207
+ );
208
+ expect(commonPrefixLength(before, after)).toBeGreaterThanOrEqual(stableHead.length);
209
+ expect(after.endsWith(afterAppend)).toBe(true);
210
+ }
211
+
212
+ describe("引擎切换只变尾部(A8 前置:变化只断尾部 cache 前缀)", () => {
213
+ it("zcode → pi:provider models 段前缀不变,变化只发生在 engine 段(尾部)", () => {
214
+ expectOnlyTailDiffers("zcode", "pi");
215
+ });
216
+
217
+ it("pi → zcode(反向切换):同款只变尾部", () => {
218
+ expectOnlyTailDiffers("pi", "zcode");
219
+ });
220
+
221
+ it("zcode → ghost(配置手误形态):G4 降级段同样只替换尾部,不破坏前缀", () => {
222
+ expectOnlyTailDiffers("zcode", "ghost");
223
+ });
224
+ });
225
+
226
+ // ── 3. 段序守护:engine 段恒链尾 ────────────────────────
227
+
228
+ /** 链模拟用 handler 形态(参数收窄到运行时实际消费的字段)。 */
229
+ type ChainHandler = (event: { systemPrompt: string }, ctx: unknown) => unknown;
230
+
231
+ /** fake ExtensionAPI:捕获 pi.on 注册的 handler(保持注册序)。 */
232
+ function capturePi(): { handlers: ChainHandler[]; api: ExtensionAPI } {
233
+ const handlers: ChainHandler[] = [];
234
+ const api = {
235
+ on: (_event: string, handler: (...args: unknown[]) => unknown) => {
236
+ // on 的 mock 签名参数为 unknown[],收窄到两参形态(pi 调用约定恒两参)
237
+ handlers.push(((event: unknown, ctx: unknown) => handler(event, ctx)) as ChainHandler);
238
+ },
239
+ } as unknown as ExtensionAPI;
240
+ return { handlers, api };
241
+ }
242
+
243
+ /** 模拟 pi 的 before_agent_start 串联语义:按注册序执行,前序返回的 systemPrompt 作为后序输入。 */
244
+ async function runHandlerChain(handlers: ChainHandler[], initialPrompt: string): Promise<string> {
245
+ let prompt = initialPrompt;
246
+ const ctx = { modelRegistry: { getAvailable: () => PROVIDER_ENTRIES } };
247
+ for (const handler of handlers) {
248
+ const result: unknown = await handler({ systemPrompt: prompt }, ctx);
249
+ // 运行时收窄:handler 返回 { systemPrompt: string } 时替换链上 prompt,否则保持
250
+ if (typeof result === "object" && result !== null && "systemPrompt" in result) {
251
+ const sp: unknown = (result as { systemPrompt: unknown }).systemPrompt;
252
+ if (typeof sp === "string") prompt = sp;
253
+ }
254
+ }
255
+ return prompt;
256
+ }
257
+
258
+ describe("段序守护:engine 段恒链尾(D7)", () => {
259
+ it("handler 链模拟:按 index.ts 注册序执行后,engine 段位于 provider models 段之后且居尾", async () => {
260
+ const { handlers, api } = capturePi();
261
+ // 真实导入 model list injector(provider models 段的生产渲染路径)
262
+ setupModelListInjector(api);
263
+ // 复刻 index.ts engine handler 的渲染拼装部分(检测编排部分归 engine-awareness.test.ts)
264
+ const defaultEngine = "zcode";
265
+ handlers.push((event) => {
266
+ const append = composeEngineAppend(defaultEngine);
267
+ return { systemPrompt: `${event.systemPrompt}${APPEND_SEPARATOR}${append}` };
268
+ });
269
+
270
+ const prompt = await runHandlerChain(handlers, BASE);
271
+
272
+ const providerIdx = prompt.indexOf("<available_provider_models>");
273
+ const statusIdx = prompt.indexOf("<current_subagent_engine>");
274
+ const zcodeListIdx = prompt.indexOf("<available_zcode_models>");
275
+ expect(providerIdx).toBeGreaterThanOrEqual(0);
276
+ // 段序硬约束:engine 状态段与引擎清单段都在 provider models 段之后
277
+ expect(statusIdx).toBeGreaterThan(providerIdx);
278
+ expect(zcodeListIdx).toBeGreaterThan(providerIdx);
279
+ // 恒链尾:engine 追加段是 prompt 的最后内容
280
+ expect(prompt.endsWith(`${APPEND_SEPARATOR}${composeEngineAppend(defaultEngine)}`)).toBe(true);
281
+ // model list handler 注入形态锚定(BASE + injection 无分隔,生产 handler 同款)
282
+ expect(prompt.startsWith(BASE + formatModelList(PROVIDER_ENTRIES))).toBe(true);
283
+ });
284
+
285
+ it("对照:注册序颠倒(engine 先、model list 后)时段序断言必失败——守护有判别力", async () => {
286
+ const { handlers, api } = capturePi();
287
+ const defaultEngine = "zcode";
288
+ handlers.push((event) => {
289
+ const append = composeEngineAppend(defaultEngine);
290
+ return { systemPrompt: `${event.systemPrompt}${APPEND_SEPARATOR}${append}` };
291
+ });
292
+ setupModelListInjector(api);
293
+
294
+ const prompt = await runHandlerChain(handlers, BASE);
295
+
296
+ // 颠倒后 engine 段跑到 provider models 段之前——证明上一条 it 的断言非平凡
297
+ const providerIdx = prompt.indexOf("<available_provider_models>");
298
+ const statusIdx = prompt.indexOf("<current_subagent_engine>");
299
+ expect(statusIdx).toBeGreaterThanOrEqual(0);
300
+ expect(statusIdx).toBeLessThan(providerIdx);
301
+ });
302
+
303
+ it("index.ts 源码级守护:engine handler 注册序在 model list injector 之后(段序由注册序唯一决定)", () => {
304
+ const src = readFileSync(fileURLToPath(new URL("../../index.ts", import.meta.url)), "utf8");
305
+ const lineOf = (needle: string): number => {
306
+ const idx = src.indexOf(needle);
307
+ if (idx < 0) throw new Error(`index.ts 中未找到守护锚点:${needle}`);
308
+ return src.slice(0, idx).split("\n").length;
309
+ };
310
+
311
+ // 注册序:subagents → workflows → provider models → engine handler(链尾,D7)。
312
+ // engine handler 是唯一的直接 pi.on("before_agent_start")(其余三段经 setup* 间接注册),
313
+ // 恰出现一次的断言让「链尾」语义精确成立;未来若新增直接注册需人工复核段序。
314
+ expect(src.split('pi.on("before_agent_start"')).toHaveLength(2);
315
+ const lineSubagents = lineOf("setupSubagentListInjector(pi);");
316
+ const lineWorkflows = lineOf("setupWorkflowListInjector(pi);");
317
+ const lineModels = lineOf("setupModelListInjector(pi);");
318
+ const lineEngineHandler = lineOf('pi.on("before_agent_start"');
319
+ expect(lineSubagents).toBeLessThan(lineWorkflows);
320
+ expect(lineWorkflows).toBeLessThan(lineModels);
321
+ expect(lineModels).toBeLessThan(lineEngineHandler);
322
+
323
+ // engine handler 内渲染调用序:状态段在清单段之前(append 内段序:状态段 → 清单段)。
324
+ // 两个调用在 index.ts 中位于同一行(数组字面量),行号无法区分先后——用字符偏移断言
325
+ const offsetStatusCall = src.indexOf("buildSubagentEngineSection(defaultEngine)");
326
+ const offsetModelsCall = src.indexOf("buildEngineModelsPromptAppend(defaultEngine)");
327
+ if (offsetStatusCall < 0 || offsetModelsCall < 0) {
328
+ throw new Error("index.ts 中未找到 engine 段渲染调用锚点");
329
+ }
330
+ expect(offsetStatusCall).toBeGreaterThan(src.indexOf('pi.on("before_agent_start"'));
331
+ expect(offsetStatusCall).toBeLessThan(offsetModelsCall);
332
+
333
+ // 链模拟复刻保真锚点:append 拼装形态(空段剔除 + \n\n 连接 + 尾部追加模板)。
334
+ // 这些锚点保证 composeEngineAppend 与 index.ts 生产拼装的一致性——生产形态
335
+ // 变化会先在这里红,提醒同步更新本文件全部复刻点。
336
+ expect(src).toContain('[buildSubagentEngineSection(defaultEngine), buildEngineModelsPromptAppend(defaultEngine)]');
337
+ expect(src).toContain('.filter((part) => part !== "")');
338
+ expect(src).toContain('.join("\\n\\n")');
339
+ expect(src).toContain("${event.systemPrompt}\\n\\n${append}");
340
+ });
341
+ });
@@ -10,7 +10,7 @@
10
10
 
11
11
  import { beforeEach, describe, expect, it, vi } from "vitest";
12
12
 
13
- import type { DiscoveredResource } from "../../shared/resource-discovery.ts";
13
+ import type { DiscoveredResource } from "@zhushanwen/subagent-core/shared/resource-discovery.ts";
14
14
  // 共享 mock 基建(vi.mock 工厂 / mock pi / mock ctx):helpers/injector-test-mocks.ts
15
15
  import { createDiscoveryModuleMock, createLoggerModuleMock, createMockCtx, createMockPi, type CapturedHandlers } from "./helpers/injector-test-mocks.ts";
16
16
 
@@ -19,7 +19,7 @@ import { createDiscoveryModuleMock, createLoggerModuleMock, createMockCtx, creat
19
19
  // resetModules 调用计数连续,mockClear 控制每用例重置) ──
20
20
  const spies = vi.hoisted(() => ({ discoverResources: vi.fn(), getCachedFileContent: vi.fn() }));
21
21
 
22
- vi.mock("../../shared/resource-discovery.ts", () => createDiscoveryModuleMock(spies));
22
+ vi.mock("@zhushanwen/subagent-core/shared/resource-discovery.ts", () => createDiscoveryModuleMock(spies));
23
23
 
24
24
  // 工厂必须写成箭头惰性形式:vi.mock 被提升到 import 之前执行,直接传
25
25
  // createLoggerModuleMock 引用会在提升位置立即求值 import 绑定 → TDZ ReferenceError
@@ -306,7 +306,7 @@ describe("discoverAllAgents 顺序契约(KV-cache)", () => {
306
306
  spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
307
307
 
308
308
  const { discoverAllAgents } = await import("../subagent-list-injector");
309
- const agents = await discoverAllAgents("/ws", "/agent");
309
+ const agents = await discoverAllAgents("/ws");
310
310
  expect(agents.map((a) => a.name)).toEqual(["alpha", "worker", "zeta"]);
311
311
  });
312
312
 
@@ -328,8 +328,8 @@ describe("discoverAllAgents 顺序契约(KV-cache)", () => {
328
328
  spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
329
329
 
330
330
  const { discoverAllAgents, formatAgentList } = await import("../subagent-list-injector");
331
- const first = await discoverAllAgents("/ws", "/agent");
332
- const second = await discoverAllAgents("/ws", "/agent");
331
+ const first = await discoverAllAgents("/ws");
332
+ const second = await discoverAllAgents("/ws");
333
333
  expect(second).toEqual(first);
334
334
  expect(formatAgentList(second)).toBe(formatAgentList(first));
335
335
  });