stageflow 0.23.0 → 0.27.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 (511) hide show
  1. package/README.md +24 -5
  2. package/dist/a2a/configDiscovery.d.ts +9 -0
  3. package/dist/a2a/configDiscovery.js +18 -0
  4. package/dist/a2a/contracts.d.ts +94 -0
  5. package/dist/a2a/contracts.js +164 -0
  6. package/dist/a2a/limits.d.ts +14 -0
  7. package/dist/a2a/limits.js +29 -0
  8. package/dist/a2a/registry.d.ts +46 -0
  9. package/dist/a2a/registry.js +178 -0
  10. package/dist/a2a/server.d.ts +36 -0
  11. package/dist/a2a/server.js +344 -0
  12. package/dist/a2a/service.d.ts +97 -0
  13. package/dist/a2a/service.js +545 -0
  14. package/dist/a2a/store.d.ts +109 -0
  15. package/dist/a2a/store.js +202 -0
  16. package/dist/agent/activity.d.ts +3 -0
  17. package/dist/agent/bootProviderConfig.d.ts +21 -0
  18. package/dist/agent/bootProviderConfig.js +74 -0
  19. package/dist/agent/piAdapter.d.ts +9 -1
  20. package/dist/agent/piAdapter.js +58 -3
  21. package/dist/agent/piIsolatedMcp.d.ts +2 -0
  22. package/dist/agent/piIsolatedMcp.js +10 -2
  23. package/dist/agent/port.d.ts +6 -0
  24. package/dist/agent/streamLogRedact.d.ts +1 -1
  25. package/dist/agent/streamLogRedact.js +4 -11
  26. package/dist/cli/a2aCommand.d.ts +8 -0
  27. package/dist/cli/a2aCommand.js +131 -0
  28. package/dist/cli/artifactCommand.js +9 -16
  29. package/dist/cli/backupCommand.d.ts +9 -0
  30. package/dist/cli/backupCommand.js +111 -0
  31. package/dist/cli/debugRunCommand.d.ts +10 -0
  32. package/dist/cli/debugRunCommand.js +109 -0
  33. package/dist/cli/doctorCommand.d.ts +32 -0
  34. package/dist/cli/doctorCommand.js +453 -0
  35. package/dist/cli/envelopeCommand.js +9 -2
  36. package/dist/cli/exportAllCommand.d.ts +55 -0
  37. package/dist/cli/exportAllCommand.js +218 -0
  38. package/dist/cli/exportRunCommand.js +23 -21
  39. package/dist/cli/hostClient.d.ts +21 -3
  40. package/dist/cli/hostClient.js +93 -12
  41. package/dist/cli/initTemplates.d.ts +1 -1
  42. package/dist/cli/initTemplates.js +8 -1
  43. package/dist/cli/resolveSafeOutPath.d.ts +2 -0
  44. package/dist/cli/resolveSafeOutPath.js +40 -0
  45. package/dist/cli/restoreCommand.d.ts +10 -0
  46. package/dist/cli/restoreCommand.js +90 -0
  47. package/dist/cli/runCommand.d.ts +3 -2
  48. package/dist/cli/runCommand.js +93 -11
  49. package/dist/cli/runOutput.d.ts +4 -0
  50. package/dist/cli/runOutput.js +33 -7
  51. package/dist/cli/runStageCommand.d.ts +34 -0
  52. package/dist/cli/runStageCommand.js +426 -0
  53. package/dist/cli/runsCommand.d.ts +1 -1
  54. package/dist/cli/runsCommand.js +162 -8
  55. package/dist/cli.js +190 -30
  56. package/dist/config/catalogRelativePath.d.ts +62 -0
  57. package/dist/config/catalogRelativePath.js +118 -0
  58. package/dist/config/configOrigin.d.ts +27 -0
  59. package/dist/config/configOrigin.js +59 -0
  60. package/dist/config/hostConfig.d.ts +55 -0
  61. package/dist/config/hostConfig.js +333 -0
  62. package/dist/config/listSkills.d.ts +26 -0
  63. package/dist/config/listSkills.js +190 -3
  64. package/dist/config/loadOutcome.d.ts +1 -1
  65. package/dist/config/loadPipeline.js +23 -2
  66. package/dist/config/loadStage.js +18 -0
  67. package/dist/config/loadTask.d.ts +2 -0
  68. package/dist/config/loadTask.js +57 -18
  69. package/dist/config/materializeStageModels.js +5 -1
  70. package/dist/config/mergePipelineIncludes.d.ts +3 -0
  71. package/dist/config/mergePipelineIncludes.js +19 -0
  72. package/dist/config/migrateYaml.js +6 -11
  73. package/dist/config/multiProjectCatalog.d.ts +30 -0
  74. package/dist/config/multiProjectCatalog.js +142 -0
  75. package/dist/config/normalizePipelineStageEntry.js +20 -1
  76. package/dist/config/pipelineStageKeys.js +2 -0
  77. package/dist/config/resolveCatalogRoots.d.ts +27 -0
  78. package/dist/config/resolveCatalogRoots.js +76 -0
  79. package/dist/config/resolveModel.d.ts +7 -1
  80. package/dist/config/resolveModel.js +11 -1
  81. package/dist/config/resolveStageMcpServers.d.ts +9 -1
  82. package/dist/config/resolveStageMcpServers.js +36 -9
  83. package/dist/config/secretFromEnvOrFile.d.ts +9 -0
  84. package/dist/config/secretFromEnvOrFile.js +34 -0
  85. package/dist/config/seededCatalog.d.ts +11 -0
  86. package/dist/config/seededCatalog.js +29 -0
  87. package/dist/config/toolRequires.d.ts +24 -0
  88. package/dist/config/toolRequires.js +115 -0
  89. package/dist/config/validateCatalog.d.ts +5 -1
  90. package/dist/config/validateCatalog.js +140 -1
  91. package/dist/diagnostics/checks.d.ts +39 -0
  92. package/dist/diagnostics/checks.js +135 -0
  93. package/dist/errors/codes.d.ts +14 -0
  94. package/dist/errors/codes.js +59 -0
  95. package/dist/git/cache.d.ts +14 -0
  96. package/dist/git/cache.js +150 -0
  97. package/dist/git/credentials.d.ts +9 -0
  98. package/dist/git/credentials.js +88 -0
  99. package/dist/git/errors.d.ts +20 -0
  100. package/dist/git/errors.js +94 -0
  101. package/dist/git/exec.d.ts +16 -0
  102. package/dist/git/exec.js +135 -0
  103. package/dist/git/operations.d.ts +29 -0
  104. package/dist/git/operations.js +153 -0
  105. package/dist/git/version.d.ts +1 -0
  106. package/dist/git/version.js +10 -0
  107. package/dist/logging/audit.d.ts +16 -0
  108. package/dist/logging/audit.js +19 -0
  109. package/dist/logging/commonWords.d.ts +2 -0
  110. package/dist/logging/commonWords.js +13 -0
  111. package/dist/logging/logger.d.ts +49 -0
  112. package/dist/logging/logger.js +193 -0
  113. package/dist/logging/namedSecrets.d.ts +6 -0
  114. package/dist/logging/namedSecrets.js +58 -0
  115. package/dist/logging/redact.d.ts +13 -0
  116. package/dist/logging/redact.js +88 -0
  117. package/dist/mcp/catalogTools.js +591 -76
  118. package/dist/mcp/checkoutTools.d.ts +52 -0
  119. package/dist/mcp/checkoutTools.js +332 -0
  120. package/dist/mcp/controlTools.js +179 -5
  121. package/dist/mcp/deps.d.ts +1 -0
  122. package/dist/mcp/getStartedTool.d.ts +3 -0
  123. package/dist/mcp/getStartedTool.js +62 -0
  124. package/dist/mcp/readArtifact.d.ts +11 -0
  125. package/dist/mcp/readArtifact.js +46 -30
  126. package/dist/mcp/server.js +1 -0
  127. package/dist/mcp/tools.js +4 -0
  128. package/dist/net/proxy.d.ts +14 -0
  129. package/dist/net/proxy.js +78 -0
  130. package/dist/package-meta.d.ts +2 -1
  131. package/dist/package-meta.js +2 -1
  132. package/dist/preflight/bash.d.ts +8 -0
  133. package/dist/preflight/bash.js +34 -0
  134. package/dist/preflight/claudeRoot.d.ts +11 -0
  135. package/dist/preflight/claudeRoot.js +23 -0
  136. package/dist/preflight/mcpCommands.d.ts +8 -0
  137. package/dist/preflight/mcpCommands.js +36 -0
  138. package/dist/preflight/pipelinePreflight.d.ts +40 -0
  139. package/dist/preflight/pipelinePreflight.js +192 -0
  140. package/dist/preflight/tls.d.ts +6 -0
  141. package/dist/preflight/tls.js +18 -0
  142. package/dist/preflight/toolchain.d.ts +45 -0
  143. package/dist/preflight/toolchain.js +219 -0
  144. package/dist/project/findProjectRoot.js +3 -4
  145. package/dist/project/globalHome.d.ts +7 -0
  146. package/dist/project/globalHome.js +63 -2
  147. package/dist/project/resolveProjectContext.js +2 -2
  148. package/dist/project/resolveStageflowContext.js +2 -2
  149. package/dist/projection/projectRun.d.ts +2 -1
  150. package/dist/projection/projectRun.js +1 -0
  151. package/dist/runstore/assertTmpdir.d.ts +1 -0
  152. package/dist/runstore/assertTmpdir.js +21 -0
  153. package/dist/runstore/backup.d.ts +52 -0
  154. package/dist/runstore/backup.js +292 -0
  155. package/dist/runstore/createStore.d.ts +27 -0
  156. package/dist/runstore/createStore.js +75 -7
  157. package/dist/runstore/debugBundle.d.ts +60 -0
  158. package/dist/runstore/debugBundle.js +143 -0
  159. package/dist/runstore/diskUsage.d.ts +50 -0
  160. package/dist/runstore/diskUsage.js +214 -0
  161. package/dist/runstore/exportRunPayload.d.ts +8 -0
  162. package/dist/runstore/exportRunPayload.js +23 -0
  163. package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
  164. package/dist/runstore/normalizeCatalogPath.js +10 -0
  165. package/dist/runstore/paths.d.ts +5 -0
  166. package/dist/runstore/paths.js +145 -1
  167. package/dist/runstore/port.d.ts +119 -8
  168. package/dist/runstore/port.js +8 -1
  169. package/dist/runstore/restore.d.ts +81 -0
  170. package/dist/runstore/restore.js +355 -0
  171. package/dist/runstore/retention.d.ts +27 -0
  172. package/dist/runstore/retention.js +74 -0
  173. package/dist/runstore/runManifest.d.ts +146 -0
  174. package/dist/runstore/runManifest.js +288 -0
  175. package/dist/runstore/runProjection.d.ts +3 -1
  176. package/dist/runstore/runProjection.js +63 -1
  177. package/dist/runstore/sqlite/SqliteRunStore.d.ts +37 -3
  178. package/dist/runstore/sqlite/SqliteRunStore.js +401 -227
  179. package/dist/runstore/sqlite/applyStorePragmas.d.ts +39 -0
  180. package/dist/runstore/sqlite/applyStorePragmas.js +145 -0
  181. package/dist/runstore/sqlite/migrations/001-baseline.d.ts +7 -0
  182. package/dist/runstore/sqlite/migrations/001-baseline.js +10 -0
  183. package/dist/runstore/sqlite/migrations/002-repository-binding.d.ts +7 -0
  184. package/dist/runstore/sqlite/migrations/002-repository-binding.js +29 -0
  185. package/dist/runstore/sqlite/migrations/003-run-lifecycle.d.ts +7 -0
  186. package/dist/runstore/sqlite/migrations/003-run-lifecycle.js +28 -0
  187. package/dist/runstore/sqlite/migrations/004-auto-resume-count.d.ts +7 -0
  188. package/dist/runstore/sqlite/migrations/004-auto-resume-count.js +19 -0
  189. package/dist/runstore/sqlite/migrations/005-config-origins.d.ts +7 -0
  190. package/dist/runstore/sqlite/migrations/005-config-origins.js +19 -0
  191. package/dist/runstore/sqlite/migrations/006-pipeline-body-and-caller.d.ts +7 -0
  192. package/dist/runstore/sqlite/migrations/006-pipeline-body-and-caller.js +28 -0
  193. package/dist/runstore/sqlite/migrations/007-projects-registry.d.ts +7 -0
  194. package/dist/runstore/sqlite/migrations/007-projects-registry.js +31 -0
  195. package/dist/runstore/sqlite/migrations/index.d.ts +21 -0
  196. package/dist/runstore/sqlite/migrations/index.js +72 -0
  197. package/dist/runstore/sqlite/schema.d.ts +20 -1
  198. package/dist/runstore/sqlite/schema.js +283 -1
  199. package/dist/runstore/sqlite/storeOpenError.d.ts +6 -0
  200. package/dist/runstore/sqlite/storeOpenError.js +11 -0
  201. package/dist/runstore/sqlite/storeSchemaError.d.ts +5 -0
  202. package/dist/runstore/sqlite/storeSchemaError.js +8 -0
  203. package/dist/runstore/stageExecution.js +3 -0
  204. package/dist/runstore/storeFilesystem.d.ts +32 -0
  205. package/dist/runstore/storeFilesystem.js +148 -0
  206. package/dist/runstore/submission.d.ts +11 -0
  207. package/dist/runstore/submission.js +7 -0
  208. package/dist/runstore/trackProjection.js +2 -0
  209. package/dist/runstore/workspaceLayout.d.ts +13 -0
  210. package/dist/runstore/workspaceLayout.js +76 -0
  211. package/dist/runtime/completionCheckRunner.d.ts +3 -0
  212. package/dist/runtime/completionCheckRunner.js +45 -7
  213. package/dist/runtime/containerLimits.d.ts +16 -0
  214. package/dist/runtime/containerLimits.js +84 -0
  215. package/dist/runtime/credentialMaterialisation.d.ts +28 -0
  216. package/dist/runtime/credentialMaterialisation.js +128 -0
  217. package/dist/runtime/gitCheckoutCapability.js +7 -13
  218. package/dist/runtime/pipelineRunner.d.ts +38 -1
  219. package/dist/runtime/pipelineRunner.js +179 -15
  220. package/dist/runtime/pipelineScheduler.d.ts +15 -0
  221. package/dist/runtime/pipelineScheduler.js +167 -42
  222. package/dist/runtime/reloadRunCatalog.js +12 -1
  223. package/dist/runtime/repositoryMaterialize.d.ts +47 -0
  224. package/dist/runtime/repositoryMaterialize.js +235 -0
  225. package/dist/runtime/resolveEnvelopeRef.d.ts +30 -0
  226. package/dist/runtime/resolveEnvelopeRef.js +79 -0
  227. package/dist/runtime/resumeTimedOut.d.ts +1 -1
  228. package/dist/runtime/resumeTimedOut.js +5 -2
  229. package/dist/runtime/runChangeBus.js +9 -0
  230. package/dist/runtime/runDeletion.d.ts +7 -0
  231. package/dist/runtime/runDeletion.js +37 -0
  232. package/dist/runtime/runManager.d.ts +179 -4
  233. package/dist/runtime/runManager.js +1644 -83
  234. package/dist/runtime/runRetentionSweep.d.ts +31 -0
  235. package/dist/runtime/runRetentionSweep.js +328 -0
  236. package/dist/runtime/runRetryCoordinator.d.ts +2 -0
  237. package/dist/runtime/runRetryCoordinator.js +19 -2
  238. package/dist/runtime/runSkills.d.ts +40 -0
  239. package/dist/runtime/runSkills.js +173 -0
  240. package/dist/runtime/stageAttemptBootstrap.d.ts +5 -1
  241. package/dist/runtime/stageAttemptBootstrap.js +195 -24
  242. package/dist/runtime/stageCacheEnv.d.ts +6 -0
  243. package/dist/runtime/stageCacheEnv.js +34 -0
  244. package/dist/runtime/stageConcurrency.d.ts +2 -1
  245. package/dist/runtime/stageConcurrency.js +4 -2
  246. package/dist/runtime/stageEnvironment.d.ts +38 -0
  247. package/dist/runtime/stageEnvironment.js +193 -0
  248. package/dist/runtime/stageProcessLauncher.d.ts +18 -0
  249. package/dist/runtime/stageProcessLauncher.js +194 -38
  250. package/dist/runtime/stageRecovery.d.ts +3 -1
  251. package/dist/runtime/stageRecovery.js +19 -4
  252. package/dist/runtime/stageRoots.d.ts +38 -1
  253. package/dist/runtime/stageRoots.js +118 -0
  254. package/dist/runtime/stageRunner.d.ts +1 -0
  255. package/dist/runtime/stageRunner.js +3 -1
  256. package/dist/runtime/stageSecretDecl.d.ts +9 -0
  257. package/dist/runtime/stageSecretDecl.js +102 -0
  258. package/dist/runtime/stageSecrets.d.ts +40 -0
  259. package/dist/runtime/stageSecrets.js +181 -0
  260. package/dist/runtime/stageWorker.d.ts +3 -2
  261. package/dist/runtime/stageWorker.js +58 -10
  262. package/dist/runtime/stageWorkerProtocol.d.ts +5 -0
  263. package/dist/runtime/stageWorkerProtocol.js +8 -0
  264. package/dist/runtime/startPayload.d.ts +32 -0
  265. package/dist/runtime/startPayload.js +59 -0
  266. package/dist/runtime/taskInput.js +14 -5
  267. package/dist/runtime/verifiedStageExecution.d.ts +1 -0
  268. package/dist/runtime/verifiedStageExecution.js +2 -1
  269. package/dist/runtime/workspaceBinding.d.ts +20 -0
  270. package/dist/runtime/workspaceBinding.js +60 -0
  271. package/dist/server/allowedHosts.d.ts +19 -0
  272. package/dist/server/allowedHosts.js +179 -0
  273. package/dist/server/bootstrap.d.ts +38 -2
  274. package/dist/server/bootstrap.js +219 -4
  275. package/dist/server/controlToken.d.ts +47 -0
  276. package/dist/server/controlToken.js +241 -0
  277. package/dist/server/createHttpHost.d.ts +11 -2
  278. package/dist/server/createHttpHost.js +109 -10
  279. package/dist/server/ensureGlobalService.d.ts +5 -1
  280. package/dist/server/ensureGlobalService.js +35 -1
  281. package/dist/server/healthSurfaces.d.ts +6 -0
  282. package/dist/server/healthSurfaces.js +98 -0
  283. package/dist/server/http.d.ts +12 -1
  284. package/dist/server/http.js +1085 -485
  285. package/dist/server/listenHost.d.ts +5 -0
  286. package/dist/server/listenHost.js +56 -0
  287. package/dist/server/mcpHost.d.ts +10 -1
  288. package/dist/server/mcpHost.js +36 -6
  289. package/dist/server/operatorResults.d.ts +2 -2
  290. package/dist/server/operatorResults.js +40 -29
  291. package/dist/server/requestAuthContext.d.ts +12 -0
  292. package/dist/server/requestAuthContext.js +21 -0
  293. package/dist/server/shutdown.d.ts +69 -0
  294. package/dist/server/shutdown.js +219 -0
  295. package/dist/types/pipeline.d.ts +7 -0
  296. package/dist/types/stage.d.ts +8 -0
  297. package/dist/types/task.d.ts +8 -0
  298. package/dist/ui/assets/index-B6hlSXXn.js +120 -0
  299. package/dist/ui/index.html +1 -1
  300. package/examples/README.md +98 -0
  301. package/examples/archify-on-pr/README.md +213 -0
  302. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +19 -0
  303. package/examples/archify-on-pr/archify-on-pr.task.yaml +19 -0
  304. package/examples/archify-on-pr/author-diagrams.yaml +106 -0
  305. package/examples/archify-on-pr/detect-changes.yaml +89 -0
  306. package/examples/ci-validate/README.md +35 -0
  307. package/examples/ci-validate/check.yaml +13 -0
  308. package/examples/ci-validate/ci-demo.pipeline.yaml +4 -0
  309. package/examples/ci-validate/validate.sh +5 -0
  310. package/examples/compliance-review-demo/README.md +125 -0
  311. package/examples/compliance-review-demo/compliance-review-demo.pipeline.yaml +29 -0
  312. package/examples/compliance-review-demo/connect-account.yaml +47 -0
  313. package/examples/compliance-review-demo/map-to-control.yaml +123 -0
  314. package/examples/compliance-review-demo/poll-bucket-config.yaml +103 -0
  315. package/examples/compliance-review-demo/publish-report.yaml +79 -0
  316. package/examples/compliance-review-demo/review-verdict.yaml +122 -0
  317. package/examples/compliance-review-demo/run-compliance-check.yaml +96 -0
  318. package/examples/compliance-review-demo/store-evidence.yaml +143 -0
  319. package/examples/conditional-fork/README.md +89 -0
  320. package/examples/conditional-fork/branch-a.yaml +13 -0
  321. package/examples/conditional-fork/branch-b.yaml +13 -0
  322. package/examples/conditional-fork/decide.yaml +35 -0
  323. package/examples/conditional-fork/fork-demo.pipeline.yaml +20 -0
  324. package/examples/conditional-fork/fork-demo.task.yaml +8 -0
  325. package/examples/context7-mcp/README.md +42 -0
  326. package/examples/context7-mcp/context7-mcp.pipeline.yaml +15 -0
  327. package/examples/context7-mcp/context7-mcp.task.yaml +4 -0
  328. package/examples/context7-mcp/fetch-docs.yaml +52 -0
  329. package/examples/context7-mcp/resolve-library.yaml +31 -0
  330. package/examples/context7-mcp/write-brief.yaml +52 -0
  331. package/examples/feature-loop/README.md +104 -0
  332. package/examples/feature-loop/address-feedback.yaml +148 -0
  333. package/examples/feature-loop/align.yaml +116 -0
  334. package/examples/feature-loop/decompose.yaml +86 -0
  335. package/examples/feature-loop/default-model-hierarchy.task.yaml +58 -0
  336. package/examples/feature-loop/feature-loop.pipeline.yaml +140 -0
  337. package/examples/feature-loop/feature-loop.task.yaml +25 -0
  338. package/examples/feature-loop/implement.yaml +128 -0
  339. package/examples/feature-loop/plan.yaml +111 -0
  340. package/examples/feature-loop/publish.yaml +167 -0
  341. package/examples/feature-loop/review.yaml +146 -0
  342. package/examples/feature-loop/verify.yaml +106 -0
  343. package/examples/feedback-loop/README.md +72 -0
  344. package/examples/feedback-loop/VERIFY.md +208 -0
  345. package/examples/feedback-loop/continue-only.task.yaml +4 -0
  346. package/examples/feedback-loop/exhaust-require-continue.task.yaml +6 -0
  347. package/examples/feedback-loop/exhaust-wait-human.task.yaml +11 -0
  348. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +23 -0
  349. package/examples/feedback-loop/feedback-loop.pipeline.yaml +23 -0
  350. package/examples/feedback-loop/feedback-loop.task.yaml +4 -0
  351. package/examples/feedback-loop/implement.yaml +14 -0
  352. package/examples/feedback-loop/plan.yaml +14 -0
  353. package/examples/feedback-loop/review.yaml +15 -0
  354. package/examples/feedback-loop/send-back-once.task.yaml +6 -0
  355. package/examples/feedback-loop/submit.yaml +12 -0
  356. package/examples/generic-fan-in/README.md +151 -0
  357. package/examples/generic-fan-in/clarify.yaml +18 -0
  358. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +19 -0
  359. package/examples/generic-fan-in/generic-fan-in-accepted.task.yaml +8 -0
  360. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +18 -0
  361. package/examples/generic-fan-in/generic-fan-in.task.yaml +9 -0
  362. package/examples/generic-fan-in/research.yaml +45 -0
  363. package/examples/generic-fan-in/synthesize.yaml +66 -0
  364. package/examples/generic-fan-in/validation.yaml +45 -0
  365. package/examples/github-release/README.md +78 -0
  366. package/examples/github-release/draft-release-notes.yaml +64 -0
  367. package/examples/github-release/github-release.pipeline.yaml +11 -0
  368. package/examples/github-release/github-release.task.yaml +13 -0
  369. package/examples/github-release/publish-github-release.yaml +64 -0
  370. package/examples/hello-world/README.md +28 -0
  371. package/examples/hello-world/hello.pipeline.yaml +4 -0
  372. package/examples/hello-world/my-task.task.yaml +6 -0
  373. package/examples/hello-world/research.yaml +18 -0
  374. package/examples/mcp-hitl-tour/README.md +113 -0
  375. package/examples/mcp-hitl-tour/draft-angle.yaml +33 -0
  376. package/examples/mcp-hitl-tour/draft-short.yaml +32 -0
  377. package/examples/mcp-hitl-tour/emit-angles.yaml +45 -0
  378. package/examples/mcp-hitl-tour/gather.yaml +31 -0
  379. package/examples/mcp-hitl-tour/intake.yaml +62 -0
  380. package/examples/mcp-hitl-tour/mcp-hitl-tour-exhaust.task.yaml +13 -0
  381. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +54 -0
  382. package/examples/mcp-hitl-tour/mcp-hitl-tour.task.yaml +16 -0
  383. package/examples/mcp-hitl-tour/polish.yaml +33 -0
  384. package/examples/mcp-hitl-tour/review.yaml +17 -0
  385. package/examples/mcp-hitl-tour/ship.yaml +25 -0
  386. package/examples/oss-issue-contribution/README.md +218 -0
  387. package/examples/oss-issue-contribution/oss-address-review-feedback.yaml +117 -0
  388. package/examples/oss-issue-contribution/oss-approve-contribution.yaml +181 -0
  389. package/examples/oss-issue-contribution/oss-approve-plan.yaml +111 -0
  390. package/examples/oss-issue-contribution/oss-explain-fix.yaml +108 -0
  391. package/examples/oss-issue-contribution/oss-explain-issue.yaml +114 -0
  392. package/examples/oss-issue-contribution/oss-implement-source-fix.yaml +171 -0
  393. package/examples/oss-issue-contribution/oss-investigate-area.yaml +116 -0
  394. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +125 -0
  395. package/examples/oss-issue-contribution/oss-issue-intake.yaml +106 -0
  396. package/examples/oss-issue-contribution/oss-plan-investigation.yaml +112 -0
  397. package/examples/oss-issue-contribution/oss-plan-review.yaml +109 -0
  398. package/examples/oss-issue-contribution/oss-reproduce-issue.yaml +140 -0
  399. package/examples/oss-issue-contribution/oss-review-change.yaml +107 -0
  400. package/examples/oss-issue-contribution/oss-verify-fix.yaml +130 -0
  401. package/examples/oss-issue-contribution/oss-write-regression-test.yaml +135 -0
  402. package/examples/oss-issue-contribution/pi-7629.task.yaml +79 -0
  403. package/examples/oss-issue-contribution/pi-8919.task.yaml +75 -0
  404. package/examples/oss-issue-contribution/pydantic-ai-7971.task.yaml +53 -0
  405. package/examples/oss-quick-fix/README.md +65 -0
  406. package/examples/oss-quick-fix/implement-fix.yaml +84 -0
  407. package/examples/oss-quick-fix/oss-quick-fix.pipeline.yaml +35 -0
  408. package/examples/oss-quick-fix/oss-quick-fix.task.yaml +19 -0
  409. package/examples/oss-quick-fix/ship-fix.yaml +81 -0
  410. package/examples/oss-quick-fix/understand-issue.yaml +67 -0
  411. package/examples/oss-quick-fix/verify-fix.yaml +73 -0
  412. package/examples/plan-review/README.md +37 -0
  413. package/examples/plan-review/plan-review-followup.yaml +13 -0
  414. package/examples/plan-review/plan-review.pipeline.yaml +9 -0
  415. package/examples/plan-review/plan-review.task.yaml +9 -0
  416. package/examples/plan-review/plan-review.yaml +25 -0
  417. package/examples/playwright-mcp/README.md +39 -0
  418. package/examples/playwright-mcp/playwright-mcp.pipeline.yaml +5 -0
  419. package/examples/playwright-mcp/playwright-mcp.task.yaml +4 -0
  420. package/examples/playwright-mcp/screenshot.yaml +40 -0
  421. package/examples/retry-tour/README.md +75 -0
  422. package/examples/retry-tour/retry-tour.pipeline.yaml +122 -0
  423. package/examples/retry-tour/retry-tour.task.yaml +9 -0
  424. package/examples/route-if-tour/README.md +202 -0
  425. package/examples/route-if-tour/route-if-tour.pipeline.yaml +950 -0
  426. package/examples/route-if-tour/route-if-tour.task.yaml +4 -0
  427. package/examples/route-wiring-smoke-test/01-core-routing.pipeline.yaml +168 -0
  428. package/examples/route-wiring-smoke-test/02-on-gating.pipeline.yaml +44 -0
  429. package/examples/route-wiring-smoke-test/03-two-entry-points.pipeline.yaml +28 -0
  430. package/examples/route-wiring-smoke-test/04-entry-false-equivalent-to-omitted.pipeline.yaml +27 -0
  431. package/examples/route-wiring-smoke-test/05-fork-choice.pipeline.yaml +110 -0
  432. package/examples/route-wiring-smoke-test/06-fan-out-fan-in.pipeline.yaml +82 -0
  433. package/examples/route-wiring-smoke-test/07-loop-human-decision.pipeline.yaml +82 -0
  434. package/examples/route-wiring-smoke-test/08-skip-fallback-and-multi-on-gate.pipeline.yaml +77 -0
  435. package/examples/route-wiring-smoke-test/09-multi-loop-targets.pipeline.yaml +102 -0
  436. package/examples/route-wiring-smoke-test/10-uses-dialect-fork.pipeline.yaml +23 -0
  437. package/examples/route-wiring-smoke-test/11-sequential-io-handoff.pipeline.yaml +37 -0
  438. package/examples/route-wiring-smoke-test/12-complex-io-schemas.pipeline.yaml +138 -0
  439. package/examples/route-wiring-smoke-test/13-ref-io-handoff.pipeline.yaml +84 -0
  440. package/examples/route-wiring-smoke-test/14-if-eq-gating.pipeline.yaml +50 -0
  441. package/examples/route-wiring-smoke-test/15-if-all-gated.pipeline.yaml +37 -0
  442. package/examples/route-wiring-smoke-test/16-if-composition.pipeline.yaml +65 -0
  443. package/examples/route-wiring-smoke-test/17-if-join.pipeline.yaml +80 -0
  444. package/examples/route-wiring-smoke-test/README.md +377 -0
  445. package/examples/route-wiring-smoke-test/rejected/01-reject-legacy-needs.pipeline.yaml +26 -0
  446. package/examples/route-wiring-smoke-test/rejected/02-reject-legacy-fork.pipeline.yaml +25 -0
  447. package/examples/route-wiring-smoke-test/rejected/03-reject-legacy-feedback-loop.pipeline.yaml +30 -0
  448. package/examples/route-wiring-smoke-test/rejected/04-reject-missing-entry.pipeline.yaml +24 -0
  449. package/examples/route-wiring-smoke-test/rejected/05-reject-unreachable.pipeline.yaml +35 -0
  450. package/examples/route-wiring-smoke-test/rejected/06-reject-allow-none-without-route-select.pipeline.yaml +37 -0
  451. package/examples/route-wiring-smoke-test/rejected/07-reject-cycle.pipeline.yaml +27 -0
  452. package/examples/route-wiring-smoke-test/rejected/08-reject-loop-non-ancestor.pipeline.yaml +42 -0
  453. package/examples/route-wiring-smoke-test/rejected/09-reject-route-select-leaf.pipeline.yaml +26 -0
  454. package/examples/route-wiring-smoke-test/rejected/12-reject-loop-replay-unsafe.pipeline.yaml +55 -0
  455. package/examples/route-wiring-smoke-test/rejected/13-reject-multiple-loop-entries.pipeline.yaml +51 -0
  456. package/examples/route-wiring-smoke-test/rejected/14-reject-duplicate-stage-id.pipeline.yaml +35 -0
  457. package/examples/route-wiring-smoke-test/rejected/15-reject-unknown-route-target.pipeline.yaml +15 -0
  458. package/examples/route-wiring-smoke-test/rejected/16-reject-malformed-route-item.pipeline.yaml +15 -0
  459. package/examples/route-wiring-smoke-test/rejected/17-reject-io-incompatible.pipeline.yaml +33 -0
  460. package/examples/route-wiring-smoke-test/rejected/18-reject-nested-io.pipeline.yaml +51 -0
  461. package/examples/route-wiring-smoke-test/rejected/19-reject-array-item-io.pipeline.yaml +55 -0
  462. package/examples/route-wiring-smoke-test/rejected/20-reject-closed-io.pipeline.yaml +36 -0
  463. package/examples/route-wiring-smoke-test/rejected/21-reject-ref-io.pipeline.yaml +42 -0
  464. package/examples/route-wiring-smoke-test/rejected/22-reject-if-unknown-field.pipeline.yaml +33 -0
  465. package/examples/route-wiring-smoke-test/rejected/23-reject-if-optional-field.pipeline.yaml +32 -0
  466. package/examples/route-wiring-smoke-test/rejected/24-reject-if-empty-all.pipeline.yaml +31 -0
  467. package/examples/route-wiring-smoke-test/rejected/25-reject-if-gt-on-string.pipeline.yaml +33 -0
  468. package/examples/route-wiring-smoke-test/rejected/26-reject-if-optional-nested.pipeline.yaml +36 -0
  469. package/examples/route-wiring-smoke-test/rejected/27-reject-if-on-loop.pipeline.yaml +63 -0
  470. package/examples/route-wiring-smoke-test/rejected/29-reject-if-on-failed.pipeline.yaml +46 -0
  471. package/examples/route-wiring-smoke-test/smoke-test.task.yaml +4 -0
  472. package/examples/route-wiring-smoke-test/stages/branch-a.yaml +12 -0
  473. package/examples/route-wiring-smoke-test/stages/branch-b.yaml +12 -0
  474. package/examples/route-wiring-smoke-test/stages/decide.yaml +12 -0
  475. package/examples/route-wiring-smoke-test/stages/finish.yaml +12 -0
  476. package/examples/ship-feature/README.md +190 -0
  477. package/examples/ship-feature/feat-address-feedback.yaml +119 -0
  478. package/examples/ship-feature/feat-implement.yaml +180 -0
  479. package/examples/ship-feature/feat-plan.yaml +120 -0
  480. package/examples/ship-feature/feat-review.yaml +109 -0
  481. package/examples/ship-feature/feat-ship.yaml +203 -0
  482. package/examples/ship-feature/ship-feature.pipeline.yaml +44 -0
  483. package/examples/ship-feature/ship-feature.task.yaml +78 -0
  484. package/examples/stage-mcp/README.md +144 -0
  485. package/examples/stage-mcp/echo-mcp.mjs +20 -0
  486. package/examples/stage-mcp/stage-mcp-dead.pipeline.yaml +5 -0
  487. package/examples/stage-mcp/stage-mcp-dead.task.yaml +3 -0
  488. package/examples/stage-mcp/stage-mcp-missing-var.pipeline.yaml +5 -0
  489. package/examples/stage-mcp/stage-mcp-missing-var.task.yaml +3 -0
  490. package/examples/stage-mcp/stage-mcp.pipeline.yaml +5 -0
  491. package/examples/stage-mcp/stage-mcp.task.yaml +4 -0
  492. package/examples/stage-mcp/use-echo.yaml +20 -0
  493. package/examples/verify-tour/README.md +80 -0
  494. package/examples/verify-tour/verify-tour.pipeline.yaml +162 -0
  495. package/examples/verify-tour/verify-tour.task.yaml +11 -0
  496. package/package.json +6 -1
  497. package/skills/stageflow/SKILL.md +6 -6
  498. package/skills/stageflow/references/control-surface.md +11 -5
  499. package/skills/stageflow/scripts/detect-host.mjs +14 -1
  500. package/skills/stageflow/scripts/detect-host.test.mjs +29 -1
  501. package/skills/stageflow-author/SKILL.md +4 -3
  502. package/skills/stageflow-author/references/catalog-mapping.md +16 -0
  503. package/skills/stageflow-author/references/catalog-write-conventions.md +1 -1
  504. package/skills/stageflow-run/SKILL.md +20 -12
  505. package/skills/stageflow-run/references/mcp-call.md +9 -1
  506. package/skills/stageflow-run/references/task-and-pipeline-selection.md +16 -5
  507. package/skills/stageflow-run/scripts/mcp-call.mjs +46 -6
  508. package/skills/stageflow-run/scripts/mcp-call.test.mjs +64 -0
  509. package/skills/stageflow-session-capture/references/catalog-authoring.md +2 -0
  510. package/skills/stageflow-setup/SKILL.md +40 -8
  511. package/dist/ui/assets/index-Da5EHU6M.js +0 -119
