@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
@@ -11,7 +11,10 @@ import { describe, expect, it, vi } from "vitest";
11
11
 
12
12
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
13
13
 
14
- import type { ModelEntry } from "../model-list-injector";
14
+ // C5①:formatModelList + ModelEntry 口径下沉 core barrel——测试改从 barrel 取实现,
15
+ // guide 用 pi 宿主注入文案
16
+ import { formatModelList, type ModelEntry } from "@zhushanwen/subagent-core";
17
+ import { MODEL_LIST_GUIDE } from "../model-list-injector";
15
18
 
16
19
  // ── 测试数据工厂 ────────────────────────────────────────
17
20
 
@@ -30,65 +33,59 @@ function entry(overrides: Partial<ModelEntry> = {}): ModelEntry {
30
33
  // ── 纯函数:formatModelList ─────────────────────────────
31
34
 
32
35
  describe("formatModelList", () => {
33
- it("空列表返回空串(不注入)", async () => {
34
- const { formatModelList } = await import("../model-list-injector");
35
- expect(formatModelList([])).toBe("");
36
+ it("空列表返回空串(不注入)", () => {
37
+ expect(formatModelList([], { guide: MODEL_LIST_GUIDE })).toBe("");
36
38
  });
37
39
 
38
- it("渲染 provider/modelId + name + caps + contextWindow", async () => {
39
- const { formatModelList } = await import("../model-list-injector");
40
+ it("渲染 provider/modelId + name + caps + contextWindow", () => {
40
41
  const out = formatModelList([
41
42
  entry({ provider: "p1", id: "m1", name: "Model One" }),
42
- ]);
43
+ ], { guide: MODEL_LIST_GUIDE });
43
44
  expect(out).toContain("<available_provider_models>");
44
45
  expect(out).toContain("<model><id>p1/m1</id><name>Model One</name><caps>reasoning</caps><contextWindow>200000</contextWindow></model>");
45
46
  expect(out).toContain("</available_provider_models>");
46
47
  });
47
48
 
48
- it("input 含 image 时 caps 加 vision;无能力时省略 caps 段", async () => {
49
- const { formatModelList } = await import("../model-list-injector");
49
+ it("input 含 image 时 caps 加 vision;无能力时省略 caps 段", () => {
50
50
  const out = formatModelList([
51
51
  entry({ id: "vision-m", input: ["text", "image"] }),
52
52
  entry({ id: "plain-m", reasoning: false }),
53
- ]);
53
+ ], { guide: MODEL_LIST_GUIDE });
54
54
  expect(out).toContain("<id>zai-coding-cn/vision-m</id>");
55
55
  expect(out).toContain("<caps>reasoning,vision</caps>");
56
56
  // plain-m:reasoning=false 且无 image → 无 caps 段
57
57
  expect(out).toMatch(/<id>zai-coding-cn\/plain-m<\/id><name>[^<]*<\/name><contextWindow>/);
58
58
  });
59
59
 
60
- it("按 (provider, id) 排序——输入乱序输出仍字节稳定(KV cache 前提)", async () => {
61
- const { formatModelList } = await import("../model-list-injector");
60
+ it("按 (provider, id) 排序——输入乱序输出仍字节稳定(KV cache 前提)", () => {
62
61
  const models = [
63
62
  entry({ provider: "b-prov", id: "z-model" }),
64
63
  entry({ provider: "a-prov", id: "y-model" }),
65
64
  entry({ provider: "a-prov", id: "x-model" }),
66
65
  ];
67
- const out1 = formatModelList([...models]);
68
- const out2 = formatModelList([...models].reverse());
66
+ const out1 = formatModelList([...models], { guide: MODEL_LIST_GUIDE });
67
+ const out2 = formatModelList([...models].reverse(), { guide: MODEL_LIST_GUIDE });
69
68
  expect(out1).toBe(out2);
70
69
  // 排序断言:a-prov/x-model 在最前,b-prov 在最后
71
70
  const ids = [...out1.matchAll(/<id>([^<]+)<\/id>/g)].map((m) => m[1]);
72
71
  expect(ids).toEqual(["a-prov/x-model", "a-prov/y-model", "b-prov/z-model"]);
73
72
  });
74
73
 
75
- it("排序为码点序而非 locale 序(跨环境字节一致契约,禁 localeCompare)", async () => {
76
- const { formatModelList } = await import("../model-list-injector");
74
+ it("排序为码点序而非 locale 序(跨环境字节一致契约,禁 localeCompare)", () => {
77
75
  // 判别样本:码点序 "B"(0x42) < "a"(0x61),而多数 locale 的 localeCompare
78
76
  // 会把 "a-model" 排在 "B-model" 前——本断言在 localeCompare 实现下必挂
79
77
  const out = formatModelList([
80
78
  entry({ provider: "p", id: "a-model" }),
81
79
  entry({ provider: "p", id: "B-model" }),
82
- ]);
80
+ ], { guide: MODEL_LIST_GUIDE });
83
81
  const ids = [...out.matchAll(/<id>([^<]+)<\/id>/g)].map((m) => m[1]);
84
82
  expect(ids).toEqual(["p/B-model", "p/a-model"]);
85
83
  });
86
84
 
87
- it("name 含 XML 特殊字符时转义(防注入段结构破坏)", async () => {
88
- const { formatModelList } = await import("../model-list-injector");
85
+ it("name 含 XML 特殊字符时转义(防注入段结构破坏)", () => {
89
86
  const out = formatModelList([
90
87
  entry({ provider: "p", id: "m", name: `A<&>"'B` }),
91
- ]);
88
+ ], { guide: MODEL_LIST_GUIDE });
92
89
  expect(out).toContain("<name>A&lt;&amp;&gt;&quot;&apos;B</name>");
93
90
  });
94
91
  });
@@ -99,7 +96,7 @@ describe("setupModelListInjector", () => {
99
96
  type Handler = (event: unknown, ctx: unknown) => Promise<unknown>;
100
97
 
101
98
  async function setupWithRegistry(models: ModelEntry[], fail = false): Promise<Handler> {
102
- const mod = await import("../model-list-injector");
99
+ const mod = await import("../model-list-injector"); // handler 注册面(含 MODEL_LIST_GUIDE 注入)
103
100
  const handlers: Record<string, Handler> = {};
104
101
  const pi = {
105
102
  on: (name: string, fn: Handler) => {
@@ -10,23 +10,27 @@
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
+ // ——必须先于 barrel import 初始化:barrel 的 re-export 链会触发深路径 mock 工厂,
16
+ // 工厂闭包引用本 helpers 的绑定(helpers 文件头「惰性求值」约束)
15
17
  import { createDiscoveryModuleMock, createLoggerModuleMock, createMockCtx, createMockPi, type CapturedHandlers } from "./helpers/injector-test-mocks.ts";
18
+ // C5①:formatAgentList 下沉 core barrel——测试改从 barrel 取实现,guide 用 pi 宿主注入文案
19
+ import { formatAgentList } from "@zhushanwen/subagent-core";
16
20
 
17
21
  // ── 稳定 spy:vi.hoisted 保证 resetModules 后引用不变(vi.mock 工厂闭包捕获同一 fn,
18
22
  // 故 fresh import 的注入器拿到的 discoverResources === spies.discoverResources,跨
19
23
  // resetModules 调用计数连续,mockClear 控制每用例重置) ──
20
24
  const spies = vi.hoisted(() => ({ discoverResources: vi.fn(), getCachedFileContent: vi.fn() }));
21
25
 
22
- vi.mock("../../shared/resource-discovery.ts", () => createDiscoveryModuleMock(spies));
26
+ vi.mock("@zhushanwen/subagent-core/shared/resource-discovery.ts", () => createDiscoveryModuleMock(spies));
23
27
 
24
28
  // 工厂必须写成箭头惰性形式:vi.mock 被提升到 import 之前执行,直接传
25
29
  // createLoggerModuleMock 引用会在提升位置立即求值 import 绑定 → TDZ ReferenceError
26
30
  vi.mock("@zhushanwen/pi-extension-logger", () => createLoggerModuleMock());
27
31
 
28
32
  // ── 纯函数测试:静态 import(模块级缓存状态不影响纯函数;与下方缓存 describe 隔离) ──
29
- import { formatAgentList, parseAgentFrontmatter } from "../subagent-list-injector";
33
+ import { parseAgentFrontmatter, SUBAGENT_LIST_GUIDE } from "../subagent-list-injector";
30
34
 
31
35
  describe("parseAgentFrontmatter", () => {
32
36
  it("解析双引号包裹的 name + description", () => {
@@ -70,7 +74,7 @@ description: '代码审查'
70
74
 
71
75
  describe("formatAgentList", () => {
72
76
  it("空列表返回空串(不注入)", () => {
73
- expect(formatAgentList([])).toBe("");
77
+ expect(formatAgentList([], { guide: SUBAGENT_LIST_GUIDE })).toBe("");
74
78
  });
75
79
 
76
80
  it("TC1: when + examples 注入(原样渲染 + escapeXml + 缺省兼容)", () => {
@@ -86,7 +90,7 @@ describe("formatAgentList", () => {
86
90
  ],
87
91
  },
88
92
  { name: "legacy", description: "未迁移 agent", path: "/agents/legacy.md" },
89
- ]);
93
+ ], { guide: SUBAGENT_LIST_GUIDE });
90
94
  expect(out).toContain("<when>用户要求 review 代码</when>");
91
95
  // 正反原样渲染——negative action 含原因文本(评审 M5:渲染器不硬编码)
92
96
  expect(out).toContain('"帮我 review 这段代码" → 调用 reviewer 对抗式审查');
@@ -99,29 +103,34 @@ describe("formatAgentList", () => {
99
103
  path: "/agents/x.md",
100
104
  examples: [{ match: "处理 <task> 的 diff", action: "调用 x", positive: true }],
101
105
  },
102
- ]);
106
+ ], { guide: SUBAGENT_LIST_GUIDE });
103
107
  expect(xmlOut).toContain("&lt;task&gt;");
104
108
  // 缺省兼容:无 when/examples 的 agent 不渲染该段
105
- const legacyOut = formatAgentList([{ name: "legacy", description: "未迁移 agent", path: "/agents/legacy.md" }]);
109
+ const legacyOut = formatAgentList([{ name: "legacy", description: "未迁移 agent", path: "/agents/legacy.md" }], { guide: SUBAGENT_LIST_GUIDE });
106
110
  expect(legacyOut).not.toContain("<when>");
107
111
  expect(legacyOut).not.toContain("<examples>");
108
112
  });
109
113
 
110
114
  it("包含 P3 正向触发引导语(何时该 delegate)", () => {
111
- const out = formatAgentList([{ name: "worker", description: "d", path: "/agents/worker.md" }]);
115
+ const out = formatAgentList([{ name: "worker", description: "d", path: "/agents/worker.md" }], { guide: SUBAGENT_LIST_GUIDE });
112
116
  expect(out).toContain("PRIORITY");
113
117
  expect(out).toContain("3+ files");
114
118
  expect(out).toContain("delegate");
115
119
  expect(out).toContain("FIRST");
116
120
  });
117
121
 
118
- it("保留原 'ONLY use agent names from this list' 名字约束", () => {
119
- const out = formatAgentList([{ name: "worker", description: "d", path: "/agents/worker.md" }]);
120
- expect(out).toContain("ONLY use agent names from this list");
122
+ it("C5① guide 更新:agent 引用改为 <location> 路径形态,动态 agent 指引改为缺省 general-purpose(systemPrompt 参数已不在 schema)", () => {
123
+ const out = formatAgentList([{ name: "worker", description: "d", path: "/agents/worker.md" }], { guide: SUBAGENT_LIST_GUIDE });
124
+ expect(out).toContain("ONLY use agents from this list");
125
+ expect(out).toContain("pass the <location> path (absolute .md path) as the agent param");
126
+ expect(out).toContain("omit agent (a general-purpose agent is used)");
127
+ // 旧句已过期(subagent tool schema 无 systemPrompt 参数,见 subagent-tool-schema.ts)
128
+ expect(out).not.toContain("systemPrompt");
129
+ expect(out).not.toContain("ONLY use agent names from this list");
121
130
  });
122
131
 
123
132
  it("包含 'Do NOT call list to discover' 引导语", () => {
124
- const out = formatAgentList([{ name: "worker", description: "d", path: "/agents/worker.md" }]);
133
+ const out = formatAgentList([{ name: "worker", description: "d", path: "/agents/worker.md" }], { guide: SUBAGENT_LIST_GUIDE });
125
134
  expect(out).toContain("Do NOT call list to discover");
126
135
  expect(out).toContain("use list only for running state");
127
136
  });
@@ -130,7 +139,7 @@ describe("formatAgentList", () => {
130
139
  const out = formatAgentList([
131
140
  { name: "worker", description: "does work", path: "/agents/worker.md" },
132
141
  { name: "reviewer", description: "reviews code", path: "/agents/reviewer.md" },
133
- ]);
142
+ ], { guide: SUBAGENT_LIST_GUIDE });
134
143
  expect(out).toContain("<available_subagents>");
135
144
  expect(out).toContain("</available_subagents>");
136
145
  expect(out).toContain("<name>worker</name>");
@@ -143,10 +152,39 @@ describe("formatAgentList", () => {
143
152
  });
144
153
 
145
154
  it("转义 XML 特殊字符", () => {
146
- const out = formatAgentList([{ name: "a&b<c>", description: "\"q\"", path: "/agents/a&b<c>.md" }]);
155
+ const out = formatAgentList([{ name: "a&b<c>", description: "\"q\"", path: "/agents/a&b<c>.md" }], { guide: SUBAGENT_LIST_GUIDE });
147
156
  expect(out).toContain("<name>a&amp;b&lt;c&gt;</name>");
148
157
  expect(out).toContain("&quot;q&quot;");
149
158
  });
159
+
160
+ it("CA2 快照等价锚定:骨架 + 条目段逐字节等于 pi 旧本地实现模板(除 location 前缀外零变化)", () => {
161
+ // 条目模板逐字复制自 C5① 前的本地 formatAgentList 实现(escapeXml 同函数),
162
+ // 锚定 core 下沉未改变渲染字节(红线 8:等价豁免仅 location 前缀 + guide 段)
163
+ const agents = [
164
+ {
165
+ name: "reviewer",
166
+ description: "代码审查",
167
+ path: "/OLD-PREFIX/agents/reviewer.md",
168
+ when: "用户要求 review",
169
+ },
170
+ { name: "worker", description: "does work", path: "/OLD-PREFIX/agents/worker.md" },
171
+ ];
172
+ const expectedItems = [
173
+ ' <agent><name>reviewer</name><description>代码审查</description><when>用户要求 review</when><location>/OLD-PREFIX/agents/reviewer.md</location></agent>',
174
+ ' <agent><name>worker</name><description>does work</description><location>/OLD-PREFIX/agents/worker.md</location></agent>',
175
+ ];
176
+ const out = formatAgentList(agents, { guide: SUBAGENT_LIST_GUIDE });
177
+ expect(out).toBe(
178
+ `\n\n<available_subagents>\n${SUBAGENT_LIST_GUIDE}\n${expectedItems.join("\n")}\n</available_subagents>`,
179
+ );
180
+
181
+ // location 前缀替换(资产来源迁移:pi-sw 安装目录 → core 包)后其余字节零变化
182
+ const migrated = formatAgentList(
183
+ agents.map((a) => ({ ...a, path: a.path.replace("/OLD-PREFIX/", "/NEW-CORE-PKG/") })),
184
+ { guide: SUBAGENT_LIST_GUIDE },
185
+ );
186
+ expect(migrated).toBe(out.replaceAll("/OLD-PREFIX/", "/NEW-CORE-PKG/"));
187
+ });
150
188
  });
151
189
 
152
190
  // ──────────────────────────────────────────────────────────────
@@ -306,7 +344,7 @@ describe("discoverAllAgents 顺序契约(KV-cache)", () => {
306
344
  spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
307
345
 
308
346
  const { discoverAllAgents } = await import("../subagent-list-injector");
309
- const agents = await discoverAllAgents("/ws", "/agent");
347
+ const agents = await discoverAllAgents("/ws");
310
348
  expect(agents.map((a) => a.name)).toEqual(["alpha", "worker", "zeta"]);
311
349
  });
312
350
 
@@ -327,10 +365,12 @@ describe("discoverAllAgents 顺序契约(KV-cache)", () => {
327
365
  ]);
328
366
  spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
329
367
 
330
- const { discoverAllAgents, formatAgentList } = await import("../subagent-list-injector");
331
- const first = await discoverAllAgents("/ws", "/agent");
332
- const second = await discoverAllAgents("/ws", "/agent");
368
+ const { discoverAllAgents } = await import("../subagent-list-injector");
369
+ const first = await discoverAllAgents("/ws");
370
+ const second = await discoverAllAgents("/ws");
333
371
  expect(second).toEqual(first);
334
- expect(formatAgentList(second)).toBe(formatAgentList(first));
372
+ expect(formatAgentList(second, { guide: SUBAGENT_LIST_GUIDE })).toBe(
373
+ formatAgentList(first, { guide: SUBAGENT_LIST_GUIDE }),
374
+ );
335
375
  });
336
376
  });
@@ -10,24 +10,24 @@
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
+ // ——必须先于 barrel import 初始化:barrel 的 re-export 链会触发深路径 mock 工厂,
16
+ // 工厂闭包引用本 helpers 的绑定(helpers 文件头「惰性求值」约束)
15
17
  import { createDiscoveryModuleMock, createLoggerModuleMock, createMockCtx, createMockPi, type CapturedHandlers } from "./helpers/injector-test-mocks.ts";
18
+ // C5①:formatWorkflowList/summarizeDescription 下沉 core barrel——测试改从 barrel 取实现
19
+ import { formatWorkflowList, summarizeDescription } from "@zhushanwen/subagent-core";
16
20
 
17
21
  // ── 稳定 spy(vi.hoisted 保证 resetModules 后引用不变,见 subagent 测试同款注释) ──
18
22
  const spies = vi.hoisted(() => ({ discoverResources: vi.fn(), getCachedFileContent: vi.fn() }));
19
23
 
20
- vi.mock("../../shared/resource-discovery.ts", () => createDiscoveryModuleMock(spies));
24
+ vi.mock("@zhushanwen/subagent-core/shared/resource-discovery.ts", () => createDiscoveryModuleMock(spies));
21
25
 
22
26
  // 工厂必须写成箭头惰性形式(vi.mock 提升后直接传引用会 TDZ,见 helper 文件头注释)
23
27
  vi.mock("@zhushanwen/pi-extension-logger", () => createLoggerModuleMock());
24
28
 
25
29
  // ── 纯函数测试:静态 import(模块级缓存状态不影响纯函数) ──
26
- import {
27
- formatWorkflowList,
28
- parseWorkflowMeta,
29
- summarizeDescription,
30
- } from "../workflow-list-injector";
30
+ import { parseWorkflowMeta, WORKFLOW_LIST_GUIDE } from "../workflow-list-injector";
31
31
 
32
32
  describe("summarizeDescription", () => {
33
33
  it("短描述原样返回", () => {
@@ -113,14 +113,14 @@ phases: [Review, Fix]
113
113
 
114
114
  describe("formatWorkflowList", () => {
115
115
  it("空列表返回空串(不注入)", () => {
116
- expect(formatWorkflowList([])).toBe("");
116
+ expect(formatWorkflowList([], { guide: WORKFLOW_LIST_GUIDE })).toBe("");
117
117
  });
118
118
 
119
119
  it("用 <available_workflows> 标签包裹并列出每个 workflow", () => {
120
120
  const out = formatWorkflowList([
121
121
  { name: "chain", description: "三步链", path: "/workflows/chain.js" },
122
122
  { name: "parallel", description: "并行分析", path: "/workflows/parallel.js" },
123
- ]);
123
+ ], { guide: WORKFLOW_LIST_GUIDE });
124
124
  expect(out).toContain("<available_workflows>");
125
125
  expect(out).toContain("</available_workflows>");
126
126
  expect(out).toContain("<name>chain</name>");
@@ -131,14 +131,26 @@ describe("formatWorkflowList", () => {
131
131
  expect(out).toContain("<location>/workflows/parallel.js</location>");
132
132
  });
133
133
 
134
+ it("CA2 快照等价锚定:骨架 + 条目段逐字节等于 pi 旧本地实现模板(workflow 段 guide 未变)", () => {
135
+ // 条目模板逐字复制自 C5① 前的本地 formatWorkflowList 实现——锚定 core 下沉
136
+ // 未改变渲染字节(workflow 段无 location 变化面,全段逐字节等价)
137
+ const out = formatWorkflowList(
138
+ [{ name: "chain", description: "三步链", path: "/workflows/chain.js" }],
139
+ { guide: WORKFLOW_LIST_GUIDE },
140
+ );
141
+ expect(out).toBe(
142
+ `\n\n<available_workflows>\n${WORKFLOW_LIST_GUIDE}\n <workflow><name>chain</name><description>三步链</description><location>/workflows/chain.js</location></workflow>\n</available_workflows>`,
143
+ );
144
+ });
145
+
134
146
  it("包含 'Do NOT call list to discover available workflows' 引导语", () => {
135
- const out = formatWorkflowList([{ name: "chain", description: "d", path: "/workflows/chain.js" }]);
147
+ const out = formatWorkflowList([{ name: "chain", description: "d", path: "/workflows/chain.js" }], { guide: WORKFLOW_LIST_GUIDE });
136
148
  expect(out).toContain("Do NOT call list to discover available workflows");
137
149
  expect(out).toContain("use list only for running state");
138
150
  });
139
151
 
140
152
  it("引导语通用化:不写死内置 workflow 名,含 info 回收指引", () => {
141
- const out = formatWorkflowList([{ name: "chain", description: "d", path: "/workflows/chain.js" }]);
153
+ const out = formatWorkflowList([{ name: "chain", description: "d", path: "/workflows/chain.js" }], { guide: WORKFLOW_LIST_GUIDE });
142
154
  expect(out).toContain("All listed workflows run directly via action:run");
143
155
  expect(out).toContain("read the <location> script file");
144
156
  // 通用化约束:引导语不点名具体 workflow(名字由 @pi-meta 动态注入,
@@ -147,7 +159,7 @@ describe("formatWorkflowList", () => {
147
159
  });
148
160
 
149
161
  it("转义 XML 特殊字符", () => {
150
- const out = formatWorkflowList([{ name: "a&b", description: "<x>", path: "/workflows/a&b.js" }]);
162
+ const out = formatWorkflowList([{ name: "a&b", description: "<x>", path: "/workflows/a&b.js" }], { guide: WORKFLOW_LIST_GUIDE });
151
163
  expect(out).toContain("<name>a&amp;b</name>");
152
164
  expect(out).toContain("&lt;x&gt;");
153
165
  });
@@ -293,7 +305,7 @@ describe("discoverAllWorkflows 顺序契约(KV-cache)", () => {
293
305
  spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
294
306
 
295
307
  const { discoverAllWorkflows } = await import("../workflow-list-injector");
296
- const workflows = await discoverAllWorkflows("/ws", "/agent");
308
+ const workflows = await discoverAllWorkflows("/ws");
297
309
  expect(workflows.map((w) => w.name)).toEqual(["alpha", "chain", "zeta"]);
298
310
  });
299
311
 
@@ -313,10 +325,12 @@ describe("discoverAllWorkflows 顺序契约(KV-cache)", () => {
313
325
  ]);
314
326
  spies.getCachedFileContent.mockImplementation((p: string) => byPath[p] ?? null);
315
327
 
316
- const { discoverAllWorkflows, formatWorkflowList } = await import("../workflow-list-injector");
317
- const first = await discoverAllWorkflows("/ws", "/agent");
318
- const second = await discoverAllWorkflows("/ws", "/agent");
328
+ const { discoverAllWorkflows } = await import("../workflow-list-injector");
329
+ const first = await discoverAllWorkflows("/ws");
330
+ const second = await discoverAllWorkflows("/ws");
319
331
  expect(second).toEqual(first);
320
- expect(formatWorkflowList(second)).toBe(formatWorkflowList(first));
332
+ expect(formatWorkflowList(second, { guide: WORKFLOW_LIST_GUIDE })).toBe(
333
+ formatWorkflowList(first, { guide: WORKFLOW_LIST_GUIDE }),
334
+ );
321
335
  });
322
336
  });
@@ -0,0 +1,119 @@
1
+ // src/injectors/engine-awareness.ts
2
+ //
3
+ // [engine-awareness U3] 引擎感知检测编排。设计权威源:
4
+ // docs/design/subagent-engine-awareness-injection.md §2.3 物理数据流 /
5
+ // §3.3 D1(per-turn poll)、D1b(lastEngine 初始化与静默基线化)、D2(reload 先行)、
6
+ // D3(通知通道)、D5(三态读取语义)。
7
+ //
8
+ // 职责:per-turn(before_agent_start)三态 poll 全局 config → 与 per-session
9
+ // lastEngine diff → 变更时按硬约束顺序编排:提交读取结果到路由缓存(D2,先于一切——
10
+ // 只改注入不改路由会出现「prompt 说引擎 B、实际派发跑引擎 A」,权威信息源说谎)
11
+ // → sendMessage 对话流通知(D3/D4,短文案不含任何模型清单)→ 更新 lastEngine。
12
+ //
13
+ // 本模块是无状态纯编排(依赖注入式,可测);状态(lastEngine)与宿主能力
14
+ // (readConfig / applyRead / sendMessage)由 index.ts engine handler 装配注入。
15
+
16
+ import { getLogger } from "@zhushanwen/pi-extension-logger";
17
+
18
+ import type { GlobalConfigReadResult } from "@zhushanwen/subagent-core/execution/config.ts";
19
+ import { DEFAULT_ENGINE_ID, normalizeEngineId } from "@zhushanwen/subagent-core/execution/engine/registry.ts";
20
+
21
+ const logger = getLogger("subagents");
22
+
23
+ /** 引擎变更通知 customType(D3;renderer/测试按此识别引擎变更消息)。 */
24
+ export const ENGINE_CHANGE_CUSTOM_TYPE = "subagent-engine-changed";
25
+
26
+ /**
27
+ * 引擎变更通知文案(设计 §3.1 英文基准,三行结构逐字骨架;D4:不含任何模型清单,
28
+ * 清单只活在 system prompt 现值一份)。指路段按目标引擎参数化,与
29
+ * <current_subagent_engine> 恒在段的分界语义一致:目标 pi → 指向核心段
30
+ * <available_provider_models>;非 pi → 指向该引擎清单段 <available_<engine>_models>。
31
+ */
32
+ export function buildEngineChangeNotice(from: string, to: string): string {
33
+ const modelsLine =
34
+ to === DEFAULT_ENGINE_ID
35
+ ? "Use pi-registry ids from <available_provider_models> for explicit models;"
36
+ : `Use ids from <available_${to}_models> for explicit models;`;
37
+ return [
38
+ `Subagent default engine changed: ${from} → ${to} (effective this turn).`,
39
+ modelsLine,
40
+ "omit `model` to inherit. The <current_subagent_engine> section reflects the current state.",
41
+ ].join("\n");
42
+ }
43
+
44
+ /** 检测编排依赖(注入式)。生产装配在 index.ts engine handler;测试用 stub。 */
45
+ export interface EngineAwarenessDeps {
46
+ /** 三态读取全局 config(D5;生产 = () => readGlobalConfig(getAgentDir()))。 */
47
+ readConfig(): GlobalConfigReadResult;
48
+ /**
49
+ * D2:将本 turn 三态读取结果提交路由缓存(生产 = ModelConfigService.applyGlobalConfig,
50
+ * 纯赋值幂等;failed 已由编排层早退,到达此调用的必为 ok/absent)。
51
+ */
52
+ applyRead(read: GlobalConfigReadResult): void;
53
+ /** 对话流通知投递(D3;生产 = pi.sendMessage(message, {}),不设 triggerTurn)。 */
54
+ sendMessage(message: {
55
+ customType: string;
56
+ content: string;
57
+ display: boolean;
58
+ details?: unknown;
59
+ }): void;
60
+ /** per-session lastEngine 读取(undefined = 未基线化,D1b)。 */
61
+ getLastEngine(): string | undefined;
62
+ /** per-session lastEngine 写入(仅合法引擎 id,不写 undefined)。 */
63
+ setLastEngine(engine: string): void;
64
+ }
65
+
66
+ /** 单次 per-turn 检测的编排结果(测试断言面 + 诊断留痕消费)。 */
67
+ export type EngineAwarenessOutcome =
68
+ | { outcome: "changed"; from: string; to: string }
69
+ | { outcome: "unchanged"; engine: string }
70
+ | { outcome: "baseline"; engine: string }
71
+ | { outcome: "read-failed"; reason: string };
72
+
73
+ /**
74
+ * 单个 session 的 per-turn 引擎检测编排(§2.3 数据流 ①② 步;③ 渲染归 index.ts)。
75
+ *
76
+ * 分派(D5 三态 × D1b 基线):
77
+ * - read failed → 保持 lastEngine 不动、不提交缓存、不通知(防 torn write 瞬间伪通知);
78
+ * read-failure warn 日志由 readGlobalConfig 内部落,此处只返回结果。
79
+ * - lastEngine === undefined(session_start 初始化读失败的兜底形态)→ 静默基线化为
80
+ * 当前目标值:不算变更、不发通知(防「changed: undefined → zcode」首 turn 伪通知)。
81
+ * - last === target(absent 归一后同值也在内)→ 无事。
82
+ * - last ≠ target(含 ENOENT=合法缺省)→ 提交缓存先行 → 通知 → 记账,三步同 turn。
83
+ *
84
+ * 抛错面:不主动 try-catch——readGlobalConfig 三态返回不抛,applyRead/sendMessage 的
85
+ * 生产实现各自吞错;handler 顶层 catch 兜底(fail-safe 不阻塞 agent loop)。
86
+ */
87
+ export function runEngineAwarenessTurn(deps: EngineAwarenessDeps): EngineAwarenessOutcome {
88
+ const read = deps.readConfig();
89
+ if (read.status === "failed") {
90
+ return { outcome: "read-failed", reason: read.reason };
91
+ }
92
+ const target = normalizeEngineId(read.config.defaultEngine);
93
+ const last = deps.getLastEngine();
94
+ if (last === undefined) {
95
+ // D1b(修订):基线化前把本 turn 读取结果提交到单例缓存。baseline 形态可能来自
96
+ // session_start 读取失败(lastEngine 未设置 + Service 缓存回落缺省)而文件此后被
97
+ // 修好——若只记账不提交,缓存/路由/状态段永停旧值且永不通知(「改 config 不生效」
98
+ // 以更隐蔽形态复活)。apply 纯赋值——检测值与缓存值构造性同源,重复提交无害。
99
+ deps.applyRead(read);
100
+ deps.setLastEngine(target);
101
+ return { outcome: "baseline", engine: target };
102
+ }
103
+ if (last === target) {
104
+ return { outcome: "unchanged", engine: target };
105
+ }
106
+ // D2 顺序硬约束:先提交读取结果到路由缓存(本 turn 路由 + 渲染同源生效)→ 再通知 → 再记账。
107
+ deps.applyRead(read);
108
+ deps.sendMessage({
109
+ customType: ENGINE_CHANGE_CUSTOM_TYPE,
110
+ content: buildEngineChangeNotice(last, target),
111
+ display: true,
112
+ details: { from: last, to: target },
113
+ });
114
+ deps.setLastEngine(target);
115
+ logger.debug(
116
+ `[engine-awareness] default engine changed: ${last} -> ${target} (route reloaded, notice sent)`,
117
+ );
118
+ return { outcome: "changed", from: last, to: target };
119
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Model List Injector
2
+ * Model List Injector(C5① 渲染改接 core)
3
3
  *
4
4
  * 通过 before_agent_start 每 turn 注入 `<available_provider_models>` 段,列出
5
5
  * 当前 auth 可用的模型(provider/modelId + 能力 + contextWindow),与
@@ -19,6 +19,11 @@
19
19
  * injector 的码点序契约对齐)。数据真实变化(用户中途配置了新 provider)
20
20
  * 时下一 turn 自然反映。
21
21
  *
22
+ * C5①(convergence D-3):formatModelList + ModelEntry 口径下沉 core 并改调
23
+ * barrel——pi 投影全字段必给(provider/reasoning:boolean/input[]/contextWindow),
24
+ * core 的 (provider,id) 码点序 + provider/id 拼接在 pi 投影下与本地旧实现逐字节
25
+ * 一致(CA2 快照验收前提);guide 文案是 pi 宿主注入(core 不内嵌平台文案)。
26
+ *
22
27
  * 立场:本注入段只服务「派发时选模型」,明确告知模型不要在会话中切换主模型
23
28
  * (KV cache 不友好);用户明确要求换模型时走 pi 原生 /model 命令(人手动触发)。
24
29
  */
@@ -35,19 +40,17 @@ import type {
35
40
  } from "@earendil-works/pi-coding-agent";
36
41
  import { getLogger } from "@zhushanwen/pi-extension-logger";
37
42
 
38
- import { escapeXml, renderXmlSection } from "../shared/xml-injection.ts";
43
+ // C5①/C5⑦:渲染统一走 core barrel(formatModelList + ModelEntry 类型为 barrel 导出面)
44
+ import { formatModelList, type ModelEntry } from "@zhushanwen/subagent-core";
39
45
 
40
46
  const logger = getLogger("injector");
41
47
 
42
- /** 注入段的最小模型投影(从 Model<Api> 收窄,测试无需构造完整 Model) */
43
- export interface ModelEntry {
44
- provider: string;
45
- id: string;
46
- name: string;
47
- reasoning: boolean;
48
- input: string[];
49
- contextWindow: number;
50
- }
48
+ /**
49
+ * pi 版注入引导文案(C5① guide 参数化——core 渲染函数不内嵌平台文案,宿主注入;
50
+ * 文案与改造前本地实现逐字一致——models 段 guide 无过期问题)。
51
+ */
52
+ export const MODEL_LIST_GUIDE =
53
+ "The following models are available (auth-configured). Use these ids when delegating via the subagent/workflow `model` param (\"provider/modelId\" format) to match the task (e.g. vision models for screenshots, strong reasoners for architecture). Do NOT switch the main conversation model mid-session — per-call model override on delegates only (switching the main model is cache-hostile); use the /model command only when the user explicitly asks to change it.";
51
54
 
52
55
  /** Model<Api> → ModelEntry 投影(只留注入段消费的字段;模块内唯一消费方 setupModelListInjector) */
53
56
  function toModelEntry(model: Model<Api>): ModelEntry {
@@ -61,53 +64,6 @@ function toModelEntry(model: Model<Api>): ModelEntry {
61
64
  };
62
65
  }
63
66
 
64
- /** 码点序比较(显式契约,禁 localeCompare——宿主 locale 差异会破坏跨环境字节一致)。 */
65
- function compareByCodepoint(a: string, b: string): number {
66
- return a < b ? -1 : a > b ? 1 : 0;
67
- }
68
-
69
- /** 能力标记:reasoning → "reasoning",input 含 image → "vision"(空则省略 caps 段) */
70
- function formatCaps(entry: ModelEntry): string {
71
- const caps: string[] = [];
72
- if (entry.reasoning) caps.push("reasoning");
73
- if (entry.input.includes("image")) caps.push("vision");
74
- return caps.join(",");
75
- }
76
-
77
- /**
78
- * 将模型列表格式化为 XML 注入段。
79
- *
80
- * 输入按 (provider, id) 码点序排序——registry 返回顺序不作保证,排序后同一
81
- * 数据集输出字节稳定。码点序是显式契约(禁 localeCompare——宿主 locale 差异
82
- * 会破坏跨环境字节一致,见 subagent-list-injector.ts sortByCodepoint 注释),
83
- * 保证注入段进每 turn system prompt 时跨环境逐字节可复现(cache-probe 前缀
84
- * 指纹归因 / 换机器 resume 场景依赖此性质)。空列表返回空串(不注入)。
85
- */
86
- export function formatModelList(models: ModelEntry[]): string {
87
- if (models.length === 0) return "";
88
-
89
- const sorted = [...models].sort((a, b) =>
90
- a.provider === b.provider
91
- ? compareByCodepoint(a.id, b.id)
92
- : compareByCodepoint(a.provider, b.provider),
93
- );
94
-
95
- const items = sorted.map((m) => {
96
- const caps = formatCaps(m);
97
- return (
98
- ` <model><id>${escapeXml(`${m.provider}/${m.id}`)}</id>`
99
- + `<name>${escapeXml(m.name)}</name>`
100
- + (caps ? `<caps>${caps}</caps>` : "")
101
- + `<contextWindow>${m.contextWindow}</contextWindow></model>`
102
- );
103
- });
104
- return renderXmlSection({
105
- tag: "available_provider_models",
106
- guide: "The following models are available (auth-configured). Use these ids when delegating via the subagent/workflow `model` param (\"provider/modelId\" format) to match the task (e.g. vision models for screenshots, strong reasoners for architecture). Do NOT switch the main conversation model mid-session — per-call model override on delegates only (switching the main model is cache-hostile); use the /model command only when the user explicitly asks to change it.",
107
- items,
108
- });
109
- }
110
-
111
67
  /**
112
68
  * 注册 before_agent_start handler,注入 `<available_provider_models>` 段。
113
69
  *
@@ -125,6 +81,7 @@ export function setupModelListInjector(pi: ExtensionAPI): void {
125
81
  try {
126
82
  const injection = formatModelList(
127
83
  ctx.modelRegistry.getAvailable().map(toModelEntry),
84
+ { guide: MODEL_LIST_GUIDE },
128
85
  );
129
86
  if (!injection) return;
130
87
  return { systemPrompt: event.systemPrompt + injection };