@zhushanwen/subagent-core 0.2.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 (330) hide show
  1. package/README.md +114 -0
  2. package/dist/chunk-3VOERJPJ.js +22 -0
  3. package/dist/chunk-A4IVZWVX.js +31 -0
  4. package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
  5. package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
  6. package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
  7. package/dist/execution/engine/engines/zcode/constants.js +22 -0
  8. package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
  9. package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
  10. package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
  11. package/dist/execution/engine/engines/zcode/reader.js +239 -0
  12. package/dist/execution/engine/paths.cjs +55 -0
  13. package/dist/execution/engine/paths.d.cts +8 -0
  14. package/dist/execution/engine/paths.d.ts +8 -0
  15. package/dist/execution/engine/paths.js +26 -0
  16. package/dist/execution/relay-env.cjs +62 -0
  17. package/dist/execution/relay-env.d.cts +33 -0
  18. package/dist/execution/relay-env.d.ts +33 -0
  19. package/dist/execution/relay-env.js +20 -0
  20. package/dist/index.cjs +1852 -0
  21. package/dist/index.d.cts +1184 -0
  22. package/dist/index.d.ts +1184 -0
  23. package/dist/index.js +1810 -0
  24. package/dist/types-BxyAidGf.d.cts +875 -0
  25. package/dist/types-BxyAidGf.d.ts +875 -0
  26. package/package.json +99 -0
  27. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
  28. package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
  29. package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
  30. package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
  31. package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
  32. package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
  33. package/src/__tests__/m2-append-content-probe.test.ts +73 -0
  34. package/src/__tests__/manifest-store.test.ts +368 -0
  35. package/src/__tests__/record-store-cache.test.ts +335 -0
  36. package/src/__tests__/record-store-index.test.ts +560 -0
  37. package/src/__tests__/record-store-last-line.test.ts +129 -0
  38. package/src/__tests__/review-fix-loop-script.test.ts +288 -0
  39. package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
  40. package/src/__tests__/robustness-low-batch1.test.ts +286 -0
  41. package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
  42. package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
  43. package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
  44. package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
  45. package/src/__tests__/session-runner.test.ts +69 -0
  46. package/src/__tests__/smoke.test.ts +9 -0
  47. package/src/__tests__/stream-sink.test.ts +203 -0
  48. package/src/core/__tests__/host-services.test.ts +144 -0
  49. package/src/core/__tests__/logger.test.ts +78 -0
  50. package/src/core/__tests__/notify-ports.test.ts +104 -0
  51. package/src/core/host-services.ts +97 -0
  52. package/src/core/logger.ts +47 -0
  53. package/src/core/notify-ports.ts +126 -0
  54. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
  55. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
  56. package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
  57. package/src/execution/__tests__/alive-store.test.ts +147 -0
  58. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
  59. package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
  60. package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
  61. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
  62. package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
  63. package/src/execution/__tests__/config.test.ts +302 -0
  64. package/src/execution/__tests__/delivery-methods.test.ts +424 -0
  65. package/src/execution/__tests__/dialog-queue.test.ts +299 -0
  66. package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
  67. package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
  68. package/src/execution/__tests__/execute-nesting.test.ts +323 -0
  69. package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
  70. package/src/execution/__tests__/execution-record.test.ts +1394 -0
  71. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
  72. package/src/execution/__tests__/finalize-record.test.ts +367 -0
  73. package/src/execution/__tests__/finalized-marker.test.ts +104 -0
  74. package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
  75. package/src/execution/__tests__/gc-timer.test.ts +184 -0
  76. package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
  77. package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
  78. package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
  79. package/src/execution/__tests__/host-mode.test.ts +87 -0
  80. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
  83. package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
  84. package/src/execution/__tests__/model-resolver.test.ts +465 -0
  85. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
  86. package/src/execution/__tests__/nested-visibility.test.ts +325 -0
  87. package/src/execution/__tests__/output-collector.test.ts +358 -0
  88. package/src/execution/__tests__/path-encoding.test.ts +104 -0
  89. package/src/execution/__tests__/pi-invocation.test.ts +134 -0
  90. package/src/execution/__tests__/record-store.test.ts +1057 -0
  91. package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
  92. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
  93. package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
  94. package/src/execution/__tests__/relay-env.test.ts +42 -0
  95. package/src/execution/__tests__/resource-policy.test.ts +109 -0
  96. package/src/execution/__tests__/rpc-mode.test.ts +89 -0
  97. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
  98. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
  99. package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
  100. package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
  101. package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
  102. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
  103. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
  104. package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
  105. package/src/execution/__tests__/session-file-gc.test.ts +293 -0
  106. package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
  107. package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
  108. package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
  109. package/src/execution/__tests__/spawn-args.test.ts +445 -0
  110. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
  111. package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
  112. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
  113. package/src/execution/__tests__/spawned-children.test.ts +92 -0
  114. package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
  115. package/src/execution/__tests__/stdin-writer.test.ts +462 -0
  116. package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
  117. package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
  118. package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
  119. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
  120. package/src/execution/__tests__/subagent-service.test.ts +788 -0
  121. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
  122. package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
  123. package/src/execution/__tests__/temp-prompt.test.ts +53 -0
  124. package/src/execution/__tests__/timeout-integration.test.ts +615 -0
  125. package/src/execution/__tests__/tombstone-store.test.ts +73 -0
  126. package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
  127. package/src/execution/__tests__/turn-limiter.test.ts +65 -0
  128. package/src/execution/__tests__/ui-channels.test.ts +187 -0
  129. package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
  130. package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
  131. package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
  132. package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
  133. package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
  134. package/src/execution/__tests__/worktree-manager.test.ts +633 -0
  135. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
  136. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
  137. package/src/execution/__tests__/worktree-registry.test.ts +199 -0
  138. package/src/execution/agent-registry.ts +199 -0
  139. package/src/execution/agent-result-mapper.ts +90 -0
  140. package/src/execution/alive-store.ts +119 -0
  141. package/src/execution/argv-mirror.ts +113 -0
  142. package/src/execution/best-effort.ts +38 -0
  143. package/src/execution/channel-registry-access.ts +144 -0
  144. package/src/execution/concurrency-pool.ts +116 -0
  145. package/src/execution/config.ts +165 -0
  146. package/src/execution/dialog-queue.ts +330 -0
  147. package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
  148. package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
  149. package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
  150. package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
  151. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
  152. package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
  153. package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
  154. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
  155. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
  156. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
  157. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
  158. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
  159. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
  160. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
  161. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
  162. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
  163. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
  164. package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
  165. package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
  166. package/src/execution/engine/__tests__/paths.test.ts +39 -0
  167. package/src/execution/engine/__tests__/registry.test.ts +120 -0
  168. package/src/execution/engine/__tests__/routing.test.ts +231 -0
  169. package/src/execution/engine/common/data-dir.ts +66 -0
  170. package/src/execution/engine/common/errors.ts +183 -0
  171. package/src/execution/engine/common/event-journal.ts +254 -0
  172. package/src/execution/engine/common/journal-replay.ts +62 -0
  173. package/src/execution/engine/common/kill-chain.ts +221 -0
  174. package/src/execution/engine/common/nesting-guard.ts +50 -0
  175. package/src/execution/engine/common/persona-router.ts +108 -0
  176. package/src/execution/engine/common/pool-manager.ts +226 -0
  177. package/src/execution/engine/common/schema-emulation.ts +189 -0
  178. package/src/execution/engine/common/session-view-projection.ts +51 -0
  179. package/src/execution/engine/engine-discovery.ts +65 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
  181. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
  182. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
  183. package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
  184. package/src/execution/engine/engines/pi/reader.ts +48 -0
  185. package/src/execution/engine/engines/pi/registration.ts +35 -0
  186. package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
  187. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
  188. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
  189. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
  190. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
  191. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
  192. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
  193. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
  194. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
  195. package/src/execution/engine/engines/zcode/constants.ts +43 -0
  196. package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
  197. package/src/execution/engine/engines/zcode/launcher.ts +161 -0
  198. package/src/execution/engine/engines/zcode/parser.ts +436 -0
  199. package/src/execution/engine/engines/zcode/preparer.ts +363 -0
  200. package/src/execution/engine/engines/zcode/reader.ts +381 -0
  201. package/src/execution/engine/engines/zcode/registration.ts +37 -0
  202. package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
  203. package/src/execution/engine/host-task-spec.ts +47 -0
  204. package/src/execution/engine/model-prompt.ts +158 -0
  205. package/src/execution/engine/paths.ts +42 -0
  206. package/src/execution/engine/port.ts +153 -0
  207. package/src/execution/engine/registry.ts +133 -0
  208. package/src/execution/engine/routing.ts +218 -0
  209. package/src/execution/engine/types.ts +309 -0
  210. package/src/execution/execute-options-mapper.ts +115 -0
  211. package/src/execution/execution-record.ts +984 -0
  212. package/src/execution/finalize-record.ts +254 -0
  213. package/src/execution/finalized-marker.ts +70 -0
  214. package/src/execution/get-state-handshake.ts +104 -0
  215. package/src/execution/host-mode.ts +56 -0
  216. package/src/execution/idle-gc.ts +47 -0
  217. package/src/execution/lifecycle-manager.ts +513 -0
  218. package/src/execution/lifecycle-predicates.ts +65 -0
  219. package/src/execution/manifest-store.ts +256 -0
  220. package/src/execution/model-config-service.ts +250 -0
  221. package/src/execution/model-resolver.ts +248 -0
  222. package/src/execution/notifier.ts +372 -0
  223. package/src/execution/notify-ledger.ts +580 -0
  224. package/src/execution/output-collector.ts +228 -0
  225. package/src/execution/path-encoding.ts +60 -0
  226. package/src/execution/pi-invocation.ts +120 -0
  227. package/src/execution/record-entry.ts +132 -0
  228. package/src/execution/record-store.ts +1265 -0
  229. package/src/execution/relay-env.ts +37 -0
  230. package/src/execution/session-context-resolver.ts +64 -0
  231. package/src/execution/session-file-gc.ts +120 -0
  232. package/src/execution/session-pending.ts +170 -0
  233. package/src/execution/session-reconstructor.ts +678 -0
  234. package/src/execution/session-runner.ts +1789 -0
  235. package/src/execution/sessions-index.ts +304 -0
  236. package/src/execution/spawn-event-adapter.ts +363 -0
  237. package/src/execution/stdin-writer.ts +198 -0
  238. package/src/execution/stream-sink.ts +126 -0
  239. package/src/execution/subagent-service.ts +2268 -0
  240. package/src/execution/subprocess-agent-runner.ts +317 -0
  241. package/src/execution/temp-prompt.ts +62 -0
  242. package/src/execution/tombstone-store.ts +72 -0
  243. package/src/execution/turn-limiter.ts +102 -0
  244. package/src/execution/types.ts +1023 -0
  245. package/src/execution/ui-channels.ts +216 -0
  246. package/src/execution/ui-interaction-model.ts +48 -0
  247. package/src/execution/ui-request-handler-factory.ts +243 -0
  248. package/src/execution/ui-request-observability.ts +81 -0
  249. package/src/execution/ui-request-queue.ts +184 -0
  250. package/src/execution/worktree-manager.ts +688 -0
  251. package/src/execution/worktree-registry.ts +279 -0
  252. package/src/index.ts +87 -0
  253. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
  254. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
  255. package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +143 -0
  257. package/src/orchestration/__tests__/config-loader.test.ts +503 -0
  258. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
  259. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
  260. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
  261. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
  262. package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
  263. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
  264. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
  265. package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
  266. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
  267. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
  268. package/src/orchestration/__tests__/script-lint.test.ts +831 -0
  269. package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
  270. package/src/orchestration/__tests__/test-mocks.ts +197 -0
  271. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
  272. package/src/orchestration/__tests__/worker-host.test.ts +120 -0
  273. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
  274. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
  275. package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
  276. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
  277. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
  278. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
  279. package/src/orchestration/agent-opts-resolver.ts +174 -0
  280. package/src/orchestration/args-validator.ts +127 -0
  281. package/src/orchestration/config-loader.ts +316 -0
  282. package/src/orchestration/error-recovery.ts +1018 -0
  283. package/src/orchestration/execute-agent-call.ts +256 -0
  284. package/src/orchestration/launcher.ts +455 -0
  285. package/src/orchestration/lifecycle.ts +399 -0
  286. package/src/orchestration/models/__tests__/budget.test.ts +307 -0
  287. package/src/orchestration/models/agent-call.ts +83 -0
  288. package/src/orchestration/models/budget.ts +118 -0
  289. package/src/orchestration/models/ports.ts +164 -0
  290. package/src/orchestration/models/run-runtime.ts +104 -0
  291. package/src/orchestration/models/run-spec.ts +83 -0
  292. package/src/orchestration/models/run-state.ts +44 -0
  293. package/src/orchestration/models/trace.ts +183 -0
  294. package/src/orchestration/models/types.ts +301 -0
  295. package/src/orchestration/models/workflow-run.ts +254 -0
  296. package/src/orchestration/models/workflow-script-registry.ts +35 -0
  297. package/src/orchestration/models/workflow-script.ts +117 -0
  298. package/src/orchestration/script-lint.ts +766 -0
  299. package/src/orchestration/skill-discovery.ts +135 -0
  300. package/src/orchestration/worker-handle.ts +115 -0
  301. package/src/orchestration/worker-host.ts +98 -0
  302. package/src/orchestration/worker-script-builder.ts +421 -0
  303. package/src/orchestration/workflow-files.ts +85 -0
  304. package/src/orchestration/workflow-script-registry-impl.ts +133 -0
  305. package/src/shared/__tests__/agent-ref.test.ts +34 -0
  306. package/src/shared/__tests__/meta-parser.test.ts +304 -0
  307. package/src/shared/__tests__/model-ref.test.ts +306 -0
  308. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
  309. package/src/shared/__tests__/resource-discovery.test.ts +749 -0
  310. package/src/shared/__tests__/resource-meta.test.ts +51 -0
  311. package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
  312. package/src/shared/__tests__/timer-delay.test.ts +61 -0
  313. package/src/shared/agent-event.ts +13 -0
  314. package/src/shared/agent-ref.ts +57 -0
  315. package/src/shared/meta-parser.ts +261 -0
  316. package/src/shared/model-ref.ts +286 -0
  317. package/src/shared/resource-discovery.ts +835 -0
  318. package/src/shared/resource-meta.ts +65 -0
  319. package/src/shared/schema-env.ts +44 -0
  320. package/src/shared/schema-jsonify.ts +58 -0
  321. package/src/shared/timer-delay.ts +54 -0
  322. package/src/shared/xml-injection.ts +35 -0
  323. package/workflows/README.md +81 -0
  324. package/workflows/_shared/agent-refs.cjs +40 -0
  325. package/workflows/chain.js +137 -0
  326. package/workflows/map-reduce.js +180 -0
  327. package/workflows/parallel.js +154 -0
  328. package/workflows/review-fix-loop-utils.cjs +1542 -0
  329. package/workflows/review-fix-loop.js +1605 -0
  330. package/workflows/scatter-gather.js +184 -0