@@ -1,36 +1,49 @@
1
- import path from "node:path";
2
1
  import { z } from "zod";
3
- import { browseCatalog, } from "../config/browseCatalog.js";
2
+ import { catalogPathErrorBody, CatalogPathError, resolveCatalogRelativePath, resolveCatalogStartInput, selectCatalogRootForStart, } from "../config/catalogRelativePath.js";
3
+ import { listModelsMultiProject, listPipelinesMultiProject, listTasksMultiProject, } from "../config/multiProjectCatalog.js";
4
+ import { resolveCatalogRoots } from "../config/resolveCatalogRoots.js";
4
5
  import { describePipeline } from "../config/describePipeline.js";
5
6
  import { loadPipeline } from "../config/loadPipeline.js";
7
+ import { readYamlObject } from "../config/readYamlObject.js";
6
8
  import { validateCatalog } from "../config/validateCatalog.js";
7
- import { PACKAGE_VERSION } from "../package-meta.js";
8
- import { findProjectRoot } from "../project/findProjectRoot.js";
9
+ import { writeAudit } from "../logging/audit.js";
10
+ import { logger as rootLogger } from "../logging/logger.js";
11
+ import { PACKAGE_VERSION, BUILD_SHA } from "../package-meta.js";
9
12
  import { PipelineValidationError } from "../runtime/pipelineValidationError.js";
