stageflow 0.32.0 → 0.35.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 (335) hide show
  1. package/README.md +4 -3
  2. package/dist/agent/adapterSpill.d.ts +14 -0
  3. package/dist/agent/adapterSpill.js +83 -0
  4. package/dist/agent/agentBackend.d.ts +2 -2
  5. package/dist/agent/agentBackend.js +2 -2
  6. package/dist/agent/claudeAdapter.d.ts +13 -2
  7. package/dist/agent/claudeAdapter.js +50 -6
  8. package/dist/agent/cursorProvider.d.ts +3 -0
  9. package/dist/agent/cursorProvider.js +23 -6
  10. package/dist/agent/fakeAgent.d.ts +3 -3
  11. package/dist/agent/fakeAgent.js +0 -6
  12. package/dist/agent/piAdapter.d.ts +2 -2
  13. package/dist/agent/piAdapter.js +116 -15
  14. package/dist/agent/piIsolatedMcp.d.ts +1 -0
  15. package/dist/agent/piIsolatedMcp.js +19 -1
  16. package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
  17. package/dist/agent/piIsolatedMcpProbe.js +20 -0
  18. package/dist/agent/port.d.ts +2 -2
  19. package/dist/agent/providerAuth.d.ts +6 -0
  20. package/dist/agent/providerAuth.js +86 -17
  21. package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -6
  22. package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +13 -9
  23. package/dist/agent/stageTurnDriver.d.ts +33 -0
  24. package/dist/agent/stageTurnDriver.js +64 -0
  25. package/dist/agent/toolOutputBudget.d.ts +46 -0
  26. package/dist/agent/toolOutputBudget.js +97 -0
  27. package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
  28. package/dist/agent/toolOutputBudgetExtension.js +141 -0
  29. package/dist/agent/toolOutputSpill.d.ts +34 -0
  30. package/dist/agent/toolOutputSpill.js +85 -0
  31. package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
  32. package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
  33. package/dist/browser/anchor.d.ts +2 -0
  34. package/dist/browser/anchor.js +2 -1
  35. package/dist/browser/auditSink.d.ts +8 -0
  36. package/dist/browser/browserHost.d.ts +29 -0
  37. package/dist/browser/browserSweep.d.ts +4 -1
  38. package/dist/browser/browserSweep.js +7 -0
  39. package/dist/browser/browserTeardown.d.ts +1 -0
  40. package/dist/browser/browserTeardown.js +32 -2
  41. package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
  42. package/dist/browser/containerBrowserEndpoint.js +57 -0
  43. package/dist/browser/containerBrowserHost.d.ts +24 -0
  44. package/dist/browser/containerBrowserHost.js +147 -0
  45. package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
  46. package/dist/browser/dockerSandboxOrchestrator.js +142 -0
  47. package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
  48. package/dist/browser/fakeLiveViewRelay.js +94 -0
  49. package/dist/browser/fakeLiveViewSource.d.ts +6 -0
  50. package/dist/browser/fakeLiveViewSource.js +16 -0
  51. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
  52. package/dist/browser/fakeRemoteBrowserHost.js +2 -1
  53. package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
  54. package/dist/browser/fakeSandboxOrchestrator.js +44 -0
  55. package/dist/browser/gateHandoff.d.ts +0 -2
  56. package/dist/browser/gateHandoff.js +5 -11
  57. package/dist/browser/hostCapabilities.d.ts +23 -0
  58. package/dist/browser/hostCapabilities.js +23 -0
  59. package/dist/browser/humanLogin.d.ts +2 -2
  60. package/dist/browser/humanLogin.js +11 -2
  61. package/dist/browser/liveViewPersisted.d.ts +11 -0
  62. package/dist/browser/liveViewPersisted.js +22 -0
  63. package/dist/browser/liveViewRelay.d.ts +76 -0
  64. package/dist/browser/liveViewRelay.js +1 -0
  65. package/dist/browser/liveViewSessions.d.ts +25 -0
  66. package/dist/browser/liveViewSessions.js +139 -0
  67. package/dist/browser/liveViewSource.d.ts +25 -0
  68. package/dist/browser/liveViewSource.js +7 -0
  69. package/dist/browser/liveViewTickets.d.ts +46 -0
  70. package/dist/browser/liveViewTickets.js +122 -0
  71. package/dist/browser/localBrowserHost.d.ts +10 -0
  72. package/dist/browser/localBrowserHost.js +53 -7
  73. package/dist/browser/ownerScope.d.ts +10 -0
  74. package/dist/browser/ownerScope.js +6 -0
  75. package/dist/browser/profileStore.d.ts +5 -2
  76. package/dist/browser/profileStore.js +3 -5
  77. package/dist/browser/sandboxOrchestrator.d.ts +60 -0
  78. package/dist/browser/sandboxOrchestrator.js +8 -0
  79. package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
  80. package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
  81. package/dist/browser/stageBrowserEnv.d.ts +7 -0
  82. package/dist/browser/stageBrowserEnv.js +53 -15
  83. package/dist/browser/stageHandoff.d.ts +47 -0
  84. package/dist/browser/stageHandoff.js +101 -0
  85. package/dist/browser/stageProfileLock.d.ts +1 -0
  86. package/dist/browser/stageProfileLock.js +1 -3
  87. package/dist/browser/volumeProfileStore.d.ts +7 -0
  88. package/dist/browser/volumeProfileStore.js +35 -0
  89. package/dist/cli/browserCommand.d.ts +3 -1
  90. package/dist/cli/browserCommand.js +25 -20
  91. package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
  92. package/dist/cli/resolveCliProjectRoot.js +27 -0
  93. package/dist/cli/runCommand.d.ts +1 -1
  94. package/dist/cli/runCommand.js +59 -17
  95. package/dist/cli/skillsCommand.d.ts +20 -0
  96. package/dist/cli/skillsCommand.js +74 -31
  97. package/dist/cli/validateCommand.d.ts +1 -1
  98. package/dist/cli/validateCommand.js +22 -4
  99. package/dist/cli.js +4 -4
  100. package/dist/config/browseCatalog.d.ts +2 -0
  101. package/dist/config/browseCatalog.js +6 -0
  102. package/dist/config/catalogRelativePath.d.ts +4 -0
  103. package/dist/config/catalogRelativePath.js +37 -9
  104. package/dist/config/createStage.d.ts +1 -0
  105. package/dist/config/createStage.js +11 -2
  106. package/dist/config/createTask.d.ts +30 -0
  107. package/dist/config/createTask.js +161 -0
  108. package/dist/config/createTrigger.d.ts +15 -0
  109. package/dist/config/createTrigger.js +32 -25
  110. package/dist/config/draftPackage.d.ts +41 -0
  111. package/dist/config/draftPackage.js +216 -23
  112. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  113. package/dist/config/ensureCatalogScanRoot.js +6 -2
  114. package/dist/config/findTaskInCatalog.d.ts +33 -0
  115. package/dist/config/findTaskInCatalog.js +69 -0
  116. package/dist/config/hostConfig.d.ts +21 -0
  117. package/dist/config/hostConfig.js +113 -1
  118. package/dist/config/importTask.d.ts +19 -0
  119. package/dist/config/importTask.js +133 -0
  120. package/dist/config/installSkillRequest.d.ts +40 -0
  121. package/dist/config/installSkillRequest.js +286 -0
  122. package/dist/config/listConfig.d.ts +0 -16
  123. package/dist/config/listConfig.js +0 -3
  124. package/dist/config/loadOutcome.d.ts +11 -6
  125. package/dist/config/loadPipeline.js +0 -1
  126. package/dist/config/loadStage.js +2 -0
  127. package/dist/config/loadStageflowManifest.js +4 -1
  128. package/dist/config/loadTask.js +2 -0
  129. package/dist/config/loadTrigger.js +2 -0
  130. package/dist/config/mergePipelineIncludes.js +3 -0
  131. package/dist/config/normalizePipelineStageEntry.js +10 -1
  132. package/dist/config/printTargetYaml.js +0 -1
  133. package/dist/config/publishDraftPackage.d.ts +5 -1
  134. package/dist/config/publishDraftPackage.js +4 -1
  135. package/dist/config/resolveCatalogRoots.d.ts +4 -1
  136. package/dist/config/resolveCatalogRoots.js +18 -1
  137. package/dist/config/skillUsageIndex.d.ts +6 -0
  138. package/dist/config/skillUsageIndex.js +39 -0
  139. package/dist/config/stageBrowser.js +13 -0
  140. package/dist/config/triggerAdapterStatus.d.ts +11 -0
  141. package/dist/config/triggerAdapterStatus.js +90 -0
  142. package/dist/config/triggerCatalog.d.ts +41 -2
  143. package/dist/config/triggerCatalog.js +107 -15
  144. package/dist/config/updateTask.d.ts +30 -0
  145. package/dist/config/updateTask.js +119 -0
  146. package/dist/config/updateTrigger.d.ts +38 -0
  147. package/dist/config/updateTrigger.js +220 -0
  148. package/dist/config/validateCatalog.d.ts +6 -2
  149. package/dist/config/validateCatalog.js +48 -7
  150. package/dist/config/workshopCatalogFilenames.d.ts +37 -0
  151. package/dist/config/workshopCatalogFilenames.js +125 -0
  152. package/dist/config/workshopCatalogRepair.d.ts +7 -0
  153. package/dist/config/workshopCatalogRepair.js +144 -0
  154. package/dist/config/yamlParsePosition.d.ts +4 -0
  155. package/dist/config/yamlParsePosition.js +9 -0
  156. package/dist/hosted/containment.d.ts +3 -0
  157. package/dist/hosted/containment.js +36 -0
  158. package/dist/hosted/errors.d.ts +21 -0
  159. package/dist/hosted/errors.js +30 -0
  160. package/dist/hosted/packageSkills.d.ts +3 -0
  161. package/dist/hosted/packageSkills.js +80 -0
  162. package/dist/hosted/runtime.d.ts +12 -0
  163. package/dist/hosted/runtime.js +596 -0
  164. package/dist/hosted/sequencer.d.ts +28 -0
  165. package/dist/hosted/sequencer.js +129 -0
  166. package/dist/hosted/translate.d.ts +3 -0
  167. package/dist/hosted/translate.js +43 -0
  168. package/dist/hosted/types.d.ts +108 -0
  169. package/dist/hosted/types.js +10 -0
  170. package/dist/index.d.ts +9 -1
  171. package/dist/index.js +6 -0
  172. package/dist/mcp/getStartedTool.js +15 -6
  173. package/dist/mcp/triggerTools.js +2 -8
  174. package/dist/operatorAgent/agentPort.d.ts +11 -0
  175. package/dist/operatorAgent/agentPort.js +12 -0
  176. package/dist/operatorAgent/index.d.ts +1 -0
  177. package/dist/operatorAgent/index.js +1 -0
  178. package/dist/operatorAgent/piModel.js +26 -6
  179. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  180. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
  181. package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
  182. package/dist/package-meta.d.ts +1 -1
  183. package/dist/package-meta.js +1 -1
  184. package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
  185. package/dist/project/ensureProjectRootsFromEnv.js +22 -0
  186. package/dist/project/globalHome.d.ts +1 -0
  187. package/dist/project/globalHome.js +18 -0
  188. package/dist/project/workspaceCatalog.d.ts +10 -0
  189. package/dist/project/workspaceCatalog.js +91 -0
  190. package/dist/projection/projectRun.d.ts +0 -1
  191. package/dist/projection/projectRun.js +0 -1
  192. package/dist/runstore/backup.js +14 -4
  193. package/dist/runstore/port.d.ts +11 -1
  194. package/dist/runstore/restore.js +5 -2
  195. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
  196. package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
  197. package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
  198. package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
  199. package/dist/runstore/sqlite/migrations/index.js +2 -0
  200. package/dist/runstore/stageSnapshot.d.ts +3 -1
  201. package/dist/runstore/stageSnapshot.js +19 -0
  202. package/dist/runstore/workspaceLayout.js +27 -0
  203. package/dist/runstore/writeContainedArtifact.d.ts +1 -0
  204. package/dist/runstore/writeContainedArtifact.js +52 -0
  205. package/dist/runtime/containerLimits.d.ts +0 -3
  206. package/dist/runtime/containerLimits.js +0 -5
  207. package/dist/runtime/credentialBinding.d.ts +3 -0
  208. package/dist/runtime/credentialBinding.js +23 -6
  209. package/dist/runtime/emailSource.js +20 -28
  210. package/dist/runtime/githubPollSource.js +20 -28
  211. package/dist/runtime/pipelineRunner.d.ts +4 -4
  212. package/dist/runtime/pipelineScheduler.d.ts +2 -2
  213. package/dist/runtime/pipelineScheduler.js +3 -0
  214. package/dist/runtime/resumeReconstruct.d.ts +2 -2
  215. package/dist/runtime/resumeTimedOut.d.ts +2 -2
  216. package/dist/runtime/runManager.d.ts +18 -2
  217. package/dist/runtime/runManager.js +95 -30
  218. package/dist/runtime/runRetentionSweep.d.ts +0 -2
  219. package/dist/runtime/runRetentionSweep.js +0 -8
  220. package/dist/runtime/runRetryCoordinator.d.ts +2 -2
  221. package/dist/runtime/scheduleSource.js +15 -24
  222. package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
  223. package/dist/runtime/stageAttemptBootstrap.js +10 -1
  224. package/dist/runtime/stageProcessLauncher.js +2 -2
  225. package/dist/runtime/stageRoots.js +12 -1
  226. package/dist/runtime/stageRunner.d.ts +2 -2
  227. package/dist/runtime/stageRunner.js +8 -2
  228. package/dist/runtime/stageWorker.js +2 -2
  229. package/dist/runtime/triggerPort.d.ts +5 -3
  230. package/dist/runtime/triggerRunner.d.ts +1 -0
  231. package/dist/runtime/triggerRunner.js +27 -20
  232. package/dist/runtime/webhookAccept.d.ts +25 -0
  233. package/dist/runtime/webhookAccept.js +56 -0
  234. package/dist/server/bootstrap.d.ts +7 -2
  235. package/dist/server/bootstrap.js +15 -1
  236. package/dist/server/controlToken.d.ts +1 -1
  237. package/dist/server/controlToken.js +18 -2
  238. package/dist/server/healthSurfaces.d.ts +12 -0
  239. package/dist/server/healthSurfaces.js +27 -6
  240. package/dist/server/http.d.ts +19 -10
  241. package/dist/server/http.js +605 -85
  242. package/dist/server/liveViewPath.d.ts +9 -0
  243. package/dist/server/liveViewPath.js +8 -0
  244. package/dist/server/liveViewRoutes.d.ts +58 -0
  245. package/dist/server/liveViewRoutes.js +565 -0
  246. package/dist/server/mcpHost.d.ts +4 -2
  247. package/dist/server/mcpHost.js +2 -0
  248. package/dist/server/readCatalogFile.d.ts +29 -0
  249. package/dist/server/readCatalogFile.js +215 -0
  250. package/dist/server/triggerRoutes.d.ts +1 -0
  251. package/dist/server/triggerRoutes.js +200 -73
  252. package/dist/tools/readEmail.d.ts +3 -3
  253. package/dist/tools/writeStageArtifact.d.ts +9 -0
  254. package/dist/tools/writeStageArtifact.js +2 -47
  255. package/dist/types/pipeline.d.ts +1 -1
  256. package/dist/types/stage.d.ts +1 -1
  257. package/dist/types/stageflowManifest.d.ts +1 -1
  258. package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  259. package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  260. package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  261. package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  262. package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  263. package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  264. package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  265. package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  266. package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  267. package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  268. package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  269. package/dist/ui/assets/index-BoFPkKFg.js +256 -0
  270. package/dist/ui/assets/{index-Bv4p1aPK.css → index-CIPbbBUi.css} +1 -1
  271. package/dist/ui/index.html +4 -4
  272. package/dist/workshop/autosave.d.ts +2 -2
  273. package/dist/workshop/autosave.js +4 -6
  274. package/dist/workshop/buildStore.d.ts +2 -0
  275. package/dist/workshop/chatAttachments.d.ts +27 -0
  276. package/dist/workshop/chatAttachments.js +98 -0
  277. package/dist/workshop/chatTurn.d.ts +5 -2
  278. package/dist/workshop/chatTurn.js +22 -18
  279. package/dist/workshop/sessionMutations.d.ts +17 -0
  280. package/dist/workshop/sessionMutations.js +107 -0
  281. package/dist/workshop/sessionStore.d.ts +10 -0
  282. package/dist/workshop/sessionStore.js +52 -0
  283. package/dist/workshop/workshopPin.d.ts +4 -3
  284. package/dist/workshop/workshopPin.js +26 -6
  285. package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
  286. package/dist/workshop/workshopTranscriptTurn.js +46 -0
  287. package/examples/README.md +1 -1
  288. package/examples/archify-on-pr/README.md +1 -1
  289. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
  290. package/examples/archify-on-pr/author-diagrams.yaml +1 -0
  291. package/examples/conditional-fork/branch-a.yaml +1 -1
  292. package/examples/conditional-fork/decide.yaml +2 -1
  293. package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
  294. package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
  295. package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
  296. package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
  297. package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
  298. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
  299. package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
  300. package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
  301. package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
  302. package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
  303. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
  304. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
  305. package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
  306. package/examples/hello-world/hello.pipeline.yaml +2 -2
  307. package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
  308. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
  309. package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
  310. package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
  311. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
  312. package/examples/stageflow.yaml +6 -0
  313. package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
  314. package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
  315. package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
  316. package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
  317. package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
  318. package/examples/triggers-readiness/README.md +48 -0
  319. package/examples/triggers-readiness/readiness-emit.yaml +18 -0
  320. package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
  321. package/examples/triggers-readiness/readiness.task.yaml +6 -0
  322. package/examples/triggers-readiness/stageflow.yaml +13 -0
  323. package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
  324. package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
  325. package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
  326. package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
  327. package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
  328. package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
  329. package/package.json +9 -1
  330. package/skills/stageflow/references/control-surface.md +1 -1
  331. package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
  332. package/skills/stageflow-run/SKILL.md +1 -1
  333. package/skills/stageflow-run/references/mcp-call.md +1 -1
  334. package/skills/stageflow-setup/SKILL.md +1 -1
  335. package/dist/ui/assets/index-59cjF0OA.js +0 -133