@@ -0,0 +1,766 @@
1
+ /**
2
+ * Workflow Extension — 静态 lint
3
+ *
4
+ * 在执行前捕获常见的 workflow 脚本 API 误用。纯函数,零副作用,零 IO。
5
+ *
6
+ * 设计:
7
+ * - lint 是编排层关注(非技术资源),归属 Engine 层。
8
+ * - **entry-point 检查**:脚本必须含 agent/parallel/pipeline 之一,否则视为 error。
9
+ * WorkflowScript.validate 直接委托 lintScript,故 entry-point 检查必须在此。
10
+ * - LintFinding/LintResult 类型规范的 canonical 源在本文件。
11
+ *
12
+ * 检查项:
13
+ * 1. 必须含 agent/parallel/pipeline 入口(error)
14
+ * 2. agent 选项中 outputSchema 当 key 用 → 应为 schema(error)
15
+ * 3. result.output / result.parsedOutput / result.content → agent 返回未包装值(error)
16
+ * 4. readFileSync/writeFileSync 传状态 → 脆弱(warning)
17
+ * 5. unlinkSync 清理状态 → 与 subprocess 文件读竞态(warning)
18
+ * 6. 顶层未 await 的异步 IIFE + 内部调 agent/parallel/pipeline → 子进程被提前 kill(error)
19
+ * 7. agent() 缺 description/label → TUI /workflows 显示 '(unnamed)'(warning)
20
+ * 8. meta.phases 非字符串数组(对象数组等)→ 引擎忽略(warning)
21
+ * 9. meta.phases 声明与 phase() 调用不一致 → 运行时分组与声明脱节(warning)
22
+ *
23
+ * 层归属:Engine。
24
+ *
25
+ * 参考:domain-models.md §7(validate 语义)。
26
+ */
27
+
28
+ /** m4 W2:meta 描述字段长度上限(§5.1 注入段预算约束)。 */
29
+ const DESC_MAX_LENGTH = 200;
30
+ /** m4 W4:agent examples 最少条数(正反各一需 ≥2)。 */
31
+ const EXAMPLES_MIN_COUNT = 2;
32
+ /** m5:examples 上限(注入是每 turn 常驻成本——防数据无界膨胀)。 */
33
+ const EXAMPLES_MAX_COUNT = 4;
34
+
35
+ /** Lint 检查发现项。 */
36
+ export interface LintFinding {
37
+ /** error = 会导致运行时崩溃; warning = 可能的错误 */
38
+ severity: "error" | "warning";
39
+ line: number;
40
+ message: string;
41
+ suggestion: string;
42
+ }
43
+
44
+ /** Lint 检查结果。 */
45
+ export interface LintResult {
46
+ valid: boolean;
47
+ findings: LintFinding[];
48
+ }
49
+
50
+ /** 必须命中其一——workflow 脚本不调用任何编排函数等于空跑。 */
51
+ import type { AgentMeta } from "../shared/resource-meta.ts";
52
+ import { parseResourceMeta } from "../shared/meta-parser.ts";
53
+
54
+ const ENTRY_POINT_PATTERNS = [/\bagent\s*\(/, /\bparallel\s*\(/, /\bpipeline\s*\(/] as const;
55
+
56
+ /**
57
+ * 检查脚本是否含至少一个编排入口(agent/parallel/pipeline)。
58
+ * 无入口视为 error——空跑脚本无意义。
59
+ */
60
+ function checkEntryPoint(source: string): LintFinding[] {
61
+ const hasEntryPoint = ENTRY_POINT_PATTERNS.some((p) => p.test(source));
62
+ if (hasEntryPoint) return [];
63
+ return [
64
+ {
65
+ severity: "error",
66
+ line: 0,
67
+ message: "Workflow script must call agent(), parallel(), or pipeline() at least once.",
68
+ suggestion: "Add at least one agent(), parallel(), or pipeline() invocation.",
69
+ },
70
+ ];
71
+ }
72
+
73
+ /**
74
+ * 检查单行 lint 问题,返回该行的发现项(可能为空)。
75
+ */
76
+ function checkLine(lineText: string, lineNum: number): LintFinding[] {
77
+ const results: LintFinding[] = [];
78
+
79
+ // 跳过注释行
80
+ const trimmed = lineText.trim();
81
+ if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
82
+ return results;
83
+ }
84
+
85
+ // result.output / result.parsedOutput / result.content
86
+ const resultAccessPatterns: Array<{ regex: RegExp; field: string }> = [
87
+ { regex: /\bresult\s*\.\s*output\b/, field: "output" },
88
+ { regex: /\bresult\s*\.\s*parsedOutput\b/, field: "parsedOutput" },
89
+ { regex: /\bresult\s*\.\s*content\b/, field: "content" },
90
+ ];
91
+ for (const p of resultAccessPatterns) {
92
+ if (p.regex.test(lineText)) {
93
+ results.push({
94
+ severity: "error",
95
+ line: lineNum,
96
+ message: `\`result.${p.field}\` does not exist. agent() returns the unwrapped value directly.`,
97
+ suggestion: "Use `const value = await agent(...)` and access `value` directly.",
98
+ });
99
+ }
100
+ }
101
+
102
+ // 文件传状态(readFileSync of STATE)
103
+ if (/readFileSync\(.*STATE.*\)|readFileSync\(.*state.*\.json/i.test(lineText)) {
104
+ results.push({
105
+ severity: "warning",
106
+ line: lineNum,
107
+ message: "Reading a state file between agent calls is fragile (subprocess file access).",
108
+ suggestion: "Use agent() with `schema` to get structured output directly, avoiding file I/O for state passing.",
109
+ });
110
+ }
111
+
112
+ // unlinkSync 清理状态
113
+ if (/unlinkSync.*state/i.test(lineText)) {
114
+ results.push({
115
+ severity: "warning",
116
+ line: lineNum,
117
+ message: "unlinkSync in finally may race with agent subprocess file reads.",
118
+ suggestion: "Avoid file-based state passing; use agent() `schema` for structured output.",
119
+ });
120
+ }
121
+
122
+ return results;
123
+ }
124
+
125
+ /**
126
+ * 剔除字符串字面量与注释内容(MF-4)。逐行处理,不跨行。
127
+ * 用途:`\bagent\s*\(` 不命中字符串里的 "agent(s)"(review-fix-loop L281 误报根因);
128
+ * checkAgentDescription 的 `description\s*:` 不把 schema 内嵌 description 字符串当已提供。
129
+ */
130
+ function stripStringsAndComments(line: string): string {
131
+ return line
132
+ .replace(/"(?:\\.|[^"\\])*"/g, "")
133
+ .replace(/'(?:\\.|[^'\\])*'/g, "")
134
+ .replace(/`(?:\\.|[^`\\])*`/g, "")
135
+ .replace(/\/\/.*$/, "")
136
+ .replace(/\/\*[\s\S]*?\*\//g, "");
137
+ }
138
+
139
+ /**
140
+ * 遍历 source 中所有 agent 调用的行范围,对每个调用执行 callback。
141
+ *
142
+ * agent 调用可能跨多行,通过括号配对定位起止行:
143
+ * agent({
144
+ * prompt: ...,
145
+ * })
146
+ *
147
+ * 单行 agent 调用(如 `agent({ prompt: 'x' })`)的 startLine === endLine。
148
+ * 与 checkAgentCalls / checkAgentDescription 共享同一套范围定义,确保
149
+ * outputSchema 检查与 description 检查覆盖完全相同的调用集合(含 parallel/pipeline
150
+ * 内嵌的 agent() 调用——它们同样被 `\bagent\s*\(` 匹配)。
151
+ *
152
+ * 匹配前逐行剔除字符串/注释内容(MF-4):字符串字面量里的 "agent(s)" 不再误触发。
153
+ * 非字面量实参(agent(callVar) / agent(expr))跳过不回调:description 等选项在调用点
154
+ * 静态不可见,checkAgentDescription 无法验证运行时构造的调用——继续报 warning 即误报
155
+ * (review-fix-loop 的 agent(call) 三连误报根因)。
156
+ *
157
+ * @param callback (startLine, endLine) 0-based 行号
158
+ */
159
+ function forEachAgentCallRange(
160
+ source: string,
161
+ callback: (startLine: number, endLine: number) => void,
162
+ ): void {
163
+ const lines = source.split("\n");
164
+ let inAgentCall = false;
165
+ let depth = 0;
166
+ let agentStartLine = -1;
167
+
168
+ for (let i = 0; i < lines.length; i++) {
169
+ const line = lines[i];
170
+ const trimmed = line.trim();
171
+
172
+ // 跳过注释
173
+ if (trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")) {
174
+ continue;
175
+ }
176
+
177
+ // 匹配/计括号都用剔除字符串与注释后的行,避免字面量内容干扰
178
+ const codeLine = stripStringsAndComments(line);
179
+
180
+ // 检测 agent 调用开始
181
+ if (!inAgentCall && /\bagent\s*\(/.test(codeLine)) {
182
+ inAgentCall = true;
183
+ depth = 0;
184
+ agentStartLine = i;
185
+ // 从 agent( 开始计括号
186
+ const afterAgent = codeLine.replace(/^.*?\bagent\s*\(/, "(");
187
+ // 非字面量实参(agent(callVar) / agent(expr))→ 跳过(见函数头注释)。
188
+ // 形如 `agent(` 换行 `{` 的多行字面量调用(argTail 为空)保留原追踪行为。
189
+ const argTail = afterAgent.trimStart().slice(1).trimStart();
190
+ if (argTail.length > 0 && !argTail.startsWith("{")) {
191
+ inAgentCall = false;
192
+ continue;
193
+ }
194
+ for (const ch of afterAgent) {
195
+ if (ch === "(" || ch === "{" || ch === "[") depth++;
196
+ if (ch === ")" || ch === "}" || ch === "]") depth--;
197
+ }
198
+ if (depth <= 0) {
199
+ // 单行 agent 调用
200
+ callback(agentStartLine, i);
201
+ inAgentCall = false;
202
+ }
203
+ continue;
204
+ }
205
+
206
+ if (inAgentCall) {
207
+ for (const ch of codeLine) {
208
+ if (ch === "(" || ch === "{" || ch === "[") depth++;
209
+ if (ch === ")" || ch === "}" || ch === "]") depth--;
210
+ }
211
+ if (depth <= 0) {
212
+ callback(agentStartLine, i);
213
+ inAgentCall = false;
214
+ }
215
+ }
216
+ }
217
+ }
218
+
219
+ /**
220
+ * 找出 source 中所有 agent 调用跨度,检查错误的选项 key(outputSchema)。
221
+ * 范围遍历委托 forEachAgentCallRange。
222
+ */
223
+ function checkAgentCalls(source: string): LintFinding[] {
224
+ const lines = source.split("\n");
225
+ const findings: LintFinding[] = [];
226
+ forEachAgentCallRange(source, (startLine, endLine) => {
227
+ checkAgentCallOptions(lines, startLine, endLine, findings);
228
+ });
229
+ return findings;
230
+ }
231
+
232
+ /**
233
+ * 检查 agent 调用内的错误选项 key。
234
+ * 只标记 outputSchema 作为 KEY(属性名)使用的情况,不标记作为 VALUE。
235
+ *
236
+ * Error: { outputSchema } ← 简写属性(outputSchema 是 key)
237
+ * Error: { outputSchema: ... } ← 显式 key
238
+ * OK: { schema: outputSchema } ← outputSchema 是 value,`schema` 是 key
239
+ * OK: const outputSchema = {} ← 变量声明(在 agent 调用外)
240
+ */
241
+ function checkAgentCallOptions(
242
+ lines: string[],
243
+ startLine: number,
244
+ endLine: number,
245
+ findings: LintFinding[],
246
+ ): void {
247
+ for (let i = startLine; i <= endLine; i++) {
248
+ const line = lines[i];
249
+
250
+ // 跳过变量声明(const/let/var outputSchema = ...)
251
+ if (/\b(?:const|let|var)\s+outputSchema\b/.test(line)) {
252
+ continue;
253
+ }
254
+
255
+ // 匹配:outputSchema 作为对象 key(简写或显式)
256
+ if (/\boutputSchema\s*[,\}]/.test(line) || /\boutputSchema\s*:/.test(line)) {
257
+ // 排除:outputSchema 作为 value(在另一个 key 的冒号后)
258
+ // e.g. "schema: outputSchema," — outputSchema 前是冒号
259
+ const beforeOutput = line.substring(0, line.indexOf("outputSchema"));
260
+ if (/:\s*$/.test(beforeOutput)) {
261
+ continue; // outputSchema 是 value,不是 key
262
+ }
263
+
264
+ findings.push({
265
+ severity: "error",
266
+ line: i + 1,
267
+ message: "`outputSchema` is not a valid agent() option.",
268
+ suggestion: "Use `schema` instead of `outputSchema`.",
269
+ });
270
+ }
271
+ }
272
+ }
273
+
274
+ /**
275
+ * 剔除 agent 选项对象里的 schema 块(`schema: {...}` 嵌套对象,括号配对)。
276
+ * 用于 checkAgentDescription:JSON Schema 的 properties 里常见 `description:` 字段
277
+ * (schema 文档字段,不是 agent 选项)——只剔字符串字面量时该 key 仍保留,会把
278
+ * 内嵌 description 误判为「已提供」导致漏报(I-10 修正的剩余部分)。
279
+ * 输入为已剔除字符串/注释的 range(无引号内容干扰,括号配对安全)。
280
+ */
281
+ function stripSchemaBlocks(text: string): string {
282
+ let out = "";
283
+ let i = 0;
284
+ while (i < text.length) {
285
+ const m = text.slice(i).match(/\bschema\s*:\s*\{/);
286
+ if (!m || m.index === undefined) {
287
+ out += text.slice(i);
288
+ break;
289
+ }
290
+ const start = i + m.index;
291
+ const braceIdx = start + m[0].lastIndexOf("{");
292
+ out += text.slice(i, start);
293
+ let depth = 0;
294
+ let j = braceIdx;
295
+ for (; j < text.length; j++) {
296
+ if (text[j] === "{") depth++;
297
+ else if (text[j] === "}") {
298
+ depth--;
299
+ if (depth === 0) {
300
+ j++;
301
+ break;
302
+ }
303
+ }
304
+ }
305
+ i = j;
306
+ }
307
+ return out;
308
+ }
309
+
310
+ /**
311
+ * 检查 agent 调用是否提供 description(或其别名 label)。
312
+ *
313
+ * worker-script-builder 取 `firstArg.label || firstArg.description` 作 TUI 显示名,
314
+ * 两者都缺 → node.agent 为空 → /workflows 视图显示 '(unnamed)'。
315
+ *
316
+ * 实现复用 forEachAgentCallRange 的范围定义,在范围内检测 `description:` 或 `label:`
317
+ * 作为对象 key。两层剔除保证只认 agent 选项层的真正 key:① 字符串字面量(
318
+ * stripStringsAndComments,MF-4);② schema 块(stripSchemaBlocks——schema 内嵌的
319
+ * `description:` 是 JSON Schema 字段说明不是 agent 选项,不剔除会漏报)。
320
+ */
321
+ function checkAgentDescription(source: string): LintFinding[] {
322
+ const lines = source.split("\n");
323
+ const findings: LintFinding[] = [];
324
+ forEachAgentCallRange(source, (startLine, endLine) => {
325
+ // range 逐行剔除字符串字面量(MF-4)后再剔除 schema 块:schema 内嵌的
326
+ // `description:` 对象 key(JSON Schema 字段说明)不再被误判为「已提供」——
327
+ // 只有 agent 选项层的真正 description/label key 才算数(修正 I-10 漏报方向)。
328
+ const range = stripSchemaBlocks(lines.slice(startLine, endLine + 1).map(stripStringsAndComments).join("\n"));
329
+ // 对象以展开开头(agent({ ...call, ... })):description 来自运行时对象、调用点静态
330
+ // 不可见——无法验证即不报(review-fix-loop 的 agent({ ...call, agent: ... }) 即此形态)。
331
+ if (/\{\s*\.\.\./.test(range)) return;
332
+ // 匹配 description 或 label 作为对象 key(后跟冒号)
333
+ if (!/\b(description|label)\s*:/.test(range)) {
334
+ findings.push({
335
+ severity: "warning",
336
+ line: startLine + 1,
337
+ message:
338
+ "agent() call without `description` (or `label`) will show as '(unnamed)' in TUI.",
339
+ suggestion:
340
+ "Add `description: 'kebab-case-name'` to agent() opts for readable /workflows display.",
341
+ });
342
+ }
343
+ });
344
+ return findings;
345
+ }
346
+
347
+ // ── 顶层未 await 的异步 IIFE 检测 ───────────────────────────
348
+
349
+ /**
350
+ * 匹配未 await 的 async IIFE 起点(粗筛)。
351
+ *
352
+ * 形式:`(async function`、`(async ()`、`(async (args)` 后跟 `=>`
353
+ * 不匹配:`await (async ...`(lookbehind 排除)
354
+ */
355
+ const BARE_ASYNC_IIFE_PATTERN = /(^|[;\n\s{}(])(?<!await\s)\(async\s+(?:function\b|\(\)|\([^)]*\)\s*=>)/g;
356
+
357
+ /**
358
+ * 判断 IIFE 调用表达式是否被某个上下文「接住」(return/赋值/await 链等)。
359
+ *
360
+ * 返回 true 表示 IIFE 的 Promise 被接住(合法或可能合法);
361
+ * false 表示 IIFE 是孤立语句表达式(fire-and-forget)。
362
+ *
363
+ * 判断方法:扫描 IIFE 起点 `(async` 前的非空白 token:
364
+ * - 遇到 `=` `return` `await` `(` `[` `,` → 接住
365
+ * - 遇到 `;` `{` `}` 或行首 → 孤立语句
366
+ *
367
+ * 例:
368
+ * `const x = (async ...` → '=' 接住
369
+ * `return (async ...` → 'return' 接住
370
+ * `(async ...` 行首 → 孤立
371
+ * `}; (async ...` → 孤立(前一个语句结束后新起一个)
372
+ */
373
+ function isIIFEAwaited(source: string, iifeStart: number): boolean {
374
+ let i = iifeStart - 1;
375
+ while (i >= 0) {
376
+ const ch = source[i];
377
+ if (/\s/.test(ch)) {
378
+ i--;
379
+ continue;
380
+ }
381
+ // 当前字符是标识符字符 → 向前扫完整标识符
382
+ if (/[A-Za-z0-9_$]/.test(ch)) {
383
+ // return / await / yield / 变量名(如 `foo(async ...`)→ 接住
384
+ return true;
385
+ }
386
+ // 单字符操作符
387
+ if (ch === "=" || ch === "(" || ch === "[" || ch === "," || ch === "?" || ch === ":") {
388
+ return true;
389
+ }
390
+ if (ch === ";" || ch === "{" || ch === "}" || ch === ")") {
391
+ return false;
392
+ }
393
+ // 其他字符(如 `.` `+`),保守视为接住(避免误报)
394
+ return true;
395
+ }
396
+ return false;
397
+ }
398
+
399
+ /**
400
+ * 检测未 await 的 async IIFE,且其内部调用了 agent/parallel/pipeline。
401
+ *
402
+ * 严重度分级:
403
+ * - **error**:IIFE 是孤立语句表达式(fire-and-forget)+ 内部调 agent。
404
+ * 这是 daily-news-impact 的 bug 模式——worker 外层 IIFE 不等内层就 post return,
405
+ * 主线程 transition done → releaseRuntime → controller.abort() → SIGKILL 子进程。
406
+ * - **warning**:IIFE 被 `=`/`return`/`(` 等接住(可能后续 await),但内部调 agent。
407
+ * 提醒作者确认 Promise 真的被 await,不阻断运行。
408
+ *
409
+ * 误报规避(不报):
410
+ * - await 前缀的 IIFE(lookbehind 排除)
411
+ * - IIFE 内不含 agent/parallel/pipeline(stock-screening 这类纯 execSync 合法)
412
+ *
413
+ * 局限:纯正则 + 括号配对,无法做数据流分析。「赋值后稍后 await」「return 给外层 await」
414
+ * 都识别为「接住」(warning 而非 error),避免阻断合法写法。
415
+ */
416
+ function checkBareAsyncIIFE(source: string): LintFinding[] {
417
+ if (!ENTRY_POINT_PATTERNS.some((p) => p.test(source))) return [];
418
+
419
+ // 用 matchAll 检查所有 IIFE(脚本可能有多个,每个都需独立判断)
420
+ const findings: LintFinding[] = [];
421
+ for (const match of source.matchAll(BARE_ASYNC_IIFE_PATTERN)) {
422
+ const iifeStart = match.index ?? 0;
423
+ const finding = analyzeIIFE(source, iifeStart);
424
+ if (finding) findings.push(finding);
425
+ }
426
+ return findings;
427
+ }
428
+
429
+ /**
430
+ * 分析单个 IIFE 起点是否触发 finding。
431
+ *
432
+ * 返回 LintFinding(error 或 warning)或 undefined(IIFE 内无 agent/无闭合)。
433
+ * 详见 checkBareAsyncIIFE 的 [HISTORICAL] 教训记录。
434
+ */
435
+ function analyzeIIFE(source: string, iifeStart: number): LintFinding | undefined {
436
+ const iifeLine = source.slice(0, iifeStart).split("\n").length;
437
+
438
+ const firstBrace = source.indexOf("{", iifeStart);
439
+ if (firstBrace === -1) return undefined;
440
+
441
+ let depth = 0;
442
+ let iifeEnd = -1;
443
+ for (let i = firstBrace; i < source.length; i++) {
444
+ const ch = source[i];
445
+ if (ch === "{") depth++;
446
+ else if (ch === "}") {
447
+ depth--;
448
+ if (depth === 0) {
449
+ iifeEnd = i;
450
+ break;
451
+ }
452
+ }
453
+ }
454
+ if (iifeEnd === -1) return undefined;
455
+
456
+ const iifeBody = source.slice(firstBrace, iifeEnd);
457
+ const hasAgentInside = ENTRY_POINT_PATTERNS.some((p) => p.test(iifeBody));
458
+ if (!hasAgentInside) return undefined;
459
+
460
+ const awaited = isIIFEAwaited(source, iifeStart);
461
+ if (awaited) {
462
+ return {
463
+ severity: "warning",
464
+ line: iifeLine,
465
+ message:
466
+ "Async IIFE wrapping agent() is assigned/returned but must be awaited. If the surrounding context does not await this Promise, the worker will post `return` early and kill in-flight agent() subprocesses.",
467
+ suggestion:
468
+ "Verify the surrounding code awaits this IIFE's Promise. When unsure, prefer top-level await directly (the worker already wraps your script in an async IIFE).",
469
+ };
470
+ }
471
+
472
+ return {
473
+ severity: "error",
474
+ line: iifeLine,
475
+ message:
476
+ "Top-level async IIFE is a fire-and-forget statement. The worker's outer IIFE will post `return` before agent() resolves, killing the subprocess via runtime abort.",
477
+ suggestion:
478
+ "Remove the IIFE wrapper and use top-level await directly (the worker already wraps your script in an async IIFE). Or `await` the IIFE: `await (async function main() { ... })();`.",
479
+ };
480
+ }
481
+
482
+ // ── 显示性检查(description / phase)───────────────────────────
483
+
484
+ /**
485
+ * 检查 meta.phases 是否字符串数组。
486
+ *
487
+ * 引擎 buildPhaseGroups 只按运行时 node.phase 分组,不读 meta.phases 声明。但 meta.phases
488
+ * 仍用于文档/一致性检查(见 checkPhaseConsistency),且 SSOT 约定为字符串数组。对象数组
489
+ * (如 [{title,detail}])是常见误写,提醒作者改为字符串数组。
490
+ */
491
+ function checkMetaPhases(source: string): LintFinding[] {
492
+ const findings: LintFinding[] = [];
493
+ // 跨行匹配(\s* 含换行):`phases: [` 与 `{` 换行分离的多行对象数组同样命中。
494
+ // 原逐行匹配只命中「`[` 与 `{` 同行」,最常见的格式化写法(phases: [ 换行 { ... })零检出(MF-5)。
495
+ // 行号从 match index 反推。字符串数组(phases: [\n "a")不匹配 \s*\{,不会误报。
496
+ for (const m of source.matchAll(/phases\s*:\s*\[\s*\{/g)) {
497
+ if (m.index === undefined) continue;
498
+ const lineNum = source.slice(0, m.index).split("\n").length;
499
+ findings.push({
500
+ severity: "warning",
501
+ line: lineNum,
502
+ message:
503
+ "`meta.phases` should be a string array like ['phase1','phase2']. Object arrays are ignored by the engine.",
504
+ suggestion:
505
+ "Use `phases: ['analyze','fix']`. Engine groups nodes by runtime `phase()` calls, not by `meta.phases` declarations.",
506
+ });
507
+ }
508
+ return findings;
509
+ }
510
+
511
+ /**
512
+ * 检查 meta.phases 声明与 phase() 调用的一致性。
513
+ *
514
+ * - 声明了但从未 phase() 调用 → warning(运行时分组用不上,声明形同虚设)
515
+ * - phase() 调用了但未声明 → warning(声明遗漏,meta.phases 失去文档价值)
516
+ *
517
+ * 两者都为空时跳过(脚本不使用 phase 机制,不报)。
518
+ */
519
+ function checkPhaseConsistency(source: string): LintFinding[] {
520
+ const findings: LintFinding[] = [];
521
+
522
+ // 提取 meta.phases 声明的字符串 + 声明所在行号
523
+ const declared = new Map<string, number>();
524
+ const phasesArrayMatch = source.match(/phases\s*:\s*\[[^\]]*\]/);
525
+ if (phasesArrayMatch && phasesArrayMatch.index !== undefined) {
526
+ const inner = phasesArrayMatch[0];
527
+ // 对象数组(如 [{title,detail}])由 checkMetaPhases 单独报,这里跳过提取,
528
+ // 避免从对象字段里误抽出字符串作 declared。
529
+ if (!/\[\s*\{/.test(inner)) {
530
+ const phasesLine = source.slice(0, phasesArrayMatch.index).split("\n").length;
531
+ for (const m of inner.matchAll(/['"]([^'"]+)['"]/g)) {
532
+ if (!declared.has(m[1])) declared.set(m[1], phasesLine);
533
+ }
534
+ }
535
+ }
536
+
537
+ // 提取所有 phase() 调用实参 + 首次出现的行号
538
+ const called = new Map<string, number>();
539
+ for (const m of source.matchAll(/\bphase\s*\(\s*['"]([^'"]+)['"]/g)) {
540
+ if (m.index === undefined) continue;
541
+ const lineNum = source.slice(0, m.index).split("\n").length;
542
+ if (!called.has(m[1])) called.set(m[1], lineNum);
543
+ }
544
+
545
+ // 两者都为空 → 跳过(脚本不使用 phase 机制)
546
+ if (declared.size === 0 && called.size === 0) return [];
547
+
548
+ // 声明了但从未 phase() 调用
549
+ for (const [name, line] of declared) {
550
+ if (!called.has(name)) {
551
+ findings.push({
552
+ severity: "warning",
553
+ line,
554
+ message: `declared phase '${name}' never set via phase().`,
555
+ suggestion: `Add phase('${name}') before the agent() calls belonging to this phase, or remove it from meta.phases.`,
556
+ });
557
+ }
558
+ }
559
+
560
+ // 调用了但未声明
561
+ for (const [name, line] of called) {
562
+ if (!declared.has(name)) {
563
+ findings.push({
564
+ severity: "warning",
565
+ line,
566
+ message: `phase('${name}') called but not in meta.phases.`,
567
+ suggestion: `Add '${name}' to meta.phases array, e.g. phases: [..., '${name}'].`,
568
+ });
569
+ }
570
+ }
571
+
572
+ return findings;
573
+ }
574
+
575
+ /**
576
+ * 静态检查 workflow 脚本合法性。
577
+ *
578
+ * @param source 脚本源码(原始文件内容)
579
+ * @returns LintResult(valid = 无 error 级 finding)
580
+ */
581
+ /**
582
+ * W2/W3 共享 helper:meta 描述字段长度上限 + 单句判定(workflow lintScript 与
583
+ * agent lintAgentMeta 共用同一规则——S-5 对称,避免 agent 长 description 常驻膨胀)。
584
+ *
585
+ * W2:>200 字符 → error。
586
+ * W3:括号内容剥离((…)/(…)与 (...))后含换行/。;/'. ' 分句 → error。
587
+ * 输出格式与 severity 约定与 checkMetaQuality 一致(error / line 1 / meta.<field> 前缀)。
588
+ */
589
+ function checkMetaFieldQuality(field: string, value: string): LintFinding[] {
590
+ const findings: LintFinding[] = [];
591
+ // W2
592
+ if (value.length > DESC_MAX_LENGTH) {
593
+ findings.push({
594
+ severity: "error",
595
+ line: 1,
596
+ message: `meta.${field} 长度 ${value.length} 超过 ${DESC_MAX_LENGTH} 字符(§5.1 注入段预算约束)`,
597
+ suggestion: "精简为单句路由描述,细节移入 usage",
598
+ });
599
+ }
600
+ // W3:括号剥离后分句判定
601
+ const stripped = value
602
+ .replace(/([^)]*)/g, "")
603
+ .replace(/\([^)]*\)/g, "")
604
+ .replace(/\b(?:e\.g|i\.e)\.\s/g, ""); // 缩写剔除(exec-review F8:括号外 e.g. 不误报)
605
+ if (/[\n。;]|\.\s/.test(stripped)) {
606
+ findings.push({
607
+ severity: "error",
608
+ line: 1,
609
+ message: `meta.${field} 非单句(含换行/分句标点)——§5.1 注入段要求一句话路由描述`,
610
+ suggestion: "精简为单句,细节移入 when/notFor/usage",
611
+ });
612
+ }
613
+ return findings;
614
+ }
615
+
616
+ /**
617
+ * W1-W3:SSOT lint(m4)——保 §5.1 注入段 description 短单句、不含已声明参数名。
618
+ * 仅对 parseResourceMeta 解析成功的 meta 执行(旧 const meta 格式不检查)。
619
+ *
620
+ * W1 参数名匹配(非形态匹配):'note:'/'Example:'/'a=b' 等 prose 不误报(design-review
621
+ * 探针实测形态匹配误报面);参数名集合 = meta.parameters.properties keys + patternProperties
622
+ * 的 word 前缀(^word\\d+$ 转义形态)。检查面 = description + when + notFor 三字段
623
+ * (同进 §5.1 注入段)。W2/W3 抽到 checkMetaFieldQuality(lintAgentMeta 共用)。
624
+ */
625
+ function checkMetaQuality(meta: { description?: string; when?: string; notFor?: string; parameters?: Record<string, unknown> }): LintFinding[] {
626
+ const findings: LintFinding[] = [];
627
+ const description = meta.description ?? "";
628
+
629
+ // W1 参数名集合
630
+ const paramNames = new Set<string>();
631
+ if (meta.parameters && typeof meta.parameters === "object") {
632
+ const props = (meta.parameters as Record<string, unknown>).properties;
633
+ if (props !== null && typeof props === "object") {
634
+ for (const k of Object.keys(props as Record<string, unknown>)) paramNames.add(k);
635
+ }
636
+ const pp = (meta.parameters as Record<string, unknown>).patternProperties;
637
+ if (pp !== null && typeof pp === "object") {
638
+ for (const p of Object.keys(pp as Record<string, unknown>)) {
639
+ const m = p.match(/^\^([a-zA-Z]+)\\d\+\$$/);
640
+ if (m) paramNames.add(m[1] as string);
641
+ }
642
+ }
643
+ }
644
+
645
+ const fields: Array<[string, string]> = [
646
+ ["description", description],
647
+ ["when", meta.when ?? ""],
648
+ ["notFor", meta.notFor ?? ""],
649
+ ];
650
+ for (const [field, value] of fields) {
651
+ if (value.length === 0) continue;
652
+ // W1:已声明参数名的 ':'/'=' 形态(参数名转义防 RegExp 注入崩溃——exec-review F2;
653
+ // ':' 形态加 \\b 前缀防子串误报(subtask: 不命中 task——exec-review F6))
654
+ for (const name of paramNames) {
655
+ const nameEsc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
656
+ if (new RegExp(`\\b${nameEsc}:\\s`).test(value) || new RegExp(`\\b${nameEsc}=\\S`).test(value)) {
657
+ findings.push({
658
+ severity: "error",
659
+ line: 1,
660
+ message: `meta.${field} 包含已声明参数名 '${name}'('${name}:' / '${name}=' 形态)——参数契约请 read 脚本文件的 @pi-meta parameters 查询,description/when/notFor 只放路由信息`,
661
+ suggestion: `从 meta.${field} 移除 '${name}: ...' / '${name}=...',改在 parameters/usage 中声明`,
662
+ });
663
+ }
664
+ }
665
+ findings.push(...checkMetaFieldQuality(field, value));
666
+ }
667
+ return findings;
668
+ }
669
+
670
+ /**
671
+ * W2/W3 + W4(m4/m5 挂 agent 加载路径):description/when/notFor 长度与单句检查
672
+ * (与 workflow 同规则,S-5 对称)+ examples 正反各一。
673
+ * RoutingExample.positive 判别(action 是 string 非 null);examples 缺失 → 无 finding
674
+ * (未迁移 agent 不报错——m5 挂载安全根基)。
675
+ */
676
+ export function lintAgentMeta(meta: AgentMeta): LintFinding[] {
677
+ // W2/W3(S-5 对称):agent 的 description/when/notFor 与 workflow 同规则
678
+ // (长度上限 + 单句判定)——formatAgentList 原样注入 systemPrompt,长描述是每 turn 常驻成本。
679
+ const findings: LintFinding[] = [];
680
+ const fields: Array<[string, string]> = [
681
+ ["description", meta.description],
682
+ ["when", meta.when ?? ""],
683
+ ["notFor", meta.notFor ?? ""],
684
+ ];
685
+ for (const [field, value] of fields) {
686
+ if (value.length === 0) continue;
687
+ findings.push(...checkMetaFieldQuality(field, value));
688
+ }
689
+
690
+ const examples = meta.examples;
691
+ if (examples === undefined) return findings; // 未迁移 agent(无 examples 字段)不报错
692
+ if (examples.length === 0) {
693
+ findings.push({
694
+ severity: "error",
695
+ line: 1,
696
+ message: `agent '${meta.name}' 声明了 examples 但为空——需 ≥2 条且正反各一`,
697
+ suggestion: "补正向样本(何时调用)+ 反向样本(何时不调用)",
698
+ });
699
+ return findings;
700
+ }
701
+ const hasPositive = examples.some((e) => e.positive === true);
702
+ const hasNegative = examples.some((e) => e.positive === false);
703
+ if (examples.length < EXAMPLES_MIN_COUNT || !hasPositive || !hasNegative) {
704
+ findings.push({
705
+ severity: "error",
706
+ line: 1,
707
+ message: `agent '${meta.name}' 的 examples 需 ≥2 条且正反各一(positive:true 触发路由 + positive:false 反例)`,
708
+ suggestion: "补正向样本(何时调用)+ 反向样本(何时不调用)",
709
+ });
710
+ return findings;
711
+ }
712
+ if (examples.length > EXAMPLES_MAX_COUNT) {
713
+ findings.push({
714
+ severity: "error",
715
+ line: 1,
716
+ message: `agent '${meta.name}' 的 examples ${examples.length} 条超过上限 ${EXAMPLES_MAX_COUNT}(注入段是每 turn 常驻成本)`,
717
+ suggestion: "精简到 2-4 条:正反各一 + 最多 2 条冗余",
718
+ });
719
+ }
720
+ return findings;
721
+ }
722
+
723
+ export function lintScript(source: string): LintResult {
724
+ const lines = source.split("\n");
725
+ const findings: LintFinding[] = [];
726
+
727
+ // 入口检查(必须有 agent/parallel/pipeline 之一)
728
+ findings.push(...checkEntryPoint(source));
729
+
730
+ // 逐行检查(result.output、文件传状态等)
731
+ for (let i = 0; i < lines.length; i++) {
732
+ findings.push(...checkLine(lines[i], i + 1));
733
+ }
734
+
735
+ // agent 调用上下文检查(outputSchema 作为 key)
736
+ findings.push(...checkAgentCalls(source));
737
+
738
+ // [HISTORICAL] 顶层未 await 的异步 IIFE + 内部调 agent——子进程被提前 kill。
739
+ // 教训来源:daily-news-impact.js 用 (async function main(){...})();() 包裹整个脚本,
740
+ // worker 外层 IIFE 不等内层 IIFE 就 postMessage("return"),主线程 transition done
741
+ // → release runtime → controller.abort() → spawn 后 2ms SIGKILL 子进程。
742
+ // 诊断耗时 4 轮:先后误判为 model 故障 / 工具缺失 / turn-signal abort / 并发门闩 gate 异常,
743
+ // 最终靠 worker-host → handleReturn → release → abort 的调用栈定位。
744
+ findings.push(...checkBareAsyncIIFE(source));
745
+
746
+ // m4 W1-W3:meta 质量(description/when/notFor 短单句 + 不含已声明参数名)。
747
+ // 仅对 IF1 解析成功的 @pi-meta 执行——旧 const meta 格式(D1 无 adapter)不检查。
748
+ const meta = parseResourceMeta(source, "workflow");
749
+ if (meta && meta.kind === "workflow") {
750
+ findings.push(...checkMetaQuality(meta));
751
+ }
752
+
753
+ // 显示性检查(warning):agent 缺 description / meta.phases 形式 / phase 一致性。
754
+ // 目的:让 TUI /workflows 视图避免 unnamed agent 与 (unnamed) phase 分组。
755
+ findings.push(...checkAgentDescription(source));
756
+ findings.push(...checkMetaPhases(source));
757
+ findings.push(...checkPhaseConsistency(source));
758
+
759
+ // 按行号排序,稳定输出
760
+ findings.sort((a, b) => a.line - b.line);
761
+
762
+ return {
763
+ valid: !findings.some((f) => f.severity === "error"),
764
+ findings,
765
+ };
766
+ }