13
+ import { PipelinePreflightError } from "../runtime/pipelineRunner.js";
14
+ import { EnvelopeRefError, resolveEnvelopeRefsToTask, } from "../runtime/resolveEnvelopeRef.js";
15
+ import { isTerminalProjection, isWaitingProjection, waitRun } from "./waitRun.js";
10
16
  import { mapStoreLookupError } from "../server/operatorResults.js";
17
+ import { callerIdFromRequestAuth, getRequestAuth, } from "../server/requestAuthContext.js";
11
18
  import { projectRunForMcp } from "./projectRun.js";
12
19
  import { classifyArtifactContent, readRunArtifactBytes } from "./readArtifact.js";
13
20
  import { imageResult, textResult } from "./toolResults.js";
14
- /**
15
- * Every project a host serving a global store has ever recorded a run for,
16
- * plus this host's own launch directory (always included so a project with
17
- * zero runs yet still sees its own catalog).
18
- */
19
- async function catalogRootsFor(deps) {
20
- const known = await deps.store.listProjectRoots();
21
- return [...new Set([...known, deps.cwd])];
22
- }
23
- /** Resolve which project a given catalog path (pipeline/task) belongs to. */
24
- function projectRootForPath(deps, catalogPath) {
25
- const absDir = path.dirname(path.resolve(deps.cwd, catalogPath));
26
- return findProjectRoot(absDir) ?? deps.cwd;
27
- }
21
+ import { buildRunExportPayload } from "../runstore/exportRunPayload.js";
22
+ import { redactRunManifestForRead } from "../runstore/runManifest.js";
23
+ import { getAgentDir } from "@earendil-works/pi-coding-agent";
24
+ import { runSkillsDir } from "../runtime/runSkills.js";
25
+ import { listSkillsWithOrigin } from "../config/listSkills.js";
26
+ import { findTokenShapedField, pickCheckoutOverride, tokenRejectedPayload, } from "../runtime/startPayload.js";
27
+ import { loadPipelineFromObjectOutcome, loadPipelineOutcome, } from "../config/loadPipeline.js";
28
+ import { preflightFailureCode, runPipelinePreflight, } from "../preflight/pipelinePreflight.js";
29
+ import { toolchainHealthMap } from "../preflight/toolchain.js";
30
+ const auditLog = rootLogger.child({ component: "audit" });
31
+ const gitIdentitySchema = z
32
+ .object({
33
+ name: z.string().optional(),
34
+ email: z.string().optional(),
35
+ })
36
+ .strict();
28
37
  const taskFileSchema = z.object({
29
38
  id: z.string(),
30
39
  goal: z.string(),
31
40
  context: z.string().optional(),
32
41
  constraints: z.string().optional(),
33
42
  checkout: z.string().optional(),
43
+ repository: z.string().optional(),
44
+ ref: z.string().optional(),
45
+ run_branch_template: z.string().optional(),
46
+ git_identity: gitIdentitySchema.optional(),
34
47
  input: z.record(z.string(), z.unknown()).optional(),
35
48
  });