@@ -2,7 +2,8 @@ import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { parse as parseYaml } from "yaml";
4
4
  import { coerceTaskFile } from "../config/loadTask.js";
5
- import { relativizeLocalPathForNetwork } from "../config/catalogRelativePath.js";
5
+ import { isInsideProjectRoot, relativizeLocalPathForNetwork, } from "../config/catalogRelativePath.js";
6
+ import { resolveCliProjectRootLocal } from "./resolveCliProjectRoot.js";
6
7
  import { PipelinePreflightError, PipelineValidationError } from "../runtime/pipelineRunner.js";
7
8
  import { ensureGlobalService, hostBaseUrl, } from "../server/ensureGlobalService.js";
8
9
  import { httpEnsureProject, httpStartRun, httpStoreReader } from "./hostClient.js";
@@ -10,7 +11,7 @@ import { reportCliRun, writeQueuedAdmissionLine, } from "./runOutput.js";
10
11
  import { resolveCiIdentity } from "./ciIdentity.js";
11
12
  import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
12
13
  export const RUN_USAGE = `Usage:
13
- sf run --task <path> --pipeline <path> [--checkout <path>] [--repository <owner/repo>] [--ref <ref>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]`;
14
+ sf run --task <path> --pipeline <path> [--project-root <id-or-path>] [--checkout <path>] [--repository <owner/repo>] [--ref <ref>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]`;
14
15
  const defaultIo = {
15
16
  log: (line) => console.log(line),
16
17
  error: (line) => console.error(line),
@@ -32,6 +33,7 @@ function parseRunArgs(args) {
32
33
  let ciJobUrl;
33
34
  let operatorCwd;
34
35
  let operatorAgentDir;
36
+ let projectRoot;
35
37
  let json = false;
36
38
  let includeStages = false;
37
39
  let skipGates = false;
@@ -111,6 +113,13 @@ function parseRunArgs(args) {
111
113
  }
112
114
  operatorAgentDir = value;
113
115
  }
116
+ else if (arg === "--project-root") {
117
+ const value = args[++i];
118
+ if (value === undefined || value.length === 0) {
119
+ throw new Error("Missing value for --project-root");
120
+ }
121
+ projectRoot = value;
122
+ }
114
123
  else if (arg === "--json") {
115
124
  json = true;
116
125
  }
@@ -151,8 +160,16 @@ function parseRunArgs(args) {
151
160
  ciJobUrl,
152
161
  operatorCwd,
153
162
  operatorAgentDir,
163
+ projectRoot,
154
164
  };
155
165
  }
166
+ function resolvePathUnderCliRoot(rootPath, inputPath, label) {
167
+ const abs = path.resolve(rootPath, inputPath);
168
+ if (!isInsideProjectRoot(rootPath, abs)) {
169
+ throw new Error(`${label} ${abs} is outside project root ${rootPath}`);
170
+ }
171
+ return path.relative(rootPath, abs).replace(/\\/g, "/") || ".";
172
+ }
156
173
  async function loadTaskWithBindingOverrides(taskPath, cwd, overrides) {
157
174
  const absolute = path.resolve(cwd, taskPath);
158
175
  const yamlText = await readFile(absolute, "utf8");
@@ -167,7 +184,7 @@ async function loadTaskWithBindingOverrides(taskPath, cwd, overrides) {
167
184
  task.ref = overrides.ref;
168
185
  return task;
169
186
  }
170
- function defaultStartRun(cwd, base, ensureService) {
187
+ function defaultStartRun(cwd, base, ensureService, cliProjectRoot) {
171
188
  return async (input) => {
172
189
  const ensured = await ensureService();
173
190
  if (!ensured.ok) {
@@ -180,20 +197,34 @@ function defaultStartRun(cwd, base, ensureService) {
180
197
  : {}),
181
198
  };
182
199
  }
183
- const pipelineRef = relativizeLocalPathForNetwork(cwd, input.pipeline);
200
+ let pipelineRef;
184
201
  let task = input.task;
185
- let projectRoot = pipelineRef.project_root;
186
- if (typeof input.task === "string") {
187
- const absTask = path.resolve(cwd, input.task);
188
- const cwdAbs = path.resolve(cwd);
189
- const relTask = path.relative(cwdAbs, absTask);
190
- if (relTask.startsWith("..") || path.isAbsolute(relTask)) {
191
- task = await loadTaskWithBindingOverrides(input.task, cwd, {});
202
+ let projectRoot;
203
+ if (cliProjectRoot) {
204
+ pipelineRef = {
205
+ path: resolvePathUnderCliRoot(cliProjectRoot.rootPath, input.pipeline, "Pipeline"),
206
+ project_root: cliProjectRoot.wireId,
207
+ };
208
+ projectRoot = cliProjectRoot.wireId;
209
+ if (typeof input.task === "string") {
210
+ task = resolvePathUnderCliRoot(cliProjectRoot.rootPath, input.task, "Task");
192
211
  }
193
- else {
194
- const taskRef = relativizeLocalPathForNetwork(cwd, input.task);
195
- task = taskRef.path;
196
- projectRoot = taskRef.project_root;
212
+ }
213
+ else {
214
+ pipelineRef = relativizeLocalPathForNetwork(cwd, input.pipeline);
215
+ projectRoot = pipelineRef.project_root;
216
+ if (typeof input.task === "string") {
217
+ const absTask = path.resolve(cwd, input.task);
218
+ const cwdAbs = path.resolve(cwd);
219
+ const relTask = path.relative(cwdAbs, absTask);
220
+ if (relTask.startsWith("..") || path.isAbsolute(relTask)) {
221
+ task = await loadTaskWithBindingOverrides(input.task, cwd, {});
222
+ }
223
+ else {
224
+ const taskRef = relativizeLocalPathForNetwork(cwd, input.task);
225
+ task = taskRef.path;
226
+ projectRoot = taskRef.project_root;
227
+ }
197
228
  }
198
229
  }
199
230
  const ensuredProject = await httpEnsureProject(base, projectRoot);
@@ -253,8 +284,18 @@ export async function runRunCommand(args, options = {}) {
253
284
  if (parsed.operatorCwd !== undefined || parsed.operatorAgentDir !== undefined) {
254
285
  out.error("warning: --operator-cwd/--operator-agent-dir have no effect on `sf run` now that pipelines execute in the shared global Stageflow service; set STAGEFLOW_OPERATOR_CWD/STAGEFLOW_OPERATOR_AGENT_DIR before that service first starts instead.");
255
286
  }
287
+ let cliProjectRoot;
288
+ if (parsed.projectRoot !== undefined) {
289
+ const local = resolveCliProjectRootLocal(parsed.projectRoot);
290
+ if (!local) {
291
+ out.error(`Unknown project_root: ${parsed.projectRoot.trim()}`);
292
+ out.error(RUN_USAGE);
293
+ return 1;
294
+ }
295
+ cliProjectRoot = local;
296
+ }
256
297
  const startRun = options.startRun ??
257
- defaultStartRun(cwd, base, options.ensureService ?? (() => ensureGlobalService()));
298
+ defaultStartRun(cwd, base, options.ensureService ?? (() => ensureGlobalService()), cliProjectRoot);
258
299
  const identity = resolveCiIdentity({
259
300
  flags: {
260
301
  gitSha: parsed.gitSha,
@@ -266,8 +307,9 @@ export async function runRunCommand(args, options = {}) {
266
307
  const store = options.store ?? (parsed.includeStages ? httpStoreReader(base) : undefined);
267
308
  try {
268
309
  const hasBindingOverride = parsed.repository !== undefined || parsed.ref !== undefined;
310
+ const taskLoadRoot = cliProjectRoot?.rootPath ?? cwd;
269
311
  const taskInput = hasBindingOverride
270
- ? await loadTaskWithBindingOverrides(parsed.task, cwd, {
312
+ ? await loadTaskWithBindingOverrides(parsed.task, taskLoadRoot, {
271
313
  repository: parsed.repository,
272
314
  ref: parsed.ref,
273
315
  })
@@ -5,6 +5,26 @@ export type SkillsCommandIo = {
5
5
  fetch: typeof globalThis.fetch;
6
6
  runDoctor: (scriptPath: string, cwd: string) => number;
7
7
  };
8
+ export type InstallProjectSkillInput = {
9
+ projectRoot: string;
10
+ fromPath?: string;
11
+ fromZip?: string;
12
+ zipBytes?: Uint8Array;
13
+ skillName?: string;
14
+ checksum?: string;
15
+ io?: Partial<SkillsCommandIo>;
16
+ };
17
+ export type InstallProjectSkillResult = {
18
+ ok: true;
19
+ skillName: string;
20
+ destPath: string;
21
+ message: string;
22
+ } | {
23
+ ok: false;
24
+ error: string;
25
+ doctorFailed?: boolean;
26
+ };
27
+ export declare function installProjectSkill(input: InstallProjectSkillInput): Promise<InstallProjectSkillResult>;
8
28
  export declare function runSkillsCommand(args: string[], options?: {
9
29
  cwd?: string;
10
30
  projectRoot?: string;
@@ -291,14 +291,18 @@ async function findSkillRootBySkillMd(dir) {
291
291
  }
292
292
  return undefined;
293
293
  }
294
- async function resolveInstallSource(parsed, io) {
295
- const hasPath = parsed.fromPath !== undefined;
296
- const hasZip = parsed.fromZip !== undefined;
297
- if (hasPath === hasZip) {
298
- throw new Error("specify exactly one of --from-path or --from-zip");
299
- }
300
- if (parsed.fromPath !== undefined) {
301
- const sourceRoot = path.resolve(parsed.fromPath);
294
+ async function resolveInstallSource(input, io) {
295
+ const hasPath = input.fromPath !== undefined;
296
+ const hasZip = input.fromZip !== undefined;
297
+ const hasZipBytes = input.zipBytes !== undefined;
298
+ const sourceCount = Number(hasPath) + Number(hasZip) + Number(hasZipBytes);
299
+ if (sourceCount !== 1) {
300
+ throw new Error(hasZipBytes
301
+ ? "specify exactly one of fromPath, fromZip, or zipBytes"
302
+ : "specify exactly one of --from-path or --from-zip");
303
+ }
304
+ if (input.fromPath !== undefined) {
305
+ const sourceRoot = path.resolve(input.fromPath);
302
306
  try {
303
307
  const sourceStat = await stat(sourceRoot);
304
308
  if (!sourceStat.isDirectory()) {
@@ -308,13 +312,21 @@ async function resolveInstallSource(parsed, io) {
308
312
  catch {
309
313
  throw new Error("source path not found");
310
314
  }
311
- const skillName = parsed.skillName ?? inferSkillNameFromPath(parsed.fromPath);
315
+ const skillName = input.skillName ?? inferSkillNameFromPath(input.fromPath);
312
316
  return { sourceRoot, skillName };
313
317
  }
314
- const zipRef = parsed.fromZip;
315
- const zipBytes = await resolveZipBytes(zipRef, io);
316
- if (parsed.checksum !== undefined) {
317
- const expected = parseChecksum(parsed.checksum);
318
+ let zipBytes;
319
+ let inferredZipName;
320
+ if (input.zipBytes !== undefined) {
321
+ zipBytes = Buffer.from(input.zipBytes);
322
+ }
323
+ else {
324
+ const zipRef = input.fromZip;
325
+ zipBytes = await resolveZipBytes(zipRef, io);
326
+ inferredZipName = inferSkillNameFromZipRef(zipRef);
327
+ }
328
+ if (input.checksum !== undefined) {
329
+ const expected = parseChecksum(input.checksum);
318
330
  const actual = sha256Hex(zipBytes);
319
331
  if (actual !== expected) {
320
332
  throw new Error("checksum mismatch");
@@ -322,8 +334,9 @@ async function resolveInstallSource(parsed, io) {
322
334
  }
323
335
  const extractDir = await mkdtemp(path.join(tmpdir(), "sf-skill-zip-"));
324
336
  await extractZipBuffer(zipBytes, extractDir);
325
- const skillName = parsed.skillName ?? inferSkillNameFromZipRef(zipRef);
326
- const sourceRoot = await findSkillRoot(extractDir, skillName);
337
+ const lookupName = input.skillName ?? inferredZipName ?? "skill";
338
+ const sourceRoot = await findSkillRoot(extractDir, lookupName);
339
+ const skillName = input.skillName ?? inferredZipName ?? path.basename(sourceRoot);
327
340
  return {
328
341
  sourceRoot,
329
342
  skillName,
@@ -364,24 +377,46 @@ async function listSkills(projectRoot, io) {
364
377
  io.log(`${name}\t${version}\tbin/${name}.mjs`);
365
378
  }
366
379
  }
367
- async function installSkill(projectRoot, parsed, io) {
368
- const resolved = await resolveInstallSource(parsed, io);
380
+ export async function installProjectSkill(input) {
381
+ const io = { ...defaultIo, ...input.io };
369
382
  try {
370
- const destRoot = path.join(skillsDirForRoot(projectRoot), resolved.skillName);
371
- await copySkillTree(resolved.sourceRoot, destRoot);
372
- const doctorScript = path.join(destRoot, "bin", `${resolved.skillName}.mjs`);
373
- const doctorCode = io.runDoctor(doctorScript, projectRoot);
374
- if (doctorCode !== 0) {
375
- io.error("skill doctor failed");
376
- return 1;
383
+ const resolved = await resolveInstallSource({
384
+ fromPath: input.fromPath,
385
+ fromZip: input.fromZip,
386
+ zipBytes: input.zipBytes,
387
+ skillName: input.skillName,
388
+ checksum: input.checksum,
389
+ }, io);
390
+ try {
391
+ const destRoot = path.join(skillsDirForRoot(input.projectRoot), resolved.skillName);
392
+ await copySkillTree(resolved.sourceRoot, destRoot);
393
+ const doctorScript = path.join(destRoot, "bin", `${resolved.skillName}.mjs`);
394
+ if (await pathExists(doctorScript)) {
395
+ const doctorCode = io.runDoctor(doctorScript, input.projectRoot);
396
+ if (doctorCode !== 0) {
397
+ io.error("skill doctor failed");
398
+ return { ok: false, error: "skill doctor failed", doctorFailed: true };
399
+ }
400
+ }
401
+ const message = `Installed skill ${resolved.skillName} to ${destRoot}`;
402
+ io.log(message);
403
+ return {
404
+ ok: true,
405
+ skillName: resolved.skillName,
406
+ destPath: destRoot,
407
+ message,
408
+ };
409
+ }
410
+ finally {
411
+ if (resolved.cleanup) {
412
+ await resolved.cleanup();
413
+ }
377
414
  }
378
- io.log(`Installed skill ${resolved.skillName} to ${destRoot}`);
379
- return 0;
380
415
  }
381
- finally {
382
- if (resolved.cleanup) {
383
- await resolved.cleanup();
384
- }
416
+ catch (err) {
417
+ const error = err instanceof Error ? err.message : String(err);
418
+ io.error(error);
419
+ return { ok: false, error };
385
420
  }
386
421
  }
387
422
  export async function runSkillsCommand(args, options = {}) {
@@ -402,7 +437,15 @@ export async function runSkillsCommand(args, options = {}) {
402
437
  await listSkills(projectRoot, io);
403
438
  return 0;
404
439
  }
405
- return await installSkill(projectRoot, parsed, io);
440
+ const result = await installProjectSkill({
441
+ projectRoot,
442
+ fromPath: parsed.fromPath,
443
+ fromZip: parsed.fromZip,
444
+ skillName: parsed.skillName,
445
+ checksum: parsed.checksum,
446
+ io,
447
+ });
448
+ return result.ok ? 0 : 1;
406
449
  }
407
450
  catch (err) {
408
451
  io.error(err instanceof Error ? err.message : String(err));
@@ -1,5 +1,5 @@
1
1
  import { validateCatalog as defaultValidateCatalog, type ValidationResult } from "../config/validateCatalog.js";
2
- export declare const VALIDATE_USAGE = "Usage:\n sf validate [--pipeline <path>] [--task <path>] [--trigger <path>] [--strict] [--json]\n\n With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).\n --pipeline validates one pipeline file (includes uses:/include: transitively).\n --task validates one task file.\n --trigger validates one trigger file, including its pipeline/task refs.";
2
+ export declare const VALIDATE_USAGE = "Usage:\n sf validate [--project-root <id-or-path>] [--pipeline <path>] [--task <path>] [--trigger <path>] [--strict] [--json]\n\n With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).\n --pipeline validates one pipeline file (includes uses:/include: transitively).\n --task validates one task file.\n --trigger validates one trigger file, including its pipeline/task refs.";
3
3
  export type ValidateCommandIo = {
4
4
  log: (line: string) => void;
5
5
  error: (line: string) => void;
@@ -1,7 +1,8 @@
1
1
  import { validateCatalog as defaultValidateCatalog, } from "../config/validateCatalog.js";
2
2
  import { exitCodeForValidation, formatValidationHuman, formatValidationJson, } from "./validateOutput.js";
3
+ import { resolveCliProjectRootLocal } from "./resolveCliProjectRoot.js";
3
4
  export const VALIDATE_USAGE = `Usage:
