@zhushanwen/pi-subagent-workflow 8.5.0 → 8.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (336) hide show
  1. package/package.json +13 -16
  2. package/relay/relay.mjs +1 -1
  3. package/src/host/__tests__/pi-host.test.ts +214 -0
  4. package/src/host/pi-host.ts +109 -0
  5. package/src/index.ts +142 -32
  6. package/src/injectors/__tests__/engine-awareness.test.ts +292 -0
  7. package/src/injectors/__tests__/engine-section-stability.test.ts +341 -0
  8. package/src/injectors/__tests__/subagent-list-injector.test.ts +5 -5
  9. package/src/injectors/__tests__/workflow-list-injector.test.ts +5 -5
  10. package/src/injectors/engine-awareness.ts +123 -0
  11. package/src/injectors/model-list-injector.ts +1 -1
  12. package/src/injectors/subagent-list-injector.ts +8 -8
  13. package/src/injectors/workflow-list-injector.ts +8 -8
  14. package/src/interface/__tests__/detectors.test.ts +3 -3
  15. package/src/interface/__tests__/subagent-tool-path-guard.test.ts +157 -0
  16. package/src/interface/__tests__/subagent-tool-prompt.test.ts +12 -0
  17. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +2 -2
  18. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +1 -1
  19. package/src/interface/bg-notify-render.ts +34 -13
  20. package/src/interface/commands.ts +3 -3
  21. package/src/interface/format.ts +2 -2
  22. package/src/interface/helpers.ts +3 -3
  23. package/src/interface/list-component.ts +4 -4
  24. package/src/interface/list-shared.ts +2 -2
  25. package/src/interface/list-view.ts +2 -2
  26. package/src/interface/subagent-actions.ts +288 -19
  27. package/src/interface/subagent-tool-schema.ts +163 -0
  28. package/src/interface/subagent-tool.ts +65 -129
  29. package/src/interface/subagents.ts +5 -5
  30. package/src/interface/tool-render.ts +34 -2
  31. package/src/interface/tool-workflow-script.ts +4 -4
  32. package/src/interface/tool-workflow.ts +6 -6
  33. package/src/interface/views/WorkflowsView.ts +5 -5
  34. package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +3 -3
  35. package/src/interface/views/__tests__/detail-content-session-file.test.ts +2 -2
  36. package/src/interface/views/detail-content.ts +4 -4
  37. package/src/interface/views/format.ts +4 -4
  38. package/src/{orchestration/jsonl-run-store.ts → jsonl-run-store.ts} +132 -18
  39. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +0 -53
  40. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +0 -1
  41. package/src/execution/__tests__/agent-registry.test.ts +0 -363
  42. package/src/execution/__tests__/agent-result-mapper.test.ts +0 -150
  43. package/src/execution/__tests__/alive-store.test.ts +0 -147
  44. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  45. package/src/execution/__tests__/before-agent-start-injection.test.ts +0 -132
  46. package/src/execution/__tests__/bg-notify-render.test.ts +0 -256
  47. package/src/execution/__tests__/channel-registry-handshake.test.ts +0 -243
  48. package/src/execution/__tests__/chat-engine-routing.test.ts +0 -597
  49. package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +0 -365
  50. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -190
  51. package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +0 -215
  52. package/src/execution/__tests__/concurrency-pool.test.ts +0 -250
  53. package/src/execution/__tests__/config.test.ts +0 -110
  54. package/src/execution/__tests__/conversation-wiring.test.ts +0 -198
  55. package/src/execution/__tests__/crash-recovery.test.ts +0 -320
  56. package/src/execution/__tests__/delivery-methods.test.ts +0 -385
  57. package/src/execution/__tests__/dialog-queue.test.ts +0 -299
  58. package/src/execution/__tests__/epipe-fallback.test.ts +0 -241
  59. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -243
  60. package/src/execution/__tests__/execute-nesting.test.ts +0 -323
  61. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -189
  62. package/src/execution/__tests__/execution-record.test.ts +0 -1284
  63. package/src/execution/__tests__/finalize-record.test.ts +0 -368
  64. package/src/execution/__tests__/finalized-marker.test.ts +0 -82
  65. package/src/execution/__tests__/format-schema-instruction.test.ts +0 -138
  66. package/src/execution/__tests__/format.test.ts +0 -458
  67. package/src/execution/__tests__/gc-timer.test.ts +0 -184
  68. package/src/execution/__tests__/get-record-for-action-restart.test.ts +0 -254
  69. package/src/execution/__tests__/gui-mode-dispatch.test.ts +0 -59
  70. package/src/execution/__tests__/helpers/mock-extension-api.ts +0 -30
  71. package/src/execution/__tests__/helpers/spawn-mock.ts +0 -238
  72. package/src/execution/__tests__/host-mode.test.ts +0 -87
  73. package/src/execution/__tests__/index-session-start-identity.test.ts +0 -371
  74. package/src/execution/__tests__/index-session-start.test.ts +0 -593
  75. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  76. package/src/execution/__tests__/lifecycle-manager.test.ts +0 -337
  77. package/src/execution/__tests__/lifecycle-predicates.test.ts +0 -116
  78. package/src/execution/__tests__/list-component.test.ts +0 -401
  79. package/src/execution/__tests__/list-fields.test.ts +0 -109
  80. package/src/execution/__tests__/manifest-parentid.test.ts +0 -117
  81. package/src/execution/__tests__/model-resolver.test.ts +0 -413
  82. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +0 -287
  83. package/src/execution/__tests__/nested-visibility.test.ts +0 -325
  84. package/src/execution/__tests__/notifier-flush.test.ts +0 -574
  85. package/src/execution/__tests__/notifier-golden-snapshot.test.ts +0 -261
  86. package/src/execution/__tests__/one-shot-upgrade.test.ts +0 -205
  87. package/src/execution/__tests__/output-collector.test.ts +0 -61
  88. package/src/execution/__tests__/parent-child-matrix.test.ts +0 -336
  89. package/src/execution/__tests__/path-encoding.test.ts +0 -104
  90. package/src/execution/__tests__/pi-invocation.test.ts +0 -134
  91. package/src/execution/__tests__/record-store.test.ts +0 -1057
  92. package/src/execution/__tests__/records-cwd-isolation.test.ts +0 -91
  93. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +0 -339
  94. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -273
  95. package/src/execution/__tests__/relay-agent.test.ts +0 -448
  96. package/src/execution/__tests__/relay-env.test.ts +0 -42
  97. package/src/execution/__tests__/resource-policy.test.ts +0 -109
  98. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -267
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -253
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -644
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  105. package/src/execution/__tests__/sdk-contract.test.ts +0 -273
  106. package/src/execution/__tests__/session-context-resolver.test.ts +0 -167
  107. package/src/execution/__tests__/session-file-gc.test.ts +0 -293
  108. package/src/execution/__tests__/session-pending.test.ts +0 -197
  109. package/src/execution/__tests__/session-reconstructor.test.ts +0 -379
  110. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -178
  111. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -311
  112. package/src/execution/__tests__/session-start-reaper.test.ts +0 -233
  113. package/src/execution/__tests__/spawn-args.test.ts +0 -434
  114. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -189
  115. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  116. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -207
  117. package/src/execution/__tests__/spawned-children.test.ts +0 -92
  118. package/src/execution/__tests__/startup-config-declaration.test.ts +0 -35
  119. package/src/execution/__tests__/status-refactor.test.ts +0 -345
  120. package/src/execution/__tests__/stdin-writer.test.ts +0 -463
  121. package/src/execution/__tests__/stream-sink-retirement.test.ts +0 -261
  122. package/src/execution/__tests__/subagent-service-abort.test.ts +0 -60
  123. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -629
  124. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +0 -180
  125. package/src/execution/__tests__/subagent-service.test.ts +0 -788
  126. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -310
  127. package/src/execution/__tests__/subprocess-agent-runner.test.ts +0 -506
  128. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  129. package/src/execution/__tests__/timeout-integration.test.ts +0 -397
  130. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  131. package/src/execution/__tests__/tool-action.test.ts +0 -385
  132. package/src/execution/__tests__/truncline-snapshot.test.ts +0 -81
  133. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  134. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  135. package/src/execution/__tests__/ui-channels.test.ts +0 -187
  136. package/src/execution/__tests__/ui-interaction-model.test.ts +0 -67
  137. package/src/execution/__tests__/ui-request-handler-factory.test.ts +0 -370
  138. package/src/execution/__tests__/ui-request-handler.test.ts +0 -204
  139. package/src/execution/__tests__/ui-request-observability.test.ts +0 -114
  140. package/src/execution/__tests__/ui-request-queue.test.ts +0 -133
  141. package/src/execution/__tests__/worktree-manager.test.ts +0 -633
  142. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -229
  143. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +0 -181
  144. package/src/execution/__tests__/worktree-registry.test.ts +0 -199
  145. package/src/execution/agent-registry.ts +0 -193
  146. package/src/execution/agent-result-mapper.ts +0 -90
  147. package/src/execution/alive-store.ts +0 -92
  148. package/src/execution/argv-mirror.ts +0 -109
  149. package/src/execution/best-effort.ts +0 -38
  150. package/src/execution/channel-registry-access.ts +0 -144
  151. package/src/execution/concurrency-pool.ts +0 -116
  152. package/src/execution/config.ts +0 -96
  153. package/src/execution/dialog-queue.ts +0 -330
  154. package/src/execution/engine/__tests__/common/data-dir.test.ts +0 -53
  155. package/src/execution/engine/__tests__/common/errors.test.ts +0 -132
  156. package/src/execution/engine/__tests__/common/event-journal.test.ts +0 -177
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +0 -192
  158. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +0 -81
  159. package/src/execution/engine/__tests__/common/persona-router.test.ts +0 -123
  160. package/src/execution/engine/__tests__/common/pool-manager.test.ts +0 -154
  161. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  162. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  163. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +0 -141
  164. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +0 -109
  165. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +0 -101
  166. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +0 -77
  167. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +0 -104
  168. package/src/execution/engine/__tests__/conformance/contract.relay.test.ts +0 -342
  169. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +0 -201
  170. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +0 -76
  171. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +0 -79
  172. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -87
  173. package/src/execution/engine/__tests__/engines-declaration.test.ts +0 -36
  174. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -85
  175. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  176. package/src/execution/engine/__tests__/registry.test.ts +0 -120
  177. package/src/execution/engine/__tests__/routing.test.ts +0 -231
  178. package/src/execution/engine/common/data-dir.ts +0 -62
  179. package/src/execution/engine/common/errors.ts +0 -183
  180. package/src/execution/engine/common/event-journal.ts +0 -254
  181. package/src/execution/engine/common/journal-replay.ts +0 -62
  182. package/src/execution/engine/common/kill-chain.ts +0 -221
  183. package/src/execution/engine/common/nesting-guard.ts +0 -50
  184. package/src/execution/engine/common/persona-router.ts +0 -108
  185. package/src/execution/engine/common/pool-manager.ts +0 -226
  186. package/src/execution/engine/common/schema-emulation.ts +0 -189
  187. package/src/execution/engine/common/session-view-projection.ts +0 -51
  188. package/src/execution/engine/engine-discovery.ts +0 -65
  189. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -469
  190. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  191. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  192. package/src/execution/engine/engines/pi/pi-engine.ts +0 -415
  193. package/src/execution/engine/engines/pi/reader.ts +0 -48
  194. package/src/execution/engine/engines/pi/registration.ts +0 -35
  195. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  197. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  198. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -246
  199. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -228
  200. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  201. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -64
  202. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -127
  203. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -567
  204. package/src/execution/engine/engines/zcode/constants.ts +0 -43
  205. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -39
  206. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  207. package/src/execution/engine/engines/zcode/parser.ts +0 -436
  208. package/src/execution/engine/engines/zcode/preparer.ts +0 -363
  209. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  210. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  211. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -648
  212. package/src/execution/engine/host-task-spec.ts +0 -47
  213. package/src/execution/engine/model-prompt.ts +0 -59
  214. package/src/execution/engine/paths.ts +0 -42
  215. package/src/execution/engine/port.ts +0 -153
  216. package/src/execution/engine/registry.ts +0 -123
  217. package/src/execution/engine/routing.ts +0 -218
  218. package/src/execution/engine/types.ts +0 -304
  219. package/src/execution/execute-options-mapper.ts +0 -111
  220. package/src/execution/execution-record.ts +0 -906
  221. package/src/execution/finalize-record.ts +0 -251
  222. package/src/execution/finalized-marker.ts +0 -51
  223. package/src/execution/get-state-handshake.ts +0 -104
  224. package/src/execution/host-mode.ts +0 -56
  225. package/src/execution/idle-gc.ts +0 -47
  226. package/src/execution/lifecycle-manager.ts +0 -491
  227. package/src/execution/lifecycle-predicates.ts +0 -65
  228. package/src/execution/manifest-store.ts +0 -256
  229. package/src/execution/model-config-service.ts +0 -204
  230. package/src/execution/model-resolver.ts +0 -276
  231. package/src/execution/notifier.ts +0 -260
  232. package/src/execution/output-collector.ts +0 -88
  233. package/src/execution/path-encoding.ts +0 -60
  234. package/src/execution/pi-invocation.ts +0 -120
  235. package/src/execution/record-entry.ts +0 -132
  236. package/src/execution/record-store.ts +0 -1234
  237. package/src/execution/relay-env.ts +0 -37
  238. package/src/execution/session-context-resolver.ts +0 -64
  239. package/src/execution/session-file-gc.ts +0 -120
  240. package/src/execution/session-pending.ts +0 -164
  241. package/src/execution/session-reconstructor.ts +0 -678
  242. package/src/execution/session-runner.ts +0 -1548
  243. package/src/execution/sessions-index.ts +0 -304
  244. package/src/execution/spawn-event-adapter.ts +0 -363
  245. package/src/execution/stdin-writer.ts +0 -198
  246. package/src/execution/stream-sink.ts +0 -126
  247. package/src/execution/subagent-service.ts +0 -2119
  248. package/src/execution/subprocess-agent-runner.ts +0 -303
  249. package/src/execution/temp-prompt.ts +0 -62
  250. package/src/execution/tombstone-store.ts +0 -72
  251. package/src/execution/turn-limiter.ts +0 -102
  252. package/src/execution/types.ts +0 -910
  253. package/src/execution/ui-channels.ts +0 -216
  254. package/src/execution/ui-interaction-model.ts +0 -48
  255. package/src/execution/ui-request-handler-factory.ts +0 -212
  256. package/src/execution/ui-request-observability.ts +0 -81
  257. package/src/execution/ui-request-queue.ts +0 -174
  258. package/src/execution/worktree-manager.ts +0 -688
  259. package/src/execution/worktree-registry.ts +0 -222
  260. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +0 -336
  261. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +0 -208
  262. package/src/orchestration/__tests__/agent-call-stream.test.ts +0 -157
  263. package/src/orchestration/__tests__/args-validator.test.ts +0 -143
  264. package/src/orchestration/__tests__/concurrency-gate.test.ts +0 -125
  265. package/src/orchestration/__tests__/config-loader.test.ts +0 -503
  266. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +0 -724
  267. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +0 -405
  268. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +0 -56
  269. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +0 -166
  270. package/src/orchestration/__tests__/execute-agent-call.test.ts +0 -314
  271. package/src/orchestration/__tests__/jsonl-run-store-loadall-sources.test.ts +0 -171
  272. package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +0 -1030
  273. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -277
  274. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +0 -96
  275. package/src/orchestration/__tests__/lifecycle.test.ts +0 -625
  276. package/src/orchestration/__tests__/review-fix-loop-e2e.test.ts +0 -1971
  277. package/src/orchestration/__tests__/script-lint.test.ts +0 -831
  278. package/src/orchestration/__tests__/skill-discovery.test.ts +0 -157
  279. package/src/orchestration/__tests__/test-mocks.ts +0 -197
  280. package/src/orchestration/__tests__/worker-host.test.ts +0 -120
  281. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +0 -164
  282. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +0 -520
  283. package/src/orchestration/__tests__/worker-script-builder.test.ts +0 -309
  284. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +0 -110
  285. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -317
  286. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +0 -110
  287. package/src/orchestration/__tests__/workflows-e2e.test.ts +0 -537
  288. package/src/orchestration/agent-opts-resolver.ts +0 -93
  289. package/src/orchestration/args-validator.ts +0 -127
  290. package/src/orchestration/concurrency-gate.ts +0 -69
  291. package/src/orchestration/config-loader.ts +0 -315
  292. package/src/orchestration/error-recovery.ts +0 -862
  293. package/src/orchestration/execute-agent-call.ts +0 -217
  294. package/src/orchestration/launcher.ts +0 -410
  295. package/src/orchestration/lifecycle.ts +0 -396
  296. package/src/orchestration/models/__tests__/budget.test.ts +0 -367
  297. package/src/orchestration/models/__tests__/trace.test.ts +0 -408
  298. package/src/orchestration/models/agent-call.ts +0 -83
  299. package/src/orchestration/models/budget.ts +0 -148
  300. package/src/orchestration/models/ports.ts +0 -164
  301. package/src/orchestration/models/run-runtime.ts +0 -89
  302. package/src/orchestration/models/run-spec.ts +0 -83
  303. package/src/orchestration/models/run-state.ts +0 -44
  304. package/src/orchestration/models/trace.ts +0 -183
  305. package/src/orchestration/models/types.ts +0 -292
  306. package/src/orchestration/models/workflow-run.ts +0 -254
  307. package/src/orchestration/models/workflow-script-registry.ts +0 -35
  308. package/src/orchestration/models/workflow-script.ts +0 -117
  309. package/src/orchestration/script-lint.ts +0 -766
  310. package/src/orchestration/skill-discovery.ts +0 -92
  311. package/src/orchestration/worker-handle.ts +0 -115
  312. package/src/orchestration/worker-host.ts +0 -98
  313. package/src/orchestration/worker-script-builder.ts +0 -408
  314. package/src/orchestration/workflow-files.ts +0 -85
  315. package/src/orchestration/workflow-script-registry-impl.ts +0 -133
  316. package/src/shared/__tests__/agent-ref.test.ts +0 -34
  317. package/src/shared/__tests__/meta-parser.test.ts +0 -304
  318. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -280
  319. package/src/shared/__tests__/resource-discovery.test.ts +0 -736
  320. package/src/shared/__tests__/resource-meta.test.ts +0 -51
  321. package/src/shared/__tests__/schema-jsonify.test.ts +0 -81
  322. package/src/shared/agent-event.ts +0 -13
  323. package/src/shared/agent-ref.ts +0 -57
  324. package/src/shared/meta-parser.ts +0 -261
  325. package/src/shared/resource-discovery.ts +0 -811
  326. package/src/shared/resource-meta.ts +0 -65
  327. package/src/shared/schema-jsonify.ts +0 -56
  328. package/src/shared/xml-injection.ts +0 -35
  329. package/workflows/README.md +0 -81
  330. package/workflows/_shared/agent-refs.cjs +0 -40
  331. package/workflows/chain.js +0 -131
  332. package/workflows/map-reduce.js +0 -174
  333. package/workflows/parallel.js +0 -148
  334. package/workflows/review-fix-loop-utils.cjs +0 -1307
  335. package/workflows/review-fix-loop.js +0 -1396
  336. package/workflows/scatter-gather.js +0 -178
@@ -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
- }