36
49
  /**
@@ -47,75 +60,207 @@ const inlinePipelineSchema = z
47
60
  agent: z.unknown().optional(),
48
61
  model: z.unknown().optional(),
49
62
  schemas: z.unknown().optional(),
63
+ requires: z.unknown().optional(),
50
64
  })
51
65
  .strict();
52
66
  const startRunSchema = z
53
67
  .object({
54
68
  pipeline: z
55
69
  .union([z.string(), inlinePipelineSchema])
56
- .describe("Filesystem path to a pipeline YAML file, or an inline pipeline definition object ({ id, stages: [...] }) authored directly in this call"),
70
+ .describe("Catalog-relative pipeline path, or an inline pipeline definition object ({ id, stages: [...] }) authored directly in this call"),
57
71
  task_path: z.string().optional(),
58
72
  task: taskFileSchema.optional(),
73
+ project_root: z.string().optional(),
74
+ checkout: z
75
+ .string()
76
+ .optional()
77
+ .describe("Catalog-relative checkout path (maps to RunManager checkoutOverride); absolute paths are rejected with absolute_path_not_allowed"),
78
+ checkout_override: z
79
+ .string()
80
+ .optional()
81
+ .describe("Alias of checkout; prefer checkout"),
82
+ skip_gates: z.boolean().optional(),
83
+ git_sha: z.string().optional(),
84
+ ci_pr_url: z.string().optional(),
85
+ ci_job_url: z.string().optional(),
86
+ skills: z
87
+ .record(z.string(), z.record(z.string(), z.string()))
88
+ .optional()
89
+ .describe("Run-scoped skills: name → { relativePath: utf-8 contents }. Requires SKILL.md per name; materialised under the run workspace skills dir (never the worktree)"),
59
90
  })
91
+ .passthrough()
60
92
  .refine((data) => Boolean(data.task_path) !== Boolean(data.task), {
61
93
  message: "Exactly one of task_path or task is required",
62
94
  });
63
- const runStatusSchema = z.enum(["created", "running", "succeeded", "failed"]);
95
+ /**
96
+ * Deliberately thin, same reasoning as inlinePipelineSchema above: real
97
+ * structural validation of the stage body happens downstream through the
98
+ * same stage loader a pipeline's inline stage goes through.
99
+ */
100
+ const envelopeRefSchema = z.object({
101
+ runId: z.string(),
102
+ stageId: z.string(),
103
+ attempt: z.number().int().positive().optional(),
104
+ });
105
+ const runStageSchema = z
106
+ .object({
107
+ stage: z
108
+ .union([z.string(), z.record(z.string(), z.unknown())])
109
+ .describe("Catalog-relative path to a stage YAML file (same path contract as start_run — absolute paths rejected, must be inside a registered project_root), or a bare inline stage body object ({ id, system_prompt, io, model?, gate_kinds?, mcp?, verify?, timeout_ms? } — no uses:/route/pipeline wrapper) authored directly in this call"),
110
+ project_root: z
111
+ .string()
112
+ .optional()
113
+ .describe("Catalog project root to resolve a string stage path against; required when multiple catalog roots are configured and no sole non-seeded root exists"),
114
+ task_path: z.string().optional(),
115
+ task: taskFileSchema.optional(),
116
+ envelope_ref: z
117
+ .union([envelopeRefSchema, z.array(envelopeRefSchema).min(1)])
118
+ .optional()
119
+ .describe("Resolve one or more previously stored StageEnvelopes (from another run_stage call or from any stage inside a full pipeline run) and use them as this stage's input, instead of an inline task/task_path. A single ref: its payload becomes input, its summary becomes goal. Multiple refs (an array): each resolved payload is namespaced under its stageId in input (disambiguated by runId on a stageId collision), and summaries are combined into goal."),
120
+ checkout: z
121
+ .string()
122
+ .optional()
123
+ .describe("Optional checkout to use with envelope_ref (ignored with task/task_path, which carry their own checkout). Resolving envelope_ref never implies a checkout on its own."),
124
+ model: z
125
+ .string()
126
+ .optional()
127
+ .describe("Override the model/backend for this call only, taking precedence over the stage's own declared model and the global default. Omit to use the stage's own model (or the global default)."),
128
+ blocking: z
129
+ .boolean()
130
+ .optional()
131
+ .describe("When true, wait for the run to reach a terminal or waiting state and return the result in this same call, instead of returning immediately with just { runId }"),
132
+ timeout_ms: z
133
+ .number()
134
+ .optional()
135
+ .describe("Wait budget in ms when blocking is true (same bounds as wait_run)"),
136
+ })
137
+ .refine((data) => [data.task_path, data.task, data.envelope_ref].filter(Boolean).length === 1, { message: "Exactly one of task_path, task, or envelope_ref is required" });
138
+ const runStatusSchema = z.enum([
139
+ "created",
140
+ "queued",
141
+ "running",
142
+ "succeeded",
143
+ "failed",
144
+ "cancelled",
145
+ ]);
64
146
  export function registerCatalogTools(server, deps) {
65
147
  const { manager, store, cwd } = deps;
66
- server.registerTool("list_pipelines", {
67
- description: "List manifest-declared pipeline paths across every project this host knows about (this host's own project plus any project a run has ever been recorded for). Each entry is tagged with project_root. A project with no runs yet won't appear until its first run exists.",
68
- inputSchema: z.object({}),
69
- }, async () => {
70
- const roots = await catalogRootsFor(deps);
71
- const pipelines = [];
72
- for (const root of roots) {
73
- try {
74
- const catalog = await browseCatalog(root);
75
- for (const p of catalog.pipelines) {
76
- pipelines.push({ ...p, project_root: root });
77
- }
148
+ const agentDir = deps.agentDir ?? getAgentDir();
149
+ server.registerTool("list_skills", {
150
+ description: "List skills with name, description, and origin (run|checkout|host). Optional runId scopes to that run's resolution view (run-tier skills first, then checkout .pi/skills, then host).",
151
+ inputSchema: z.object({
152
+ runId: z.string().optional(),
153
+ }),
154
+ }, async ({ runId }) => {
155
+ try {
156
+ let runSkills;
157
+ let checkoutRoot;
158
+ if (runId !== undefined) {
159
+ const meta = await store.readRunMeta(runId);
160
+ runSkills = runSkillsDir(store.getWorkspaceDir(runId));
161
+ checkoutRoot = meta.checkout_root;
78
162
  }
79
- catch {
80
- // stale/unreadable project root recorded on an old run; skip it
163
+ const catalog = await listSkillsWithOrigin({
164
+ cwd,
165
+ agentDir,
166
+ ...(runSkills !== undefined ? { runSkillsDir: runSkills } : {}),
167
+ ...(checkoutRoot !== undefined ? { checkoutRoot } : {}),
168
+ });
169
+ return textResult({
170
+ skills: catalog.skills,
171
+ diagnostics: catalog.diagnostics,
172
+ });
173
+ }
174
+ catch (err) {
175
+ if (runId !== undefined) {
176
+ const mapped = mapStoreLookupError(err, { policy: "run" });
177
+ return textResult({ error: mapped.error, status: mapped.status }, true);
81
178
  }
179
+ throw err;
82
180
  }
83
- return textResult({ pipelines });
181
+ });
182
+ server.registerTool("list_pipelines", {
183
+ description: "List manifest-declared pipeline paths across every project this host knows about (registered store roots and seeded roots). Each entry is tagged with project_root. Optional project_root filter narrows; unknown value returns unknown_project_root.",
184
+ inputSchema: z.object({
185
+ project_root: z.string().optional(),
186
+ }),
187
+ }, async ({ project_root }) => {
188
+ const result = await listPipelinesMultiProject({
189
+ store,
190
+ bootCwd: cwd,
191
+ projectRootFilter: project_root,
192
+ });
193
+ if (result.root_errors.some((e) => e.code === "unknown_project_root") &&
194
+ result.items.length === 0) {
195
+ return textResult({
196
+ error: result.root_errors[0].message,
197
+ code: "unknown_project_root",
198
+ root_errors: result.root_errors,
199
+ }, true);
200
+ }
201
+ return textResult({
202
+ pipelines: result.items,
203
+ root_errors: result.root_errors,
204
+ });
84
205
  });
85
206
  server.registerTool("list_tasks", {
86
- description: "List manifest-declared task paths across every project this host knows about (this host's own project plus any project a run has ever been recorded for). Each entry is tagged with project_root. A project with no runs yet won't appear until its first run exists.",
87
- inputSchema: z.object({}),
88
- }, async () => {
89
- const roots = await catalogRootsFor(deps);
90
- const tasks = [];
91
- for (const root of roots) {
92
- try {
93
- const catalog = await browseCatalog(root);
94
- for (const t of catalog.tasks) {
95
- tasks.push({ ...t, project_root: root });
96
- }
97
- }
98
- catch {
99
- // stale/unreadable project root recorded on an old run; skip it
100
- }
207
+ description: "List manifest-declared task paths across every project this host knows about (registered store roots and seeded roots). Each entry is tagged with project_root. Optional project_root filter narrows; unknown value returns unknown_project_root.",
208
+ inputSchema: z.object({
209
+ project_root: z.string().optional(),
210
+ }),
211
+ }, async ({ project_root }) => {
212
+ const result = await listTasksMultiProject({
213
+ store,
214
+ bootCwd: cwd,
215
+ projectRootFilter: project_root,
216
+ });
217
+ if (result.root_errors.some((e) => e.code === "unknown_project_root") &&
218
+ result.items.length === 0) {
219
+ return textResult({
220
+ error: result.root_errors[0].message,
221
+ code: "unknown_project_root",
222
+ root_errors: result.root_errors,
223
+ }, true);
101
224
  }
102
- return textResult({ tasks });
225
+ return textResult({
226
+ tasks: result.items,
227
+ root_errors: result.root_errors,
228
+ });
103
229
  });
104
230
  server.registerTool("list_models", {
105
- description: "List catalog model ids from the project catalog (same source as GET /api/models)",
106
- inputSchema: z.object({}),
107
- }, async () => {
108
- const catalog = await browseCatalog(cwd);
109
- return textResult({ models: catalog.models });
231
+ description: "List catalog model ids across every project this host knows about (same multi-root source as GET /api/models). Optional project_root filter narrows.",
232
+ inputSchema: z.object({
233
+ project_root: z.string().optional(),
234
+ }),
235
+ }, async ({ project_root }) => {
236
+ const result = await listModelsMultiProject({
237
+ store,
238
+ bootCwd: cwd,
239
+ projectRootFilter: project_root,
240
+ });
241
+ if (result.root_errors.some((e) => e.code === "unknown_project_root") &&
242
+ result.items.length === 0) {
243
+ return textResult({
244
+ error: result.root_errors[0].message,
245
+ code: "unknown_project_root",
246
+ root_errors: result.root_errors,
247
+ }, true);
248
+ }
249
+ return textResult({
250
+ models: result.models,
251
+ entries: result.items,
252
+ root_errors: result.root_errors,
253
+ });
110
254
  });
111
255
  server.registerTool("list_runs", {
112
- description: "List known pipeline runs. Optional filters: status, since (ISO created_at lower bound), pipeline (id or path).",
256
+ description: "List known pipeline runs. Optional filters: status, since (ISO created_at lower bound), pipeline (id or path), caller_id.",
113
257
  inputSchema: z.object({
114
258
  status: runStatusSchema.optional(),
115
259
  since: z.string().optional(),
116
260
  pipeline: z.string().optional(),
261
+ caller_id: z.string().optional(),
117
262
  }),
118
- }, async ({ status, since, pipeline }) => {
263
+ }, async ({ status, since, pipeline, caller_id }) => {
119
264
  const filter = {};
120
265
  if (status !== undefined)
121
266
  filter.status = status;
@@ -127,17 +272,106 @@ export function registerCatalogTools(server, deps) {
127
272
  }
128
273
  if (pipeline !== undefined)
129
274
  filter.pipeline = pipeline;
275
+ if (caller_id !== undefined)
276
+ filter.caller_id = caller_id;
130
277
  const runs = await store.listRuns(Object.keys(filter).length > 0 ? filter : undefined);
131
278
  return textResult({ runs });
132
279
  });
133
280
  server.registerTool("get_health", {
134
- description: "Server health and soft-max run capacity: activeRunIds, activeCount, maxConcurrent, slotsAvailable, version. Start until slotsAvailable is 0; then wait for a run to finish or raise STAGEFLOW_MAX_CONCURRENT_RUNS.",
281
+ description: "Server health and soft-max run capacity: activeRunIds, activeCount, maxConcurrent, slotsAvailable, activeStageProcesses, version, plus disk breakdown (disk.runs_bytes, worktrees_bytes, repos_bytes, state_db_bytes, a2a_artifacts_bytes, free_bytes) for the durable root. Start until slotsAvailable is 0; then wait for a run to finish or raise STAGEFLOW_MAX_CONCURRENT_RUNS.",
135
282
  inputSchema: z.object({}),
136
- }, async () => textResult({ ...manager.getHealth(), version: PACKAGE_VERSION }));
283
+ }, async () => textResult({
284
+ ...(await manager.getHealthWithDisk()),
285
+ version: PACKAGE_VERSION,
286
+ build_sha: BUILD_SHA,
287
+ toolchain: toolchainHealthMap(),
288
+ }));
289
+ server.registerTool("preflight", {
290
+ description: "Check pipeline requires:/secrets:/mcp against the Host toolchain manifest and curated stage env before start_run. Accepts pipeline path or inline definition (same union as start_run). Returns { ok, checks } with status ok|missing_tool|tool_version_mismatch|unknown_version|secret_unavailable|unresolved_var. Does not create a Run. unknown_version is ok unless strict:true.",
291
+ inputSchema: z.object({
292
+ pipeline: z
293
+ .union([z.string(), inlinePipelineSchema])
294
+ .describe("Catalog-relative pipeline path, or an inline pipeline definition object"),
295
+ project_root: z.string().optional(),
296
+ strict: z
297
+ .boolean()
298
+ .optional()
299
+ .describe("When true, unknown_version fails (default false)"),
300
+ }),
301
+ }, async ({ pipeline, project_root, strict }) => {
302
+ const roots = await resolveCatalogRoots({
303
+ store: deps.store,
304
+ bootCwd: deps.cwd,
305
+ });
306
+ let loaded;
307
+ let projectRoot;
308
+ try {
309
+ const wireRoot = selectCatalogRootForStart(roots, project_root);
310
+ projectRoot = wireRoot.project_root;
311
+ if (typeof pipeline === "string") {
312
+ const resolved = resolveCatalogRelativePath({
313
+ inputPath: pipeline,
314
+ projectRoot: project_root,
315
+ roots,
316
+ fieldName: "pipeline",
317
+ });
318
+ projectRoot = resolved.root.project_root;
319
+ const outcome = await loadPipelineOutcome(resolved.absolutePath, {
320
+ cwd: deps.cwd,
321
+ projectRoot,
322
+ });
323
+ if (!outcome.ok) {
324
+ return textResult({
325
+ ok: false,
326
+ error: outcome.issues[0]?.message ?? "pipeline load failed",
327
+ code: outcome.issues[0]?.code ?? "config_invalid",
328
+ issues: outcome.issues,
329
+ }, true);
330
+ }
331
+ loaded = outcome.value;
332
+ }
333
+ else {
334
+ const outcome = await loadPipelineFromObjectOutcome(pipeline, {
335
+ cwd: deps.cwd,
336
+ projectRoot,
337
+ });
338
+ if (!outcome.ok) {
339
+ return textResult({
340
+ ok: false,
341
+ error: outcome.issues[0]?.message ?? "pipeline load failed",
342
+ code: outcome.issues[0]?.code ?? "config_invalid",
343
+ issues: outcome.issues,
344
+ }, true);
345
+ }
346
+ loaded = outcome.value;
347
+ }
348
+ }
349
+ catch (err) {
350
+ if (err instanceof CatalogPathError) {
351
+ return textResult(catalogPathErrorBody(err), true);
352
+ }
353
+ throw err;
354
+ }
355
+ const result = await runPipelinePreflight(loaded, {
356
+ projectRoot,
357
+ strict: strict === true,
358
+ });
359
+ const code = preflightFailureCode(result, { strict: strict === true });
360
+ return textResult({
361
+ ok: result.ok,
362
+ checks: result.checks,
363
+ ...(code !== undefined ? { code } : {}),
364
+ });
365
+ });
137
366
  server.registerTool("start_run", {
138
- description: "Start a pipeline run using a filesystem pipeline path or an inline pipeline definition ({ id, stages: [...] }, each stage the same shape as a YAML stage body — no uses: refs), and either task_path (catalog task file) or an inline task object. Returns { runId }. On conflict returns isError with code busy_capacity (soft max full) or busy_checkout (same checkout leased), plus activeCount/maxConcurrent/activeRunIds and optional conflictingRunId/conflictingCheckout.",
367
+ description: "Start a pipeline run. Accepted params: pipeline (catalog-relative path or inline { id, stages: [...] } — stages are YAML stage bodies, no uses: refs), task_path XOR task (inline task may include repository/ref or checkout; repository XOR checkout with code task.binding_conflict), project_root, checkout (catalog-relative; prefer this name; absolute → absolute_path_not_allowed), checkout_override (alias of checkout), skip_gates, git_sha, ci_pr_url, ci_job_url, skills (name → { relativePath: utf-8 contents }; requires SKILL.md; materialised under runs/<runId>/skills/, never the worktree). REST POST /api/runs accepts path pipelines only (no inline pipeline). Returns { runId } or { runId, queued: true, queuePosition }. Errors: busy_capacity, busy_checkout, insufficient_disk, start.token_rejected, skills_*, inline_pipeline_too_large, catalog path-contract codes.",
139
368
  inputSchema: startRunSchema,
140
- }, async ({ pipeline, task_path, task }) => {
369
+ }, async (args) => {
370
+ const tokenField = findTokenShapedField(args);
371
+ if (tokenField !== undefined) {
372
+ return textResult(tokenRejectedPayload(tokenField), true);
373
+ }
374
+ const { pipeline, task_path, task, project_root, checkout, checkout_override, skip_gates, git_sha, ci_pr_url, ci_job_url, skills, } = args;
141
375
  if (typeof pipeline === "string" && !pipeline.trim()) {
142
376
  return textResult({ error: "pipeline is required" }, true);
143
377
  }
@@ -145,21 +379,243 @@ export function registerCatalogTools(server, deps) {
145
379
  if (taskInput === undefined) {
146
380
  return textResult({ error: "Exactly one of task_path or task is required" }, true);
147
381
  }
382
+ let wireRoot;
383
+ let roots;
384
+ try {
385
+ ({ wireRoot, roots } = await resolveCatalogStartInput({ store: deps.store, bootCwd: deps.cwd }, project_root));
386
+ }
387
+ catch (err) {
388
+ if (err instanceof CatalogPathError) {
389
+ return textResult(catalogPathErrorBody(err), true);
390
+ }
391
+ throw err;
392
+ }
393
+ const wireProjectRoot = wireRoot.project_root;
394
+ let resolvedPipeline = pipeline;
395
+ let resolvedTask = taskInput;
396
+ let resolvedCheckout;
397
+ try {
398
+ if (typeof pipeline === "string") {
399
+ resolvedPipeline = resolveCatalogRelativePath({
400
+ inputPath: pipeline,
401
+ projectRoot: wireProjectRoot,
402
+ roots,
403
+ fieldName: "pipeline",
404
+ }).absolutePath;
405
+ }
406
+ if (typeof task_path === "string") {
407
+ resolvedTask = resolveCatalogRelativePath({
408
+ inputPath: task_path,
409
+ projectRoot: wireProjectRoot,
410
+ roots,
411
+ fieldName: "task_path",
412
+ }).absolutePath;
413
+ }
414
+ const rawCheckout = pickCheckoutOverride({ checkout, checkout_override });
415
+ if (rawCheckout !== undefined) {
416
+ resolvedCheckout = resolveCatalogRelativePath({
417
+ inputPath: rawCheckout,
418
+ projectRoot: wireProjectRoot,
419
+ roots,
420
+ fieldName: "checkout",
421
+ }).absolutePath;
422
+ }
423
+ }
424
+ catch (err) {
425
+ if (err instanceof CatalogPathError) {
426
+ return textResult(catalogPathErrorBody(err), true);
427
+ }
428
+ throw err;
429
+ }
430
+ const callerId = callerIdFromRequestAuth();
148
431
  let result;
149
432
  try {
150
- result = await manager.startRun({ pipeline, task: taskInput });
433
+ result = await manager.startRun({
434
+ pipeline: resolvedPipeline,
435
+ task: resolvedTask,
436
+ projectRoot: wireRoot.path,
437
+ ...(resolvedCheckout !== undefined
438
+ ? { checkoutOverride: resolvedCheckout }
439
+ : {}),
440
+ ...(skip_gates !== undefined ? { skipGates: skip_gates } : {}),
441
+ ...(git_sha !== undefined ? { gitSha: git_sha } : {}),
442
+ ...(ci_pr_url !== undefined ? { ciPrUrl: ci_pr_url } : {}),
443
+ ...(ci_job_url !== undefined ? { ciJobUrl: ci_job_url } : {}),
444
+ ...(skills !== undefined ? { skills } : {}),
445
+ callerId,
446
+ });
151
447
  }
152
448
  catch (err) {
153
449
  if (err instanceof PipelineValidationError) {
450
+ writeAudit(auditLog, {
451
+ caller_id: callerId,
452
+ surface: getRequestAuth()?.surface ?? "mcp",
453
+ action: "start_run",
454
+ outcome: "error",
455
+ error_code: "pipeline_validation",
456
+ });
154
457
  return textResult({ error: "Pipeline validation failed", validation: err.result }, true);
155
458
  }
459
+ if (err instanceof PipelinePreflightError) {
460
+ writeAudit(auditLog, {
461
+ caller_id: callerId,
462
+ surface: getRequestAuth()?.surface ?? "mcp",
463
+ action: "start_run",
464
+ outcome: "error",
465
+ error_code: err.code,
466
+ });
467
+ return textResult(err.toNetworkBody(), true);
468
+ }
156
469
  throw err;
157
470
  }
158
471
  if (!result.ok) {
472
+ writeAudit(auditLog, {
473
+ caller_id: callerId,
474
+ surface: getRequestAuth()?.surface ?? "mcp",
475
+ action: "start_run",
476
+ outcome: "error",
477
+ error_code: result.code,
478
+ });
159
479
  const { ok: _ok, reason, ...rest } = result;
160
480
  return textResult({ error: reason, ...rest }, true);
161
481
  }
162
- return textResult({ runId: result.runId });
482
+ writeAudit(auditLog, {
483
+ caller_id: callerId,
484
+ surface: getRequestAuth()?.surface ?? "mcp",
485
+ action: "start_run",
486
+ target_run_id: result.runId,
487
+ outcome: "ok",
488
+ });
489
+ return textResult({
490
+ runId: result.runId,
491
+ ...(result.queued === true
492
+ ? {
493
+ queued: true,
494
+ queuePosition: result.queuePosition,
495
+ ...(result.queuedCode !== undefined
496
+ ? { queuedCode: result.queuedCode }
497
+ : {}),
498
+ }
499
+ : {}),
500
+ });
501
+ });
502
+ server.registerTool("run_stage", {
503
+ description: "Run a single stage directly, without authoring a pipeline. `stage` is a filesystem path to a stage YAML file, or a bare inline stage body ({ id, system_prompt, io, model?, gate_kinds?, mcp?, verify?, timeout_ms? } — no uses:/route/pipeline wrapper), and exactly one of task_path, an inline task, or envelope_ref ({ runId, stageId, attempt? }) to resolve a previously stored envelope — from another run_stage call or any stage in a full pipeline run — as this stage's input. Internally this synthesizes a one-stage pipeline and executes it through the normal run path, so it shows up in list_runs/get_run and is polled with wait_run / get_envelope exactly like any other run. By default returns { runId, stageId } immediately (async); pass blocking:true to wait in this same call and get back { runId, stageId, status: \"completed\"|\"needs_input\"|\"timeout\", envelope? , pending_prompt? }.",
504
+ inputSchema: runStageSchema,
505
+ }, async ({ stage, project_root, task_path, task, envelope_ref, checkout, model, blocking, timeout_ms }) => {
506
+ let taskInput = task_path ?? task;
507
+ if (envelope_ref) {
508
+ try {
509
+ taskInput = await resolveEnvelopeRefsToTask(store, envelope_ref, checkout);
510
+ }
511
+ catch (err) {
512
+ if (err instanceof EnvelopeRefError) {
513
+ return textResult({ error: err.message, status: err.status }, true);
514
+ }
515
+ throw err;
516
+ }
517
+ }
518
+ if (taskInput === undefined) {
519
+ return textResult({ error: "Exactly one of task_path, task, or envelope_ref is required" }, true);
520
+ }
521
+ let stageBody;
522
+ let stageProjectRoot;
523
+ if (typeof stage === "string") {
524
+ if (!stage.trim()) {
525
+ return textResult({ error: "stage is required" }, true);
526
+ }
527
+ let absPath = "";
528
+ try {
529
+ const { wireRoot, roots } = await resolveCatalogStartInput({ store: deps.store, bootCwd: deps.cwd }, project_root);
530
+ stageProjectRoot = wireRoot.path;
531
+ absPath = resolveCatalogRelativePath({
532
+ inputPath: stage,
533
+ projectRoot: wireRoot.project_root,
534
+ roots,
535
+ fieldName: "stage",
536
+ }).absolutePath;
537
+ }
538
+ catch (err) {
539
+ if (err instanceof CatalogPathError) {
540
+ return textResult(catalogPathErrorBody(err), true);
541
+ }
542
+ throw err;
543
+ }
544
+ try {
545
+ stageBody = await readYamlObject(absPath);
546
+ }
547
+ catch (err) {
548
+ const message = err instanceof Error ? err.message : String(err);
549
+ return textResult({ error: `Failed to read stage file: ${message}` }, true);
550
+ }
551
+ }
552
+ else {
553
+ stageBody = stage;
554
+ }
555
+ if (typeof stageBody.id !== "string" || !stageBody.id.trim()) {
556
+ return textResult({ error: "stage.id is required" }, true);
557
+ }
558
+ const stageId = stageBody.id;
559
+ // A call-level model override wins over both the stage's own declared
560
+ // model and the global default: it replaces the field the stage body
561
+ // itself would otherwise resolve through, rather than adding a new
562
+ // tier to resolveModel's stage > pipeline > global chain.
563
+ const effectiveStageBody = model !== undefined ? { ...stageBody, model } : stageBody;
564
+ const pipeline = {
565
+ id: `standalone-${stageId}`,
566
+ stages: [effectiveStageBody],
567
+ };
568
+ let result;
569
+ try {
570
+ result = await manager.startRun({
571
+ pipeline,
572
+ task: taskInput,
573
+ ...(stageProjectRoot !== undefined ? { projectRoot: stageProjectRoot } : {}),
574
+ });
575
+ }
576
+ catch (err) {
577
+ if (err instanceof PipelineValidationError) {
578
+ return textResult({ error: "Stage validation failed", validation: err.result }, true);
579
+ }
580
+ throw err;
581
+ }
582
+ if (!result.ok) {
583
+ const { ok: _ok, reason, ...rest } = result;
584
+ return textResult({ error: reason, ...rest }, true);
585
+ }
586
+ const runId = result.runId;
587
+ if (!blocking) {
588
+ return textResult({ runId, stageId });
589
+ }
590
+ const waited = await waitRun({ store, runId, timeoutMs: timeout_ms, until: "any" });
591
+ if (!waited.ok) {
592
+ return textResult({
593
+ error: waited.error,
594
+ ...(waited.status !== undefined ? { status: waited.status } : {}),
595
+ ...(waited.code !== undefined ? { code: waited.code } : {}),
596
+ }, true);
597
+ }
598
+ const stageProjection = waited.run.stages.find((s) => s.stage_id === stageId);
599
+ if (isTerminalProjection(waited.run)) {
600
+ return textResult({
601
+ runId,
602
+ stageId,
603
+ status: "completed",
604
+ envelope: stageProjection?.envelope ?? null,
605
+ });
606
+ }
607
+ if (isWaitingProjection(waited.run)) {
608
+ return textResult({
609
+ runId,
610
+ stageId,
611
+ status: "needs_input",
612
+ pending_prompt: stageProjection?.pending_prompt,
613
+ waiting_kind: waited.run.waiting_kind,
614
+ waiting_prompt_id: waited.run.waiting_prompt_id,
615
+ waiting_summary: waited.run.waiting_summary,
616
+ });
617
+ }
618
+ return textResult({ runId, stageId, status: "timeout" });
163
619
  });
164
620
  server.registerTool("get_run", {
165
621
  description: "Poll a run: status, stage statuses, waiting_* / pending_prompt HITL fields, and envelope summary/payload/artifact paths (no events). Use list_stage_events, get_envelope, or get_stage_verification for stage detail.",
@@ -169,7 +625,25 @@ export function registerCatalogTools(server, deps) {
169
625
  }, async ({ runId }) => {
170
626
  try {
171
627
  const detail = await store.readRun(runId);
172
- return textResult(projectRunForMcp(detail));
628
+ return textResult({
629
+ ...projectRunForMcp(detail),
630
+ run_manifest: redactRunManifestForRead(detail.run_manifest),
631
+ });
632
+ }
633
+ catch (err) {
634
+ const mapped = mapStoreLookupError(err, { policy: "run" });
635
+ return textResult({ error: mapped.error, status: mapped.status }, true);
636
+ }
637
+ });
638
+ server.registerTool("export_run", {
639
+ description: "Export a single run as projectRun projection plus run_manifest. Works for running, cancelled, and terminal runs. Prefer over CLI sf export-run when the Host is reachable.",
640
+ inputSchema: z.object({
641
+ runId: z.string(),
642
+ }),
643
+ }, async ({ runId }) => {
644
+ try {
645
+ const detail = await store.readRun(runId);
646
+ return textResult(buildRunExportPayload(detail));
173
647
  }
174
648
  catch (err) {
175
649
  const mapped = mapStoreLookupError(err, { policy: "run" });
@@ -222,14 +696,21 @@ export function registerCatalogTools(server, deps) {
222
696
  else if (task !== undefined && task.trim())
223
697
  scope = "task";
224
698
  if (scope === "full") {
225
- const roots = await catalogRootsFor(deps);
699
+ const roots = await resolveCatalogRoots({
700
+ store: deps.store,
701
+ bootCwd: deps.cwd,
702
+ });
226
703
  const results = [];
227
704
  for (const root of roots) {
228
705
  try {
229
- results.push(await validateCatalog({ cwd: root, scope: "full", strict: strict ?? false }));
706
+ results.push(await validateCatalog({
707
+ cwd: root.path,
708
+ scope: "full",
709
+ strict: strict ?? false,
710
+ }));
230
711
  }
231
712
  catch {
232
- // stale/unreadable project root recorded on an old run; skip it
713
+ // unreadable root; skip
233
714
  }
234
715
  }
235
716
  const merged = {
@@ -244,13 +725,33 @@ export function registerCatalogTools(server, deps) {
244
725
  return textResult(merged);
245
726
  }
246
727
  const target = scope === "pipeline" ? pipeline.trim() : task.trim();
247
- const projectRoot = projectRootForPath(deps, target);
728
+ const roots = await resolveCatalogRoots({
729
+ store: deps.store,
730
+ bootCwd: deps.cwd,
731
+ });
732
+ let resolvedTarget = target;
733
+ let projectRoot = deps.cwd;
734
+ try {
735
+ const resolved = resolveCatalogRelativePath({
736
+ inputPath: target,
737
+ roots,
738
+ fieldName: scope === "pipeline" ? "pipeline" : "task",
739
+ });
740
+ resolvedTarget = resolved.absolutePath;
741
+ projectRoot = resolved.root.path;
742
+ }
743
+ catch (err) {
744
+ if (err instanceof CatalogPathError) {
745
+ return textResult(catalogPathErrorBody(err), true);
746
+ }
747
+ throw err;
748
+ }
248
749
  const result = await validateCatalog({
249
750
  cwd: projectRoot,
250
751
  scope,
251
752
  projectRoot,
252
- ...(scope === "pipeline" ? { pipeline: target } : {}),
253
- ...(scope === "task" ? { task: target } : {}),
753
+ ...(scope === "pipeline" ? { pipeline: resolvedTarget } : {}),
754
+ ...(scope === "task" ? { task: resolvedTarget } : {}),
254
755
  strict: strict ?? false,
255
756
  });
256
757
  return textResult(result);
@@ -261,23 +762,37 @@ export function registerCatalogTools(server, deps) {
261
762
  }
262
763
  });
263
764
  server.registerTool("describe_pipeline", {
264
- description: "Describe a pipeline DAG from a filesystem path (same as start_run): stages with inbound needs (id, on, optional if), fork, gate_kinds, Clone Chain clone_cap and clone_mode, and feedback_loop, entry, and replay_safe when set.",
765
+ description: "Describe a pipeline DAG from a catalog-relative path (same path contract as start_run): stages with inbound needs (id, on, optional if), fork, gate_kinds, Clone Chain clone_cap and clone_mode, and feedback_loop, entry, and replay_safe when set.",
265
766
  inputSchema: z.object({
266
767
  pipeline: z.string(),
768
+ project_root: z.string().optional(),
267
769
  }),
268
- }, async ({ pipeline }) => {
770
+ }, async ({ pipeline, project_root }) => {
269
771
  if (!pipeline.trim()) {
270
772
  return textResult({ error: "pipeline is required", status: 400 }, true);
271
773
  }
272
774
  try {
273
- const loaded = await loadPipeline(pipeline.trim(), {
274
- cwd: projectRootForPath(deps, pipeline.trim()),
775
+ const roots = await resolveCatalogRoots({
776
+ store: deps.store,
777
+ bootCwd: deps.cwd,
778
+ });
779
+ const resolved = resolveCatalogRelativePath({
780
+ inputPath: pipeline.trim(),
781
+ projectRoot: project_root,
782
+ roots,
783
+ fieldName: "pipeline",
784
+ });
785
+ const loaded = await loadPipeline(resolved.absolutePath, {
786
+ projectRoot: resolved.root.path,
275
787
  });
276
788
  return textResult(describePipeline(loaded));
277
789
  }
278
790
  catch (err) {
791
+ if (err instanceof CatalogPathError) {
792
+ return textResult(catalogPathErrorBody(err), true);
793
+ }
279
794
  const message = err instanceof Error ? err.message : String(err);
280
- return textResult({ error: message, status: 404 }, true);
795
+ return textResult({ error: message, status: 400 }, true);
281
796
  }
282
797
  });
283
798
  }