4
- sf validate [--pipeline <path>] [--task <path>] [--trigger <path>] [--strict] [--json]
5
+ sf validate [--project-root <id-or-path>] [--pipeline <path>] [--task <path>] [--trigger <path>] [--strict] [--json]
5
6
 
6
7
  With no flags, validates all pipelines and tasks declared in stageflow.yaml (manifest-all).
7
8
  --pipeline validates one pipeline file (includes uses:/include: transitively).
@@ -18,6 +19,7 @@ function parseValidateArgs(args) {
18
19
  if (args[0] === "--help" || args[0] === "-h") {
19
20
  return { help: true, strict: false, json: false };
20
21
  }
22
+ let projectRoot;
21
23
  let pipeline;
22
24
  let task;
23
25
  let trigger;
@@ -29,6 +31,13 @@ function parseValidateArgs(args) {
29
31
  if (arg === "--help" || arg === "-h") {
30
32
  help = true;
31
33
  }
34
+ else if (arg === "--project-root") {
35
+ const value = args[++i];
36
+ if (value === undefined || value.length === 0) {
37
+ throw new Error("Missing value for --project-root");
38
+ }
39
+ projectRoot = value;
40
+ }
32
41
  else if (arg === "--pipeline") {
33
42
  const value = args[++i];
34
43
  if (value === undefined || value.length === 0) {
@@ -67,11 +76,10 @@ function parseValidateArgs(args) {
67
76
  if (scopedFlagCount > 1) {
68
77
  throw new Error("Use at most one of --pipeline, --task, or --trigger");
69
78
  }
70
- return { help, pipeline, task, trigger, strict, json };
79
+ return { help, projectRoot, pipeline, task, trigger, strict, json };
71
80
  }
72
81
  export async function runValidateCommand(args, options = {}) {
73
- const cwd = options.cwd ?? process.cwd();
74
- const projectRoot = options.projectRoot ?? cwd;
82
+ const shellCwd = options.cwd ?? process.cwd();
75
83
  const out = { ...defaultIo, ...options.io };
76
84
  const validateCatalogFn = options.validateCatalog ?? defaultValidateCatalog;
77
85
  try {
@@ -80,6 +88,16 @@ export async function runValidateCommand(args, options = {}) {
80
88
  out.error(VALIDATE_USAGE);
81
89
  return 0;
82
90
  }
91
+ let cwd = shellCwd;
92
+ let projectRoot = options.projectRoot ?? shellCwd;
93
+ if (parsed.projectRoot !== undefined) {
94
+ const resolved = resolveCliProjectRootLocal(parsed.projectRoot);
95
+ if (!resolved) {
96
+ throw new Error(`Unknown project_root: ${parsed.projectRoot.trim()}`);
97
+ }
98
+ cwd = resolved.rootPath;
99
+ projectRoot = resolved.rootPath;
100
+ }
83
101
  const scope = parsed.pipeline
84
102
  ? "pipeline"
85
103
  : parsed.task
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { A2A_USAGE, runA2aCommand } from "./cli/a2aCommand.js";
4
4
  import { realpathSync } from "node:fs";
5
5
  import path from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
- import { globalAgentBackendFromManifest, resolveAgentPort } from "./agent/resolveAgentPort.js";
7
+ import { globalStageBackendFromManifest, resolveStagePort } from "./agent/resolveStagePort.js";
8
8
  import { BROWSER_USAGE, runBrowserCommand } from "./cli/browserCommand.js";
9
9
  import { ARTIFACT_USAGE, runArtifactCommand } from "./cli/artifactCommand.js";
10
10
  import { ENVELOPE_USAGE, runEnvelopeCommand, } from "./cli/envelopeCommand.js";
@@ -503,7 +503,7 @@ async function main(argv) {
503
503
  if (parsed.command === "browser") {
504
504
  return runBrowserCommand(argv.slice(3));
505
505
  }
506
- const globalAgent = globalAgentBackendFromManifest(ctx.manifest);
506
+ const globalAgent = globalStageBackendFromManifest(ctx.manifest);
507
507
  if (parsed.command === "ui") {
508
508
  const listenHost = resolveListenHost({ flag: parsed.host });
509
509
  const tokens = loadControlTokens();
@@ -512,7 +512,7 @@ async function main(argv) {
512
512
  mcpStateless: parsed.mcpStateless,
513
513
  });
514
514
  const host = await startUiServer({
515
- agent: resolveAgentPort({ global: globalAgent }),
515
+ agent: resolveStagePort({ global: globalAgent }),
516
516
  cwd: ctx.invocationCwd,
517
517
  rootDir: ctx.projectRoot,
518
518
  port: parsed.port,
@@ -547,7 +547,7 @@ async function main(argv) {
547
547
  mcpStateless: parsed.mcpStateless,
548
548
  });
549
549
  const host = await startMcpServer({
550
- agent: resolveAgentPort({ global: globalAgent }),
550
+ agent: resolveStagePort({ global: globalAgent }),
551
551
  cwd: ctx.invocationCwd,
552
552
  rootDir: ctx.projectRoot,
553
553
  port: parsed.port,
@@ -13,6 +13,8 @@ export type PipelineStageListing = {
13
13
  gate_kinds?: StageGateKind[];
14
14
  uses_path?: string;
15
15
  inline?: boolean;
16
+ model?: string;
17
+ skill?: string;
16
18
  };
17
19
  export type PipelineListing = {
18
20
  path: string;
@@ -22,6 +22,12 @@ function mapStageListing(stage, source, projectRoot) {
22
22
  else if (source?.kind === "file") {
23
23
  listing.uses_path = repoRelPath(projectRoot, source.path);
24
24
  }
25
+ if (typeof stage.model === "string" && stage.model.trim() !== "") {
26
+ listing.model = stage.model;
27
+ }
28
+ if (typeof stage.skill === "string" && stage.skill.trim() !== "") {
29
+ listing.skill = stage.skill;
30
+ }
25
31
  return listing;
26
32
  }
27
33
  function bakedModels() {
@@ -37,6 +37,10 @@ export declare function catalogPathErrorBody(err: CatalogPathError): {
37
37
  * otherwise require explicit — no invent.
38
38
  */
39
39
  export declare function selectCatalogRootForStart(roots: CatalogRoot[], projectRoot?: string): CatalogRoot;
40
+ export declare function resolveDefaultWriteRoot(options: ResolveCatalogRootsOptions, projectRoot?: string): Promise<{
41
+ roots: CatalogRoot[];
42
+ wireRoot: CatalogRoot;
43
+ }>;
40
44
  /**
41
45
  * Shared helper: resolveCatalogRoots → selectCatalogRootForStart.
42
46
  * All network-surface start-input call sites use this to avoid the hand-copy sequence.
@@ -82,11 +82,43 @@ export function selectCatalogRootForStart(roots, projectRoot) {
82
82
  if (roots.length === 1) {
83
83
  return roots[0];
84
84
  }
85
- const writable = roots.filter((r) => r.kind !== "seeded");
86
- if (writable.length === 1) {
87
- return writable[0];
85
+ const registeredOnly = roots.filter((r) => r.kind === "registered");
86
+ if (registeredOnly.length === 1) {
87
+ return registeredOnly[0];
88
88
  }
89
- throw new CatalogPathError("project_root is required when multiple catalog roots are configured", "unknown_project_root", registered);
89
+ const nonSeeded = roots.filter((r) => r.kind !== "seeded");
90
+ if (nonSeeded.length === 1) {
91
+ return nonSeeded[0];
92
+ }
93
+ const ids = roots.map((r) => r.project_root).join(", ");
94
+ throw new CatalogPathError(`project_root is required when multiple catalog roots are configured (${ids})`, "unknown_project_root", registered);
95
+ }
96
+ function workspaceRoot(roots) {
97
+ return roots.find((r) => r.kind === "workspace");
98
+ }
99
+ export async function resolveDefaultWriteRoot(options, projectRoot) {
100
+ const roots = await resolveCatalogRoots(options);
101
+ const registered = roots.map((r) => r.project_root);
102
+ if (projectRoot !== undefined) {
103
+ const found = findCatalogRoot(roots, projectRoot);
104
+ if (found === undefined) {
105
+ throw new CatalogPathError(`Unknown project_root: ${projectRoot}`, "unknown_project_root", registered);
106
+ }
107
+ if (found.read_only) {
108
+ throw new CatalogPathError(`Catalog root ${found.project_root} is read-only`, "catalog_root_read_only", registered);
109
+ }
110
+ if (found.kind === "workspace" && found.available === false) {
111
+ throw new CatalogPathError(found.unavailable_reason ??
112
+ `Workspace catalog is not writable (${found.path})`, "unknown_project_root", registered);
113
+ }
114
+ return { roots, wireRoot: found };
115
+ }
116
+ const ws = workspaceRoot(roots);
117
+ if (ws === undefined || ws.available === false) {
118
+ throw new CatalogPathError(ws?.unavailable_reason ??
119
+ "Workspace catalog is unavailable; set project_root explicitly", "unknown_project_root", registered);
120
+ }
121
+ return { roots, wireRoot: ws };
90
122
  }
91
123
  /**
92
124
  * Shared helper: resolveCatalogRoots → selectCatalogRootForStart.
@@ -102,11 +134,7 @@ export async function resolveCatalogStartInput(options, projectRoot) {
102
134
  * Used by write surfaces (POST /api/stages, POST /api/pipelines).
103
135
  */
104
136
  export async function resolveWritableCatalogRoot(options, projectRoot) {
105
- const { roots, wireRoot } = await resolveCatalogStartInput(options, projectRoot);
106
- if (wireRoot.read_only) {
107
- throw new CatalogPathError(`Catalog root ${wireRoot.project_root} is read-only`, "catalog_root_read_only", roots.map((r) => r.project_root));
108
- }
109
- return { roots, wireRoot };
137
+ return resolveDefaultWriteRoot(options, projectRoot);
110
138
  }
111
139
  /**
112
140
  * Local CLI control: turn a cwd-resolved filesystem path into a catalog-relative
@@ -7,6 +7,7 @@ export type CreateStageInput = {
7
7
  system_prompt: string;
8
8
  model?: string;
9
9
  gate_kinds?: StageGateKind[];
10
+ skill?: string;
10
11
  };
11
12
  export type CreatedStageListing = {
12
13
  path: string;
@@ -41,8 +41,12 @@ export function parseCreateStageBody(body) {
41
41
  if ("payload_schema" in body) {
42
42
  return { ok: false, status: 400, error: "payload_schema is not supported" };
43
43
  }
44
- if ("skill" in body) {
45
- return { ok: false, status: 400, error: "skill is not supported" };
44
+ let skill;
45
+ if (body.skill !== undefined) {
46
+ if (typeof body.skill !== "string" || body.skill.trim() === "") {
47
+ return { ok: false, status: 400, error: "skill must be a non-empty string" };
48
+ }
49
+ skill = body.skill.trim();
46
50
  }
47
51
  if (typeof body.pipeline_directory !== "string" || !body.pipeline_directory.trim()) {
48
52
  return { ok: false, status: 400, error: "pipeline_directory is required" };
@@ -84,6 +88,7 @@ export function parseCreateStageBody(body) {
84
88
  system_prompt: body.system_prompt,
85
89
  ...(model !== undefined ? { model } : {}),
86
90
  ...(gateKinds !== undefined ? { gate_kinds: gateKinds } : {}),
91
+ ...(skill !== undefined ? { skill } : {}),
87
92
  };
88
93
  }
89
94
  function formatInlineYamlScalar(value) {
@@ -122,6 +127,9 @@ export function stageConfigToYaml(stage) {
122
127
  if (stage.model !== undefined && stage.model.length > 0) {
123
128
  lines.push(`model: ${formatInlineYamlScalar(stage.model)}`);
124
129
  }
130
+ if (stage.skill !== undefined && stage.skill.length > 0) {
131
+ lines.push(`skill: ${formatInlineYamlScalar(stage.skill)}`);
132
+ }
125
133
  lines.push("io:");
126
134
  lines.push(" input:");
127
135
  lines.push(" schema:");
@@ -204,6 +212,7 @@ export async function createStage(projectRoot, input) {
204
212
  system_prompt: input.system_prompt,
205
213
  ...(input.model !== undefined && input.model.length > 0 ? { model: input.model } : {}),
206
214
  ...(input.gate_kinds !== undefined ? { gate_kinds: input.gate_kinds } : {}),
215
+ ...(input.skill !== undefined && input.skill.length > 0 ? { skill: input.skill } : {}),
207
216
  }), "utf8");
208
217
  }
209
218
  catch (err) {
@@ -0,0 +1,30 @@
1
+ import type { TaskDetail } from "./findTaskInCatalog.js";
2
+ export type CreateTaskInput = {
3
+ directory: string;
4
+ id: string;
5
+ goal: string;
6
+ context?: string;
7
+ constraints?: string;
8
+ checkout?: string;
9
+ repository?: string;
10
+ ref?: string;
11
+ };
12
+ export type CreateTaskParseError = {
13
+ ok: false;
14
+ status: 400;
15
+ error: string;
16
+ };
17
+ export type CreateTaskResult = {
18
+ ok: true;
19
+ task: TaskDetail;
20
+ } | {
21
+ ok: false;
22
+ status: 400 | 404 | 409 | 500 | 502;
23
+ error: string;
24
+ };
25
+ export declare function validateTaskId(id: string): string | null;
26
+ export declare function parseCreateTaskBody(body: unknown): CreateTaskInput | CreateTaskParseError;
27
+ export declare function resolveTaskDirectory(projectRoot: string, directory: string): string | null;
28
+ export declare function taskFieldsToYaml(fields: CreateTaskInput): string;
29
+ export declare function findTaskIdCollision(projectRoot: string, id: string, targetPath: string): Promise<string | null>;
30
+ export declare function createTask(rawProjectRoot: string, input: CreateTaskInput): Promise<CreateTaskResult>;