@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
@@ -1,811 +0,0 @@
1
- // src/shared/resource-discovery.ts
2
- //
3
- // 统一资源发现模块——agent .md 与 workflow .js/.mjs 共享同一套扫描逻辑。
4
- //
5
- // 设计原则(ADR-031 统一资源发现):
6
- // 1. 扫描源前缀统一:user/project 级目录用相同前缀,末级目录名(agents/workflows)参数化
7
- // 2. 路径动态获取:user 级用 getAgentDir()(尊重 PI_CODING_AGENT_DIR),project 级用 findWorkspaceRoot(cwd)
8
- // 3. npm/dev 包内发现:有 manifest(pi.agents/pi.workflows)只走 manifest,无 manifest 扫约定目录
9
- // 4. manifest 路径存在性校验:声明的路径不存在 → 该包发现失败,不 fallback
10
- // 5. 废弃 discovery.json:扫描路径完全由代码内推导,无外部依赖
11
- //
12
- // 优先级(低→高):user .pi/agent → user .agents → npm global → npm dev → project .pi → project .pi/.tmp(仅workflow) → project .agents
13
-
14
- import * as fsSync from "node:fs";
15
- import { access, readdir, readFile, stat } from "node:fs/promises";
16
- import { homedir } from "node:os";
17
- import { delimiter, join, resolve } from "node:path";
18
-
19
- import { getLogger } from "@zhushanwen/pi-extension-logger";
20
-
21
- // 模块级 logger(setPiHandle 注入后自动走 appendEntry,未注入时 console 兜底)
22
- const logger = getLogger("subagents");
23
-
24
- // [D8d] warn 路径进程内去重集合:key=(kind, stem, shadowedPath, keptPath),
25
- // cap 1024 超限先清空再 add(对齐 ui-request-observability 的 MAX_WARNED_SESSIONS 范式)。
26
- // debug 路径不去重(默认 no-op,无成本)。
27
- const shadowWarnDedup = new Set<string>();
28
- const MAX_SHADOW_WARN_DEDUP = 1024;
29
-
30
- /** @internal 测试辅助:重置 warn 去重集合(cap 测试用,生产代码不调用)。 */
31
- export function __testResetShadowDedup(): void {
32
- shadowWarnDedup.clear();
33
- }
34
-
35
- /** @internal 测试辅助:向 warn 去重集合注入 key(cap 测试用)。 */
36
- export function __testInjectShadowDedupKey(key: string): void {
37
- shadowWarnDedup.add(key);
38
- }
39
-
40
- // ── 类型 ─────────────────────────────────────────────────────
41
-
42
- /** 资源种类:agent 或 workflow */
43
- export type ResourceKind = "agents" | "workflows";
44
-
45
- /** 发现到的单个资源文件(原始数据,由调用方解析 frontmatter/meta) */
46
- export interface DiscoveredResource {
47
- /** 绝对路径 */
48
- path: string;
49
- /** 来源层级 */
50
- source: ResourceSource;
51
- /** 是否可用(manifest 校验失败的包整体标 false) */
52
- available: boolean;
53
- }
54
-
55
- /** 资源来源层级 */
56
- export type ResourceSource = "user-pi" | "user-agents" | "npm" | "npm-dev" | "user-extension-paths" | "project-pi" | "project-pi-tmp" | "project-agents";
57
-
58
- /** 扫描配置 */
59
- export interface ScanConfig {
60
- /** 资源种类 */
61
- kind: ResourceKind;
62
- /** 项目根目录(findWorkspaceRoot 推导结果) */
63
- workspaceRoot: string;
64
- /** agent 配置目录(getAgentDir() 结果) */
65
- agentDir: string;
66
- /** 是否包含 tmp 源(仅 workflow 用 .pi/workflows/.tmp/) */
67
- includeTmp?: boolean;
68
- }
69
-
70
- // ── 常量 ─────────────────────────────────────────────────────
71
-
72
- /** 机器源集合:包管理/工程配置产物,其同名重复是安装拓扑常态(非用户配置错误)。
73
- * 用户个人源(user-pi / user-agents)不在此列——双个人源同名重复保留 warn。 */
74
- const MACHINE_SOURCES: ReadonlySet<ResourceSource> = new Set<ResourceSource>([
75
- "npm",
76
- "npm-dev",
77
- "user-extension-paths",
78
- "project-pi",
79
- "project-pi-tmp",
80
- "project-agents",
81
- ]);
82
-
83
- /** 判断 source 是否属于机器源(安装拓扑常态,同名重复降 debug)。
84
- * 导出仅为测试穷举断言用(封闭 8 值枚举 × 分级边界)。 */
85
- export function isMachineSource(source: ResourceSource): boolean {
86
- return MACHINE_SOURCES.has(source);
87
- }
88
-
89
- /** workspace root 向上查找的最大深度 */
90
- const WORKSPACE_ROOT_MAX_DEPTH = 20;
91
-
92
- // ── workspace root 推导(从 config-loader 提取,agent/workflow 共用) ──
93
-
94
- /**
95
- * 判断 dir 是否是 workspaceRoot 的直接子目录(一层深度)。
96
- * 用于 bare+worktree 结构里识别 worktree 根。
97
- */
98
- function isDirectChildOfWorkspaceRoot(dir: string, workspaceRoot: string): boolean {
99
- return resolve(dir, "..") === workspaceRoot;
100
- }
101
-
102
- /**
103
- * 从 cwd 向上查找 workspace root。
104
- *
105
- * bare+worktree 优先找 .bare;普通 repo 找最顶层 .git;fallback 找 .pi。
106
- * 与 config-loader 原有逻辑一致(合并后提取为共享函数)。
107
- */
108
- // ── 统一 mtime 缓存层(m5 IF10)────────────────────────────────────
109
- //
110
- // 模块级 Map<path, { mtimeMs, content }>:mtime 判变缓存文件内容。
111
- // - sync 实现(statSync/readFileSync):agent-registry discoverAll 是同步路径,
112
- // async 缓存无法被 await(m5 design-review A1 探针实证约束)
113
- // - stat 失败/ENOENT → 驱逐条目(mtime 缓存下文件删除不自愈——A3 修复)
114
- // - 已知局限(C3 记录):内容变 mtime 未变(cp -p/rsync -t 保留源 mtime、
115
- // 2s 粒度文件系统)→ 漏判,invalidateCache/clearFileCache 兜底;
116
- // APFS mtimeMs 微秒级浮点 === 判变可靠(探针 P-mtime-精度)
117
-
118
- interface MtimeCacheEntry {
119
- mtimeMs: number;
120
- content: string;
121
- }
122
-
123
- const mtimeCache = new Map<string, MtimeCacheEntry>();
124
-
125
- // [perf] findWorkspaceRoot 结果按 cwd memo——每次调用最多 3 phase × 20 层 existsSync
126
- //(30-60 次 stat),调用点(session_start、discoverWorkflows、getWorkflow)在 spawn/
127
- // tool 热路径上重复付。cwd 不变则祖先目录结构(.bare/.git/.pi)不变;变化场景由
128
- // clearFileCache 兜底(invalidateCache 语义,测试隔离也走这里)。
129
- const workspaceRootCache = new Map<string, string>();
130
-
131
- // [perf] npm/dev 包 package.json manifest 缓存(swf-perf-impl cleanup TC1/IF1)。
132
- // key = package.json 绝对路径;缓存 parse 后的整个 pi 对象(agents/workflows 两 kind
133
- // 共享一次 parse,不按 kind 分裂条目)。失效 = mtimeMs 严格相等判定(与 mtimeCache 同构,
134
- // 『内容变 mtime 未变』漏判局限共享 C3 声明,clearFileCache 兜底)。
135
- // 失败语义(消歧,三方 TC1/DM1/IF1 同一口径):
136
- // - stat 失败(文件不存在/不可 stat)→ 驱逐该 path 条目 + undefined(对齐 getCachedFile:
137
- // 文件已删条目无意义)
138
- // - read 失败(stat 与 read 间竞态删除/EACCES)或 JSON.parse 失败(坏 JSON)→
139
- // 不缓存(不写新条目、不驱逐已有好条目)+ undefined,下次调用重试——毒条目永不入缓存,
140
- // 坏文件被修复且 mtime 变化后正常覆写。
141
- // pi === undefined 条目仅在『read+parse 成功且 manifest 无 pi 字段』时写入(合法解析结果)。
142
- interface ManifestCacheEntry {
143
- mtimeMs: number;
144
- pi: Record<string, unknown> | undefined;
145
- }
146
-
147
- const manifestCache = new Map<string, ManifestCacheEntry>();
148
-
149
- /** 从缓存的 pi 对象派生 kind manifest(廉价操作,每次调用新数组——语义与旧直读版一致)。 */
150
- function piToManifest(pi: Record<string, unknown> | undefined, kind: ResourceKind): string[] | undefined {
151
- if (!pi) return undefined;
152
- const entries = pi[kind];
153
- if (!Array.isArray(entries)) return undefined;
154
- // 过滤非字符串元素
155
- return entries.filter((p): p is string => typeof p === "string");
156
- }
157
-
158
- /**
159
- * package.json 文本 → pi 字段对象(pi.agents / pi.workflows 的容器)。
160
- * [review 修复] 结构守卫:JSON.parse 对 "42" / '"str"' / "null" 等合法 JSON 产出
161
- * 非对象值,旧 `as Record<string, unknown>` 盲断言下 .pi 访问是「碰巧不抛」
162
- * (primitive 装箱返 undefined / null 抛 TypeError 落入 catch)——显式判非对象,
163
- * 按「无 manifest」(undefined)处理,后果与原先一致但语义不再依赖巧合。
164
- * pi 字段本身非对象(如 {"pi":42})或为数组(如 {"pi":[]}——数组不是合法 pi 容器,
165
- * typeof "object" 守卫会放行,Record 断言对数组是谎言)同样显式归 undefined。
166
- * JSON SyntaxError 向上抛,由调用方 catch 承担「坏 JSON 不缓存、下次重试」语义。
167
- */
168
- function parsePiField(content: string): Record<string, unknown> | undefined {
169
- const parsed: unknown = JSON.parse(content);
170
- if (typeof parsed !== "object" || parsed === null) return undefined;
171
- const pi: unknown = (parsed as Record<string, unknown>).pi;
172
- return typeof pi === "object" && pi !== null && !Array.isArray(pi)
173
- ? (pi as Record<string, unknown>)
174
- : undefined;
175
- }
176
-
177
- /**
178
- * stat + content 统一缓存。文件不存在/不可读 → null(并驱逐条目)。
179
- * mtime 未变 → 返回缓存 content(不重 read);变 → readFileSync + 缓存。
180
- */
181
- export function getCachedFile(filePath: string): { mtimeMs: number; content: string } | null {
182
- let mtimeMs: number;
183
- try {
184
- mtimeMs = fsSync.statSync(filePath).mtimeMs;
185
- } catch {
186
- mtimeCache.delete(filePath);
187
- return null;
188
- }
189
- const entry = mtimeCache.get(filePath);
190
- if (entry && entry.mtimeMs === mtimeMs) return entry;
191
- let content: string;
192
- try {
193
- content = fsSync.readFileSync(filePath, "utf-8");
194
- } catch {
195
- // stat 与 read 之间的删除/EACCES 竞态 → 驱逐并返回 null(exec-review major-2:
196
- // docstring 承诺「不可读 → null」——readFileSync 也必须入守卫)
197
- mtimeCache.delete(filePath);
198
- return null;
199
- }
200
- const cached = { mtimeMs, content };
201
- mtimeCache.set(filePath, cached);
202
- return cached;
203
- }
204
-
205
- /** 便捷封装:只取 content(不存在 → null)。 */
206
- export function getCachedFileContent(filePath: string): string | null {
207
- return getCachedFile(filePath)?.content ?? null;
208
- }
209
-
210
- // [perf] 解析结果缓存(KV-cache 稳定性改造):外层 key = parse 函数身份,内层 key =
211
- // path,value = { mtimeMs, parsed }。key 含 parse 身份是正确性要求——同一 path 可能被
212
- // 不同 parse(agent frontmatter vs workflow meta)解析,单层 path key 会跨 parse 类型
213
- // 互相污染缓存(先 parse 的结果被 as T 断言返回)。用普通 Map 而非 WeakMap:
214
- // clearFileCache 需全量清空(测试隔离),WeakMap 不可遍历;parse 函数均为模块级
215
- // 常量,强引用无泄漏。复用 getCachedFile 的 mtime 判变——mtime 未变时跳过 parse
216
- // (frontmatter YAML 解析是重建发现时最大的可省 CPU 项)。parse 的确定性结果(含
217
- // null,如 frontmatter 非法)均可缓存:同一 content 必然解析出同一结果。失效与
218
- // mtimeCache 同步(clearFileCache)。
219
- const parsedCache = new Map<
220
- (content: string) => unknown,
221
- Map<string, { mtimeMs: number; parsed: unknown }>
222
- >();
223
-
224
- /**
225
- * mtime 级解析结果缓存:mtime 未变返回缓存 parsed,变则经 getCachedFile 取 content
226
- * 重新 parse 并缓存。文件不存在/不可读 → null(并驱逐条目)。缓存按 parse 函数隔离
227
- * ——同一 path 的不同 parse 互不污染。
228
- */
229
- export function getCachedParsed<T>(filePath: string, parse: (content: string) => T): T | null {
230
- const file = getCachedFile(filePath);
231
- let perParse = parsedCache.get(parse);
232
- if (!file) {
233
- perParse?.delete(filePath);
234
- return null;
235
- }
236
- if (!perParse) {
237
- perParse = new Map();
238
- parsedCache.set(parse, perParse);
239
- }
240
- const entry = perParse.get(filePath);
241
- if (entry && entry.mtimeMs === file.mtimeMs) return entry.parsed as T;
242
- const parsed = parse(file.content);
243
- perParse.set(filePath, { mtimeMs: file.mtimeMs, parsed });
244
- return parsed;
245
- }
246
-
247
- /** 清空(invalidateCache 语义——测试隔离 + mtime 漏判场景手动刷新兜底)。 */
248
- export function clearFileCache(): void {
249
- mtimeCache.clear();
250
- workspaceRootCache.clear();
251
- manifestCache.clear();
252
- for (const perParse of parsedCache.values()) perParse.clear();
253
- }
254
-
255
- export function findWorkspaceRoot(cwd?: string): string {
256
- const dir = cwd ?? process.cwd();
257
- const memo = workspaceRootCache.get(dir);
258
- if (memo !== undefined) return memo;
259
- const root = computeWorkspaceRoot(dir);
260
- workspaceRootCache.set(dir, root);
261
- return root;
262
- }
263
-
264
- function computeWorkspaceRoot(dir: string): string {
265
- const root = resolve("/");
266
-
267
- // Phase 1: bare repo 优先——先全路径扫一遍找 .bare
268
- let probe = dir;
269
- for (let i = 0; i < WORKSPACE_ROOT_MAX_DEPTH; i++) {
270
- if (fsSync.existsSync(resolve(probe, ".bare"))) {
271
- // worktree 是 workspace 根的直接子目录。若 cwd 自身有 .pi/,优先用 cwd
272
- if (probe !== dir && isDirectChildOfWorkspaceRoot(dir, probe) && fsSync.existsSync(resolve(dir, ".pi"))) {
273
- return dir;
274
- }
275
- return probe;
276
- }
277
- if (probe === root) break;
278
- probe = resolve(probe, "..");
279
- }
280
-
281
- // Phase 2: 无 .bare 时,找最顶层的 .git
282
- let topLevel = dir;
283
- probe = dir;
284
- for (let i = 0; i < WORKSPACE_ROOT_MAX_DEPTH; i++) {
285
- if (fsSync.existsSync(resolve(probe, ".git"))) {
286
- topLevel = probe;
287
- }
288
- if (probe === root) break;
289
- probe = resolve(probe, "..");
290
- }
291
- if (topLevel !== dir) {
292
- return topLevel;
293
- }
294
-
295
- // Phase 3: fallback——用第一个遇到的 .pi
296
- probe = dir;
297
- for (let i = 0; i < WORKSPACE_ROOT_MAX_DEPTH; i++) {
298
- if (fsSync.existsSync(resolve(probe, ".pi"))) {
299
- return probe;
300
- }
301
- if (probe === root) break;
302
- probe = resolve(probe, "..");
303
- }
304
-
305
- return dir;
306
- }
307
-
308
- // ── 文件扩展名判定 ───────────────────────────────────────────
309
-
310
- /** 根据资源种类判定脚本文件扩展名 */
311
- function isTargetFile(name: string, kind: ResourceKind): boolean {
312
- // _ 前缀 = draft/示例,不参与发现(与原 agent-registry/workflow 约定一致)
313
- if (name.startsWith("_")) return false;
314
- if (kind === "agents") {
315
- return name.endsWith(".md") && !name.endsWith(".chain.md");
316
- }
317
- // workflows
318
- return name.endsWith(".js") || name.endsWith(".mjs");
319
- }
320
-
321
- /** 提取文件名 stem(去目录去扩展名) */
322
- function stem(filePath: string): string {
323
- const base = filePath.split("/").pop() ?? filePath;
324
- const dot = base.lastIndexOf(".");
325
- return dot > 0 ? base.slice(0, dot) : base;
326
- }
327
-
328
- // ── 目录扫描 ─────────────────────────────────────────────────
329
-
330
- /**
331
- * 扫描单个目录下的资源文件。
332
- * 返回文件绝对路径列表。目录不存在时返回空数组。
333
- */
334
- async function scanDirectory(dirPath: string, kind: ResourceKind): Promise<string[]> {
335
- try {
336
- await access(dirPath);
337
- } catch {
338
- return [];
339
- }
340
-
341
- const entries = await readdir(dirPath, { withFileTypes: true });
342
- const files: string[] = [];
343
- for (const e of entries) {
344
- if (!isTargetFile(e.name, kind)) continue;
345
- const absPath = resolve(dirPath, e.name);
346
- // symlink 单独处理:Dirent.isFile() 对 symlink 返回 false
347
- if (e.isFile()) {
348
- files.push(absPath);
349
- } else if (e.isSymbolicLink()) {
350
- const targetStat = await stat(absPath).catch(() => null);
351
- if (targetStat?.isFile()) files.push(absPath);
352
- }
353
- }
354
- return files;
355
- }
356
-
357
- // ── npm/dev 包内 manifest 发现 ───────────────────────────────
358
-
359
- /**
360
- * 读取 package.json 的 pi.{kind} manifest(pi.agents / pi.workflows)。
361
- * 返回 undefined 表示无 manifest 声明(无 pi / pi[kind] 非数组 / 解析失败)。
362
- * 内部走 manifestCache(与 readPackageManifestSync 共享同一 Map,失败语义见声明处)。
363
- */
364
- async function readPackageManifest(pkgDir: string, kind: ResourceKind): Promise<string[] | undefined> {
365
- const pkgJsonPath = resolve(pkgDir, "package.json");
366
- let mtimeMs: number;
367
- try {
368
- mtimeMs = (await stat(pkgJsonPath)).mtimeMs;
369
- } catch {
370
- // stat 失败:文件不存在/不可 stat → 驱逐条目(对齐 getCachedFile 语义)
371
- manifestCache.delete(pkgJsonPath);
372
- return undefined;
373
- }
374
- const entry = manifestCache.get(pkgJsonPath);
375
- if (entry && entry.mtimeMs === mtimeMs) return piToManifest(entry.pi, kind);
376
- let pi: Record<string, unknown> | undefined;
377
- try {
378
- const content = await readFile(pkgJsonPath, "utf-8");
379
- pi = parsePiField(content);
380
- } catch {
381
- // read 失败或坏 JSON → 不缓存不驱逐已有好条目,下次调用重试
382
- return undefined;
383
- }
384
- manifestCache.set(pkgJsonPath, { mtimeMs, pi });
385
- return piToManifest(pi, kind);
386
- }
387
-
388
- /**
389
- * 处理单个 npm/dev 包:按 manifest 或约定目录发现资源。
390
- *
391
- * 规则:
392
- * - 有 manifest → 只按 manifest 声明路径加载。路径不存在 → 整包失败(返回 available=false 占位)
393
- * - 无 manifest → 扫约定目录 {kind}/(agents/ 或 workflows/)
394
- */
395
- async function processPackage(
396
- pkgDir: string,
397
- kind: ResourceKind,
398
- ): Promise<DiscoveredResource[]> {
399
- const manifestPaths = await readPackageManifest(pkgDir, kind);
400
-
401
- // manifest 模式:只按声明路径加载,路径不存在则整包失败
402
- if (manifestPaths && manifestPaths.length > 0) {
403
- const results: DiscoveredResource[] = [];
404
- let allFailed = true;
405
-
406
- for (const relPath of manifestPaths) {
407
- const absPath = resolve(pkgDir, relPath);
408
- const fileStat = await stat(absPath).catch(() => null);
409
- if (!fileStat) {
410
- // manifest 声明的路径不存在 → 记录失败占位(路径存在性校验)
411
- results.push({ path: absPath, source: "npm", available: false });
412
- continue;
413
- }
414
-
415
- if (fileStat.isDirectory()) {
416
- const files = await scanDirectory(absPath, kind);
417
- for (const f of files) {
418
- results.push({ path: f, source: "npm", available: true });
419
- allFailed = false;
420
- }
421
- } else if (fileStat.isFile()) {
422
- results.push({ path: absPath, source: "npm", available: true });
423
- allFailed = false;
424
- }
425
- }
426
-
427
- // manifest 全失败:返回 available=false 占位,不 fallback 到约定目录
428
- if (allFailed) {
429
- return results;
430
- }
431
- return results;
432
- }
433
-
434
- // 无 manifest:扫约定目录 {kind}/
435
- const conventionDir = resolve(pkgDir, kind);
436
- const files = await scanDirectory(conventionDir, kind);
437
- return files.map((f) => ({ path: f, source: "npm", available: true }));
438
- }
439
-
440
- /**
441
- * 扫描 npm node_modules 目录下所有包的资源。
442
- * 支持 scoped(@scope/pkg)和 unscoped(pkg)包。
443
- */
444
- async function scanNpmDir(
445
- nodeModulesDir: string,
446
- kind: ResourceKind,
447
- ): Promise<DiscoveredResource[]> {
448
- let entries: string[];
449
- try {
450
- entries = await readdir(nodeModulesDir);
451
- } catch {
452
- return [];
453
- }
454
-
455
- // [perf] 包级并行(swf-perf-impl cleanup TC2/IF2):entries.map + Promise.all,
456
- // perEntry.flat() 按原 readdir 序 concat——输出序与串行逐包 push 等价。
457
- // 不加新增 catch:每包失败面由 processPackage 内部既有 catch 承担,未捕获异常
458
- // 传播语义与串行版一致(Promise.all 整体 reject ↔ 串行版向上抛)。
459
- const perEntry = await Promise.all(
460
- entries.map(async (entry): Promise<DiscoveredResource[]> => {
461
- const entryPath = resolve(nodeModulesDir, entry);
462
-
463
- if (entry.startsWith("@")) {
464
- // scoped 包——迭代子包(子包级同样并行,flat 保序)
465
- let scopedEntries: string[];
466
- try {
467
- scopedEntries = await readdir(entryPath);
468
- } catch {
469
- return [];
470
- }
471
- const perScoped = await Promise.all(
472
- scopedEntries.map((scopedPkg) => processPackage(resolve(entryPath, scopedPkg), kind)),
473
- );
474
- return perScoped.flat();
475
- }
476
- // unscoped 包
477
- return processPackage(entryPath, kind);
478
- }),
479
- );
480
-
481
- return perEntry.flat();
482
- }
483
-
484
- // ── 扫描源构建 ───────────────────────────────────────────────
485
-
486
- /** 扫描源定义:路径 + source 标签 */
487
- interface ScanTarget {
488
- dir: string;
489
- source: ResourceSource;
490
- /** 该源是否参与本次扫描(如 tmp 仅 workflow 启用) */
491
- enabled: boolean;
492
- }
493
-
494
- /**
495
- * 读取 XYZ_EXTENSION_PATHS 环境变量(dev-link 写入的扩展源码路径)。
496
- *
497
- * delimiter 分隔(POSIX ':' / Windows ';'),trim + 过滤空 + ~ 展开。
498
- * 每个路径是一个 extension 包目录(dev-link 指向源码),走 processPackage 发现其
499
- * agents/workflows。解析逻辑与 extension-service.getUserExtensionPaths() 一致。
500
- */
501
- function readExtensionPaths(): string[] {
502
- const raw = process.env.XYZ_EXTENSION_PATHS;
503
- if (!raw) return [];
504
- const paths = raw
505
- .split(delimiter)
506
- .map((p) => p.trim())
507
- .filter((p) => p.length > 0)
508
- .map((p) => (p.startsWith("~") ? join(homedir(), p.slice(1)) : p));
509
- return [...new Set(paths)];
510
- }
511
-
512
- /**
513
- * 构建所有扫描源(按优先级低→高排列)。
514
- *
515
- * agent 和 workflow 共享相同的前缀体系,末级目录名由 kind 决定。
516
- */
517
- function buildScanTargets(config: ScanConfig): ScanTarget[] {
518
- const { kind, workspaceRoot, agentDir, includeTmp } = config;
519
- const home = homedir();
520
-
521
- const targets: ScanTarget[] = [
522
- // 1. user .pi/agent/{kind}/
523
- { dir: join(agentDir, kind), source: "user-pi", enabled: true },
524
- // 2. user .agents/{kind}/
525
- { dir: join(home, ".agents", kind), source: "user-agents", enabled: true },
526
- // 3. npm global: agentDir/npm/node_modules/*/<pkg>/
527
- { dir: join(agentDir, "npm", "node_modules"), source: "npm", enabled: true },
528
- // 4. npm dev symlink: agentDir/extensions/*/<pkg>/
529
- { dir: join(agentDir, "extensions"), source: "npm-dev", enabled: true },
530
- // user extension paths (XYZ_EXTENSION_PATHS, dev-link): each path is a package dir,
531
- // 走 processPackage 读 pi.{kind} manifest 或扫 {kind}/ 目录。优先级高于 npm/npm-dev
532
- // (dev-link 是开发版 override),低于 project(项目正式资源优先)。
533
- ...readExtensionPaths().map((dir) => ({ dir, source: "user-extension-paths" as const, enabled: true })),
534
- // 5. project .pi/{kind}/
535
- { dir: join(workspaceRoot, ".pi", kind), source: "project-pi", enabled: true },
536
- ];
537
-
538
- // 6. project .pi/{kind}/.tmp/(仅 workflow)
539
- if (includeTmp) {
540
- targets.push({
541
- dir: join(workspaceRoot, ".pi", kind, ".tmp"),
542
- source: "project-pi-tmp",
543
- enabled: true,
544
- });
545
- }
546
-
547
- // 7. project .agents/{kind}/
548
- targets.push({
549
- dir: join(workspaceRoot, ".agents", kind),
550
- source: "project-agents",
551
- enabled: true,
552
- });
553
-
554
- return targets.filter((t) => t.enabled);
555
- }
556
-
557
- // ── 公共 API ─────────────────────────────────────────────────
558
-
559
- /**
560
- * 发现所有资源文件(agent .md 或 workflow .js/.mjs)。
561
- *
562
- * 按优先级低→高扫描所有源,同名资源靠后覆盖靠前(last-writer-wins)。
563
- * npm/dev 包内:有 manifest 只走 manifest(路径不存在则失败),无 manifest 扫约定目录。
564
- *
565
- * Throws on unrecoverable scan errors——未捕获异常向上抛(Promise.all 首个 reject
566
- * 即整体拒绝,与串行版 discoverResourcesSync 的传播语义一致,见实现内 [perf] 注释)。
567
- * 预期失败不抛:目录不存在/不可读返回空列表,manifest 声明路径缺失以 available=false 返回。
568
- *
569
- * @returns 去重后的资源列表(按优先级合并,高优先级覆盖低优先级同名)
570
- */
571
- export async function discoverResources(config: ScanConfig): Promise<DiscoveredResource[]> {
572
- const targets = buildScanTargets(config);
573
-
574
- // [perf] 源级并行(swf-perf-impl cleanup TC2/IF2):targets.map + Promise.all,
575
- // Promise.all 对 map 数组保序——allBySource 顺序 = targets 优先级序(低→高),
576
- // 与串行逐源 push 完全一致 → 下方合并去重逻辑零改动、输出逐字节等价。
577
- // 不加新增 catch:每源预期失败路径由内部既有 catch 面承担(scanDirectory access
578
- // catch / scanNpmDir readdir catch / processPackage 全链 catch),未捕获异常
579
- // 传播语义与串行版等价(ES2:串行版 target k 抛错中断后续源,并行版全部源已并发
580
- // 启动、以首个 reject 拒绝——两版对调用方同为 discoverResources 抛出,各源只读无副作用)。
581
- const allBySource: Array<{ source: ResourceSource; resources: DiscoveredResource[] }> = await Promise.all(
582
- targets.map(async (target) => {
583
- if (target.source === "npm" || target.source === "npm-dev") {
584
- // npm/dev 目录:迭代包,走 manifest 或约定目录
585
- const resources = await scanNpmDir(target.dir, config.kind);
586
- // 覆盖 source 标签(scanNpmDir 内部统一标 "npm",这里修正为实际源)
587
- const tagged = resources.map((r) => ({ ...r, source: target.source }));
588
- return { source: target.source, resources: tagged };
589
- }
590
- if (target.source === "user-extension-paths") {
591
- // XYZ_EXTENSION_PATHS(dev-link):每个 dir 是单个包目录,走 processPackage
592
- const resources = await processPackage(target.dir, config.kind);
593
- const tagged = resources.map((r) => ({ ...r, source: target.source }));
594
- return { source: target.source, resources: tagged };
595
- }
596
- // 普通目录:直接扫
597
- const files = await scanDirectory(target.dir, config.kind);
598
- const resources = files.map((f) => ({ path: f, source: target.source, available: true }));
599
- return { source: target.source, resources };
600
- }),
601
- );
602
-
603
- // 按优先级合并:targets 数组顺序即优先级(低→高),高优先级后写覆盖
604
- // 用文件名 stem 作为去重 key(与旧逻辑一致:同名资源高优先级覆盖)
605
- const merged = new Map<string, DiscoveredResource>();
606
-
607
- for (const { resources } of allBySource) {
608
- for (const r of resources) {
609
- const key = stem(r.path);
610
- const existing = merged.get(key);
611
- // available=false 的占位不覆盖已有的 available=true
612
- if (!r.available && existing) {
613
- continue;
614
- }
615
- // [D8d] 同名遮蔽可观测:高优先级源覆盖低优先级同名资源时分级报告——
616
- // 机器源重复是安装拓扑常态(npm 包与用户目录结构性同名),降 debug 默认静默
617
- // (XYZ_AGENT_DEBUG=1 文件日志可查);双用户源重复是配置错误,保留 warn 首报。
618
- // warn 路径进程内去重(Set cap 1024,对齐 ui-request-observability 范式):
619
- // 每 session 独立进程(process-manager.ts L142-143),进程级去重 ≈ session 级首报。
620
- if (existing && existing.path !== r.path) {
621
- const msg =
622
- `[resource-discovery] duplicate ${config.kind} "${key}" from ${r.source} shadows ${existing.source}`;
623
- const data = { shadowed: existing.path, kept: r.path };
624
- if (isMachineSource(existing.source) || isMachineSource(r.source)) {
625
- // 任一侧为机器源 → 降级 debug(安装拓扑常态,排查走 XYZ_AGENT_DEBUG=1)
626
- logger.debug(msg, data);
627
- } else {
628
- // 双侧均为用户源 → 保持 warn,进程内去重(同 key 只报首次)
629
- const dedupKey = `${config.kind}|${key}|${existing.path}|${r.path}`;
630
- if (!shadowWarnDedup.has(dedupKey)) {
631
- if (shadowWarnDedup.size >= MAX_SHADOW_WARN_DEDUP) {
632
- shadowWarnDedup.clear();
633
- }
634
- shadowWarnDedup.add(dedupKey);
635
- logger.warn(msg, data);
636
- }
637
- }
638
- }
639
- merged.set(key, r);
640
- }
641
- }
642
-
643
- return Array.from(merged.values());
644
- }
645
-
646
- /**
647
- * 同步版:扫描单个目录下的资源文件路径(供 agent-registry 的 mtime 缓存模式使用)。
648
- *
649
- * agent .md 发现需要 mtime 缓存(hot-reload),不能走 async 全量扫描。
650
- * 此函数提供目录级同步扫描,npm/dev 包内发现仍需 async(agent-registry 用 builtin 兜底)。
651
- */
652
- export function scanDirectorySync(dirPath: string, kind: ResourceKind): string[] {
653
- try {
654
- fsSync.accessSync(dirPath);
655
- } catch {
656
- return [];
657
- }
658
-
659
- let entries: string[];
660
- try {
661
- entries = fsSync.readdirSync(dirPath);
662
- } catch {
663
- return [];
664
- }
665
-
666
- const files: string[] = [];
667
- for (const entry of entries) {
668
- if (!isTargetFile(entry, kind)) continue;
669
- files.push(resolve(dirPath, entry));
670
- }
671
- return files;
672
- }
673
-
674
- /**
675
- * 同步版:读取 package.json 的 pi.{kind} manifest。
676
- * 供 agent-registry 同步路径使用。与 async 版共享 manifestCache(双读者同一 Map)。
677
- */
678
- export function readPackageManifestSync(pkgDir: string, kind: ResourceKind): string[] | undefined {
679
- const pkgJsonPath = resolve(pkgDir, "package.json");
680
- let mtimeMs: number;
681
- try {
682
- mtimeMs = fsSync.statSync(pkgJsonPath).mtimeMs;
683
- } catch {
684
- // stat 失败:文件不存在/不可 stat → 驱逐条目(对齐 getCachedFile 语义)
685
- manifestCache.delete(pkgJsonPath);
686
- return undefined;
687
- }
688
- const entry = manifestCache.get(pkgJsonPath);
689
- if (entry && entry.mtimeMs === mtimeMs) return piToManifest(entry.pi, kind);
690
- let pi: Record<string, unknown> | undefined;
691
- try {
692
- const content = fsSync.readFileSync(pkgJsonPath, "utf-8");
693
- pi = parsePiField(content);
694
- } catch {
695
- // read 失败或坏 JSON → 不缓存不驱逐已有好条目,下次调用重试
696
- return undefined;
697
- }
698
- manifestCache.set(pkgJsonPath, { mtimeMs, pi });
699
- return piToManifest(pi, kind);
700
- }
701
-
702
- /**
703
- * 同步版:处理单个 npm/dev 包。
704
- * 供 agent-registry 同步路径使用。
705
- */
706
- export function processPackageSync(pkgDir: string, kind: ResourceKind): DiscoveredResource[] {
707
- const manifestPaths = readPackageManifestSync(pkgDir, kind);
708
-
709
- if (manifestPaths && manifestPaths.length > 0) {
710
- const results: DiscoveredResource[] = [];
711
-
712
- for (const relPath of manifestPaths) {
713
- const absPath = resolve(pkgDir, relPath);
714
- let fileStat: fsSync.Stats | null;
715
- try {
716
- fileStat = fsSync.statSync(absPath);
717
- } catch {
718
- fileStat = null;
719
- }
720
- if (!fileStat) {
721
- results.push({ path: absPath, source: "npm", available: false });
722
- continue;
723
- }
724
-
725
- if (fileStat.isDirectory()) {
726
- const files = scanDirectorySync(absPath, kind);
727
- for (const f of files) {
728
- results.push({ path: f, source: "npm", available: true });
729
- }
730
- } else if (fileStat.isFile()) {
731
- results.push({ path: absPath, source: "npm", available: true });
732
- }
733
- }
734
-
735
- return results;
736
- }
737
-
738
- // 无 manifest:扫约定目录
739
- const conventionDir = resolve(pkgDir, kind);
740
- const files = scanDirectorySync(conventionDir, kind);
741
- return files.map((f) => ({ path: f, source: "npm", available: true }));
742
- }
743
-
744
- /**
745
- * 同步版:扫描 npm node_modules 目录。
746
- * 供 agent-registry 同步路径使用。
747
- */
748
- export function scanNpmDirSync(nodeModulesDir: string, kind: ResourceKind): DiscoveredResource[] {
749
- let entries: string[];
750
- try {
751
- entries = fsSync.readdirSync(nodeModulesDir);
752
- } catch {
753
- return [];
754
- }
755
-
756
- const results: DiscoveredResource[] = [];
757
- for (const entry of entries) {
758
- const entryPath = resolve(nodeModulesDir, entry);
759
-
760
- if (entry.startsWith("@")) {
761
- let scopedEntries: string[];
762
- try {
763
- scopedEntries = fsSync.readdirSync(entryPath);
764
- } catch {
765
- continue;
766
- }
767
- for (const scopedPkg of scopedEntries) {
768
- const scopedPkgDir = resolve(entryPath, scopedPkg);
769
- results.push(...processPackageSync(scopedPkgDir, kind));
770
- }
771
- } else {
772
- results.push(...processPackageSync(entryPath, kind));
773
- }
774
- }
775
- return results;
776
- }
777
-
778
- /**
779
- * 同步版:发现所有资源(agent-registry 专用,支持 mtime 缓存的 hot-reload)。
780
- *
781
- * 与 discoverResources 对应的同步实现,扫描相同的源。
782
- * 返回所有源的资源(未去重,调用方按需处理优先级)。
783
- */
784
- export function discoverResourcesSync(config: ScanConfig): DiscoveredResource[] {
785
- const targets = buildScanTargets(config);
786
- const all: DiscoveredResource[] = [];
787
-
788
- for (const target of targets) {
789
- if (target.source === "npm" || target.source === "npm-dev") {
790
- const resources = scanNpmDirSync(target.dir, config.kind);
791
- all.push(...resources.map((r) => ({ ...r, source: target.source })));
792
- } else if (target.source === "user-extension-paths") {
793
- // XYZ_EXTENSION_PATHS(dev-link):每个 dir 是单个包目录
794
- const resources = processPackageSync(target.dir, config.kind);
795
- all.push(...resources.map((r) => ({ ...r, source: target.source })));
796
- } else {
797
- const files = scanDirectorySync(target.dir, config.kind);
798
- all.push(...files.map((f) => ({ path: f, source: target.source, available: true })));
799
- }
800
- }
801
-
802
- // 按优先级合并(targets 顺序 = 优先级低→高)
803
- const merged = new Map<string, DiscoveredResource>();
804
- for (const r of all) {
805
- const key = stem(r.path);
806
- if (!r.available && merged.has(key)) continue;
807
- merged.set(key, r);
808
- }
809
-
810
- return Array.from(merged.values());
811
- }