stageflow 0.33.0 → 0.36.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 (379) hide show
  1. package/README.md +3 -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 +56 -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/piAdapter.d.ts +2 -2
  12. package/dist/agent/piAdapter.js +125 -15
  13. package/dist/agent/piIsolatedMcp.d.ts +1 -0
  14. package/dist/agent/piIsolatedMcp.js +19 -1
  15. package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
  16. package/dist/agent/piIsolatedMcpProbe.js +20 -0
  17. package/dist/agent/port.d.ts +4 -2
  18. package/dist/agent/providerAuth.js +36 -3
  19. package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -8
  20. package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +11 -11
  21. package/dist/agent/stageTurnDriver.d.ts +33 -0
  22. package/dist/agent/stageTurnDriver.js +64 -0
  23. package/dist/agent/toolOutputBudget.d.ts +46 -0
  24. package/dist/agent/toolOutputBudget.js +97 -0
  25. package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
  26. package/dist/agent/toolOutputBudgetExtension.js +141 -0
  27. package/dist/agent/toolOutputSpill.d.ts +34 -0
  28. package/dist/agent/toolOutputSpill.js +85 -0
  29. package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
  30. package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
  31. package/dist/browser/anchor.d.ts +2 -0
  32. package/dist/browser/anchor.js +2 -1
  33. package/dist/browser/auditSink.d.ts +8 -0
  34. package/dist/browser/browserHost.d.ts +29 -0
  35. package/dist/browser/browserSweep.d.ts +4 -1
  36. package/dist/browser/browserSweep.js +7 -0
  37. package/dist/browser/browserTeardown.d.ts +1 -0
  38. package/dist/browser/browserTeardown.js +32 -2
  39. package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
  40. package/dist/browser/containerBrowserEndpoint.js +57 -0
  41. package/dist/browser/containerBrowserHost.d.ts +24 -0
  42. package/dist/browser/containerBrowserHost.js +147 -0
  43. package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
  44. package/dist/browser/dockerSandboxOrchestrator.js +142 -0
  45. package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
  46. package/dist/browser/fakeLiveViewRelay.js +94 -0
  47. package/dist/browser/fakeLiveViewSource.d.ts +6 -0
  48. package/dist/browser/fakeLiveViewSource.js +16 -0
  49. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
  50. package/dist/browser/fakeRemoteBrowserHost.js +2 -1
  51. package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
  52. package/dist/browser/fakeSandboxOrchestrator.js +44 -0
  53. package/dist/browser/gateHandoff.d.ts +0 -2
  54. package/dist/browser/gateHandoff.js +5 -11
  55. package/dist/browser/hostCapabilities.d.ts +23 -0
  56. package/dist/browser/hostCapabilities.js +23 -0
  57. package/dist/browser/humanLogin.d.ts +2 -2
  58. package/dist/browser/humanLogin.js +11 -2
  59. package/dist/browser/liveViewPersisted.d.ts +11 -0
  60. package/dist/browser/liveViewPersisted.js +22 -0
  61. package/dist/browser/liveViewRelay.d.ts +76 -0
  62. package/dist/browser/liveViewRelay.js +1 -0
  63. package/dist/browser/liveViewSessions.d.ts +25 -0
  64. package/dist/browser/liveViewSessions.js +139 -0
  65. package/dist/browser/liveViewSource.d.ts +25 -0
  66. package/dist/browser/liveViewSource.js +7 -0
  67. package/dist/browser/liveViewTickets.d.ts +46 -0
  68. package/dist/browser/liveViewTickets.js +122 -0
  69. package/dist/browser/localBrowserHost.d.ts +10 -0
  70. package/dist/browser/localBrowserHost.js +53 -7
  71. package/dist/browser/ownerScope.d.ts +10 -0
  72. package/dist/browser/ownerScope.js +6 -0
  73. package/dist/browser/profileStore.d.ts +5 -2
  74. package/dist/browser/profileStore.js +3 -5
  75. package/dist/browser/sandboxOrchestrator.d.ts +60 -0
  76. package/dist/browser/sandboxOrchestrator.js +8 -0
  77. package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
  78. package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
  79. package/dist/browser/stageBrowserEnv.d.ts +7 -0
  80. package/dist/browser/stageBrowserEnv.js +53 -15
  81. package/dist/browser/stageHandoff.d.ts +47 -0
  82. package/dist/browser/stageHandoff.js +101 -0
  83. package/dist/browser/stageProfileLock.d.ts +1 -0
  84. package/dist/browser/stageProfileLock.js +1 -3
  85. package/dist/browser/volumeProfileStore.d.ts +7 -0
  86. package/dist/browser/volumeProfileStore.js +35 -0
  87. package/dist/channels/config.d.ts +11 -0
  88. package/dist/channels/config.js +95 -0
  89. package/dist/channels/configWriter.d.ts +40 -0
  90. package/dist/channels/configWriter.js +148 -0
  91. package/dist/channels/connectionEdits.d.ts +34 -0
  92. package/dist/channels/connectionEdits.js +53 -0
  93. package/dist/channels/errorInfo.d.ts +18 -0
  94. package/dist/channels/errorInfo.js +71 -0
  95. package/dist/channels/errors.d.ts +12 -0
  96. package/dist/channels/errors.js +13 -0
  97. package/dist/channels/health.d.ts +41 -0
  98. package/dist/channels/health.js +99 -0
  99. package/dist/channels/host.d.ts +55 -0
  100. package/dist/channels/host.js +185 -0
  101. package/dist/channels/http.d.ts +31 -0
  102. package/dist/channels/http.js +79 -0
  103. package/dist/channels/index.d.ts +10 -0
  104. package/dist/channels/index.js +9 -0
  105. package/dist/channels/ledger.d.ts +37 -0
  106. package/dist/channels/ledger.js +95 -0
  107. package/dist/channels/providers/agentmail.d.ts +26 -0
  108. package/dist/channels/providers/agentmail.js +81 -0
  109. package/dist/channels/providers/kit.d.ts +40 -0
  110. package/dist/channels/providers/kit.js +78 -0
  111. package/dist/channels/providers/photonImessage.d.ts +45 -0
  112. package/dist/channels/providers/photonImessage.js +152 -0
  113. package/dist/channels/providers/photonWhatsapp.d.ts +49 -0
  114. package/dist/channels/providers/photonWhatsapp.js +146 -0
  115. package/dist/channels/providers/slack.d.ts +25 -0
  116. package/dist/channels/providers/slack.js +101 -0
  117. package/dist/channels/providers/telegram.d.ts +21 -0
  118. package/dist/channels/providers/telegram.js +100 -0
  119. package/dist/channels/providers.d.ts +5 -0
  120. package/dist/channels/providers.js +22 -0
  121. package/dist/channels/registry.d.ts +54 -0
  122. package/dist/channels/registry.js +186 -0
  123. package/dist/channels/secretStore.d.ts +21 -0
  124. package/dist/channels/secretStore.js +75 -0
  125. package/dist/channels/secrets.d.ts +15 -0
  126. package/dist/channels/secrets.js +50 -0
  127. package/dist/channels/service.d.ts +54 -0
  128. package/dist/channels/service.js +307 -0
  129. package/dist/channels/stageChannels.d.ts +45 -0
  130. package/dist/channels/stageChannels.js +110 -0
  131. package/dist/channels/types.d.ts +142 -0
  132. package/dist/channels/types.js +1 -0
  133. package/dist/cli/browserCommand.d.ts +3 -1
  134. package/dist/cli/browserCommand.js +25 -20
  135. package/dist/cli/doctorCommand.js +3 -1
  136. package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
  137. package/dist/cli/resolveCliProjectRoot.js +27 -0
  138. package/dist/cli/runCommand.d.ts +1 -1
  139. package/dist/cli/runCommand.js +59 -17
  140. package/dist/cli/skillsCommand.d.ts +20 -0
  141. package/dist/cli/skillsCommand.js +74 -31
  142. package/dist/cli/validateCommand.d.ts +1 -1
  143. package/dist/cli/validateCommand.js +22 -4
  144. package/dist/cli.js +4 -4
  145. package/dist/config/browseCatalog.d.ts +2 -0
  146. package/dist/config/browseCatalog.js +6 -0
  147. package/dist/config/catalogRelativePath.d.ts +4 -0
  148. package/dist/config/catalogRelativePath.js +37 -9
  149. package/dist/config/createStage.d.ts +1 -0
  150. package/dist/config/createStage.js +11 -2
  151. package/dist/config/createTask.d.ts +30 -0
  152. package/dist/config/createTask.js +161 -0
  153. package/dist/config/createTrigger.d.ts +15 -0
  154. package/dist/config/createTrigger.js +32 -25
  155. package/dist/config/draftPackage.d.ts +42 -0
  156. package/dist/config/draftPackage.js +220 -23
  157. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  158. package/dist/config/ensureCatalogScanRoot.js +6 -2
  159. package/dist/config/findTaskInCatalog.d.ts +33 -0
  160. package/dist/config/findTaskInCatalog.js +69 -0
  161. package/dist/config/hostConfig.d.ts +21 -0
  162. package/dist/config/hostConfig.js +114 -1
  163. package/dist/config/importTask.d.ts +19 -0
  164. package/dist/config/importTask.js +133 -0
  165. package/dist/config/installSkillRequest.d.ts +40 -0
  166. package/dist/config/installSkillRequest.js +286 -0
  167. package/dist/config/loadOutcome.d.ts +11 -6
  168. package/dist/config/loadPipeline.js +27 -4
  169. package/dist/config/loadStage.js +17 -0
  170. package/dist/config/loadStageflowManifest.js +4 -1
  171. package/dist/config/loadTask.js +2 -0
  172. package/dist/config/loadTrigger.js +2 -0
  173. package/dist/config/mergePipelineIncludes.d.ts +3 -0
  174. package/dist/config/mergePipelineIncludes.js +30 -0
  175. package/dist/config/normalizePipelineStageEntry.js +10 -1
  176. package/dist/config/pipelineStageKeys.js +1 -0
  177. package/dist/config/printTargetYaml.js +0 -1
  178. package/dist/config/publishDraftPackage.d.ts +5 -1
  179. package/dist/config/publishDraftPackage.js +4 -1
  180. package/dist/config/resolveCatalogRoots.d.ts +4 -1
  181. package/dist/config/resolveCatalogRoots.js +18 -1
  182. package/dist/config/skillUsageIndex.d.ts +6 -0
  183. package/dist/config/skillUsageIndex.js +39 -0
  184. package/dist/config/stageBrowser.js +13 -0
  185. package/dist/config/triggerAdapterStatus.d.ts +11 -0
  186. package/dist/config/triggerAdapterStatus.js +90 -0
  187. package/dist/config/triggerCatalog.d.ts +41 -2
  188. package/dist/config/triggerCatalog.js +107 -15
  189. package/dist/config/updateTask.d.ts +30 -0
  190. package/dist/config/updateTask.js +119 -0
  191. package/dist/config/updateTrigger.d.ts +38 -0
  192. package/dist/config/updateTrigger.js +220 -0
  193. package/dist/config/validateCatalog.d.ts +9 -2
  194. package/dist/config/validateCatalog.js +71 -7
  195. package/dist/config/workshopCatalogFilenames.d.ts +37 -0
  196. package/dist/config/workshopCatalogFilenames.js +125 -0
  197. package/dist/config/workshopCatalogRepair.d.ts +7 -0
  198. package/dist/config/workshopCatalogRepair.js +144 -0
  199. package/dist/config/yamlParsePosition.d.ts +4 -0
  200. package/dist/config/yamlParsePosition.js +9 -0
  201. package/dist/errors/codes.d.ts +1 -1
  202. package/dist/errors/codes.js +1 -0
  203. package/dist/hosted/runtime.d.ts +2 -2
  204. package/dist/hosted/types.d.ts +1 -1
  205. package/dist/hosted/types.js +1 -1
  206. package/dist/index.d.ts +3 -2
  207. package/dist/index.js +2 -1
  208. package/dist/mcp/catalogTools.js +1 -0
  209. package/dist/mcp/getStartedTool.js +15 -6
  210. package/dist/mcp/triggerTools.js +2 -8
  211. package/dist/operatorAgent/agentPort.d.ts +11 -0
  212. package/dist/operatorAgent/agentPort.js +12 -0
  213. package/dist/operatorAgent/index.d.ts +1 -0
  214. package/dist/operatorAgent/index.js +1 -0
  215. package/dist/operatorAgent/piModel.js +26 -6
  216. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  217. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
  218. package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
  219. package/dist/package-meta.d.ts +1 -1
  220. package/dist/package-meta.js +1 -1
  221. package/dist/preflight/channelChecks.d.ts +17 -0
  222. package/dist/preflight/channelChecks.js +106 -0
  223. package/dist/preflight/pipelinePreflight.d.ts +9 -2
  224. package/dist/preflight/pipelinePreflight.js +9 -1
  225. package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
  226. package/dist/project/ensureProjectRootsFromEnv.js +22 -0
  227. package/dist/project/globalHome.d.ts +1 -0
  228. package/dist/project/globalHome.js +18 -0
  229. package/dist/project/workspaceCatalog.d.ts +10 -0
  230. package/dist/project/workspaceCatalog.js +91 -0
  231. package/dist/runstore/backup.js +14 -4
  232. package/dist/runstore/port.d.ts +11 -1
  233. package/dist/runstore/restore.js +5 -2
  234. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
  235. package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
  236. package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
  237. package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
  238. package/dist/runstore/sqlite/migrations/index.js +2 -0
  239. package/dist/runstore/stageSnapshot.d.ts +3 -1
  240. package/dist/runstore/stageSnapshot.js +19 -0
  241. package/dist/runstore/workspaceLayout.js +27 -0
  242. package/dist/runstore/writeContainedArtifact.d.ts +1 -0
  243. package/dist/runstore/writeContainedArtifact.js +52 -0
  244. package/dist/runtime/credentialBinding.js +14 -6
  245. package/dist/runtime/emailSource.js +20 -28
  246. package/dist/runtime/githubPollSource.js +20 -28
  247. package/dist/runtime/pipelineRunner.d.ts +4 -4
  248. package/dist/runtime/pipelineRunner.js +3 -1
  249. package/dist/runtime/pipelineScheduler.d.ts +2 -2
  250. package/dist/runtime/pipelineScheduler.js +3 -0
  251. package/dist/runtime/repositoryMaterialize.d.ts +1 -1
  252. package/dist/runtime/resumeReconstruct.d.ts +2 -2
  253. package/dist/runtime/resumeTimedOut.d.ts +2 -2
  254. package/dist/runtime/runManager.d.ts +18 -2
  255. package/dist/runtime/runManager.js +99 -31
  256. package/dist/runtime/runRetryCoordinator.d.ts +2 -2
  257. package/dist/runtime/scheduleSource.js +15 -24
  258. package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
  259. package/dist/runtime/stageAttemptBootstrap.js +26 -1
  260. package/dist/runtime/stageProcessLauncher.js +33 -3
  261. package/dist/runtime/stageRoots.js +8 -1
  262. package/dist/runtime/stageRunner.d.ts +2 -2
  263. package/dist/runtime/stageRunner.js +8 -2
  264. package/dist/runtime/stageWorker.js +2 -2
  265. package/dist/runtime/triggerPort.d.ts +5 -3
  266. package/dist/runtime/triggerRunner.d.ts +1 -0
  267. package/dist/runtime/triggerRunner.js +27 -20
  268. package/dist/runtime/webhookAccept.d.ts +25 -0
  269. package/dist/runtime/webhookAccept.js +56 -0
  270. package/dist/server/bootstrap.d.ts +10 -2
  271. package/dist/server/bootstrap.js +19 -1
  272. package/dist/server/channelRoutes.d.ts +9 -0
  273. package/dist/server/channelRoutes.js +88 -0
  274. package/dist/server/controlToken.d.ts +1 -1
  275. package/dist/server/controlToken.js +18 -2
  276. package/dist/server/healthSurfaces.d.ts +12 -0
  277. package/dist/server/healthSurfaces.js +27 -6
  278. package/dist/server/http.d.ts +19 -10
  279. package/dist/server/http.js +616 -85
  280. package/dist/server/liveViewPath.d.ts +9 -0
  281. package/dist/server/liveViewPath.js +8 -0
  282. package/dist/server/liveViewRoutes.d.ts +58 -0
  283. package/dist/server/liveViewRoutes.js +565 -0
  284. package/dist/server/mcpHost.d.ts +4 -2
  285. package/dist/server/mcpHost.js +2 -0
  286. package/dist/server/readCatalogFile.d.ts +29 -0
  287. package/dist/server/readCatalogFile.js +215 -0
  288. package/dist/server/triggerRoutes.d.ts +1 -0
  289. package/dist/server/triggerRoutes.js +200 -73
  290. package/dist/tools/sendMessage.d.ts +21 -0
  291. package/dist/tools/sendMessage.js +25 -0
  292. package/dist/tools/writeStageArtifact.d.ts +9 -0
  293. package/dist/tools/writeStageArtifact.js +2 -47
  294. package/dist/types/pipeline.d.ts +5 -2
  295. package/dist/types/stage.d.ts +14 -1
  296. package/dist/types/stageflowManifest.d.ts +1 -1
  297. package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  298. package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  299. package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  300. package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  301. package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  302. package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  303. package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  304. package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  305. package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  306. package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  307. package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  308. package/dist/ui/assets/index-BOhqnPNg.js +260 -0
  309. package/dist/ui/assets/{index-Bv4p1aPK.css → index-Clv4sJpU.css} +1 -1
  310. package/dist/ui/index.html +4 -4
  311. package/dist/workshop/autosave.d.ts +2 -2
  312. package/dist/workshop/autosave.js +4 -6
  313. package/dist/workshop/buildStore.d.ts +2 -0
  314. package/dist/workshop/chatAttachments.d.ts +27 -0
  315. package/dist/workshop/chatAttachments.js +98 -0
  316. package/dist/workshop/chatTurn.d.ts +5 -2
  317. package/dist/workshop/chatTurn.js +22 -18
  318. package/dist/workshop/sessionMutations.d.ts +17 -0
  319. package/dist/workshop/sessionMutations.js +107 -0
  320. package/dist/workshop/sessionStore.d.ts +10 -0
  321. package/dist/workshop/sessionStore.js +52 -0
  322. package/dist/workshop/workshopPin.d.ts +4 -3
  323. package/dist/workshop/workshopPin.js +26 -6
  324. package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
  325. package/dist/workshop/workshopTranscriptTurn.js +46 -0
  326. package/examples/README.md +1 -1
  327. package/examples/archify-on-pr/README.md +1 -1
  328. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
  329. package/examples/archify-on-pr/author-diagrams.yaml +1 -0
  330. package/examples/channels-smoke/README.md +12 -0
  331. package/examples/channels-smoke/channels-smoke.pipeline.yaml +11 -0
  332. package/examples/channels-smoke/channels-smoke.task.yaml +3 -0
  333. package/examples/channels-smoke/send-email.yaml +32 -0
  334. package/examples/channels-smoke/send-text.yaml +31 -0
  335. package/examples/conditional-fork/branch-a.yaml +1 -1
  336. package/examples/conditional-fork/decide.yaml +2 -1
  337. package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
  338. package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
  339. package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
  340. package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
  341. package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
  342. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
  343. package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
  344. package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
  345. package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
  346. package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
  347. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
  348. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
  349. package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
  350. package/examples/hello-world/hello.pipeline.yaml +2 -2
  351. package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
  352. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
  353. package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
  354. package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
  355. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
  356. package/examples/stageflow.yaml +6 -0
  357. package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
  358. package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
  359. package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
  360. package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
  361. package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
  362. package/examples/triggers-readiness/README.md +48 -0
  363. package/examples/triggers-readiness/readiness-emit.yaml +18 -0
  364. package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
  365. package/examples/triggers-readiness/readiness.task.yaml +6 -0
  366. package/examples/triggers-readiness/stageflow.yaml +13 -0
  367. package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
  368. package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
  369. package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
  370. package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
  371. package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
  372. package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
  373. package/package.json +8 -3
  374. package/skills/stageflow/references/control-surface.md +1 -1
  375. package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
  376. package/skills/stageflow-run/SKILL.md +1 -1
  377. package/skills/stageflow-run/references/mcp-call.md +1 -1
  378. package/skills/stageflow-setup/SKILL.md +1 -1
  379. package/dist/ui/assets/index-59cjF0OA.js +0 -133
@@ -2,6 +2,7 @@ import { collectEffectiveRequires, } from "../config/toolRequires.js";
2
2
  import { resolveStageMcpServers, StageMcpError, } from "../config/resolveStageMcpServers.js";
3
3
  import { assertSecretPresent, loadSecretRegistry, SecretUnavailableError, } from "../runtime/stageSecrets.js";
4
4
  import { isForeverDeniedSecret } from "../runtime/stageEnvironment.js";
5
+ import { checkStageChannels } from "./channelChecks.js";
5
6
  import { checkToolchainRequirements, curatedStageEnv, } from "./toolchain.js";
6
7
  function toolchainItems(checks) {
7
8
  return checks.map((c) => ({
@@ -165,14 +166,21 @@ export async function runPipelinePreflight(loaded, options) {
165
166
  projectRoot: options.projectRoot,
166
167
  hostEnv,
167
168
  });
169
+ const channelChecks = await checkStageChannels(loaded, {
170
+ projectRoot: options.projectRoot,
171
+ hostEnv,
172
+ ...options.channels,
173
+ });
168
174
  const checks = [
169
175
  ...toolchainItems(toolchain.checks),
170
176
  ...secretChecks,
171
177
  ...mcpChecks,
178
+ ...channelChecks,
172
179
  ];
173
180
  const ok = toolchain.ok &&
174
181
  !secretChecks.some((c) => c.status !== "ok") &&
175
- !mcpChecks.some((c) => c.status !== "ok");
182
+ !mcpChecks.some((c) => c.status !== "ok") &&
183
+ !channelChecks.some((c) => c.status !== "ok");
176
184
  return { ok, checks, requirements, toolchain };
177
185
  }
178
186
  export function preflightFailureCode(result, options = {}) {
@@ -0,0 +1,3 @@
1
+ import type { RunStore } from "../runstore/port.js";
2
+ export declare function parseEnsureProjectRootsEnv(env: NodeJS.ProcessEnv): string[];
3
+ export declare function ensureProjectRootsFromEnv(env: NodeJS.ProcessEnv, store: RunStore): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import path from "node:path";
2
+ export function parseEnsureProjectRootsEnv(env) {
3
+ const raw = env.STAGEFLOW_ENSURE_PROJECT_ROOTS?.trim();
4
+ if (!raw)
5
+ return [];
6
+ return raw
7
+ .split(",")
8
+ .map((entry) => entry.trim())
9
+ .filter((entry) => entry.length > 0);
10
+ }
11
+ export async function ensureProjectRootsFromEnv(env, store) {
12
+ for (const entry of parseEnsureProjectRootsEnv(env)) {
13
+ const abs = path.resolve(entry);
14
+ try {
15
+ await store.ensureProject(abs);
16
+ }
17
+ catch (err) {
18
+ const message = err instanceof Error ? err.message : String(err);
19
+ console.warn(`stageflow: could not register project_root ${abs}: ${message}`);
20
+ }
21
+ }
22
+ }
@@ -5,5 +5,6 @@ export declare class StageflowHomeError extends Error {
5
5
  }
6
6
  export declare function resetGlobalStageflowHomeForTests(): void;
7
7
  export declare function globalStageflowHome(): string;
8
+ export declare function globalCredentialRoot(): string;
8
9
  export declare function assertStageflowHomeWritable(): void;
9
10
  export declare function ensureGlobalHome(): string;
@@ -10,8 +10,10 @@ export class StageflowHomeError extends Error {
10
10
  }
11
11
  }
12
12
  let memoizedHome;
13
+ let memoizedCredentialRoot;
13
14
  export function resetGlobalStageflowHomeForTests() {
14
15
  memoizedHome = undefined;
16
+ memoizedCredentialRoot = undefined;
15
17
  }
16
18
  function resolveStageflowHomeFromEnv() {
17
19
  const raw = process.env.STAGEFLOW_HOME?.trim();
@@ -30,6 +32,22 @@ export function globalStageflowHome() {
30
32
  }
31
33
  return memoizedHome;
32
34
  }
35
+ function resolveCredentialRootFromEnv() {
36
+ const raw = process.env.STAGEFLOW_CREDENTIAL_HOME?.trim();
37
+ if (raw) {
38
+ if (path.isAbsolute(raw)) {
39
+ return path.resolve(raw);
40
+ }
41
+ return path.resolve(process.cwd(), raw);
42
+ }
43
+ return path.join(os.homedir(), ".stageflow");
44
+ }
45
+ export function globalCredentialRoot() {
46
+ if (memoizedCredentialRoot === undefined) {
47
+ memoizedCredentialRoot = resolveCredentialRootFromEnv();
48
+ }
49
+ return memoizedCredentialRoot;
50
+ }
33
51
  function stageflowHomeNotWritableMessage(resolvedPath) {
34
52
  const uid = process.getuid?.() ?? 0;
35
53
  const gid = process.getgid?.() ?? 0;
@@ -0,0 +1,10 @@
1
+ export declare const WORKSPACE_CATALOG_ID = "workspace";
2
+ export type WorkspaceProvisionResult = {
3
+ path: string;
4
+ ok: boolean;
5
+ reason?: string;
6
+ };
7
+ export declare function resetWorkspaceProvisionForTests(): void;
8
+ export declare function lastWorkspaceProvisionResult(): WorkspaceProvisionResult | undefined;
9
+ export declare function workspaceCatalogPath(home?: string): string;
10
+ export declare function ensureWorkspaceCatalog(home?: string): WorkspaceProvisionResult;
@@ -0,0 +1,91 @@
1
+ import { accessSync, constants, mkdirSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { globalStageflowHome } from "./globalHome.js";
4
+ export const WORKSPACE_CATALOG_ID = "workspace";
5
+ let lastProvisionResult;
6
+ export function resetWorkspaceProvisionForTests() {
7
+ lastProvisionResult = undefined;
8
+ }
9
+ export function lastWorkspaceProvisionResult() {
10
+ return lastProvisionResult;
11
+ }
12
+ export function workspaceCatalogPath(home) {
13
+ return path.join(home ?? globalStageflowHome(), "workspace");
14
+ }
15
+ function workspaceManifestYaml() {
16
+ return `version: 1
17
+ catalog:
18
+ pipelines:
19
+ - .
20
+ tasks:
21
+ - .
22
+ triggers:
23
+ - .
24
+ patterns:
25
+ pipeline: "*.pipeline.yaml"
26
+ task: "*.task.yaml"
27
+ trigger: "*.trigger.yaml"
28
+ `;
29
+ }
30
+ function workspaceNotWritableReason(resolvedPath) {
31
+ const uid = process.getuid?.() ?? 0;
32
+ const gid = process.getgid?.() ?? 0;
33
+ return `Workspace catalog is not writable (${resolvedPath}). Running as uid=${uid} gid=${gid}. Try: sudo chown -R ${uid}:${gid} ${path.dirname(resolvedPath)}`;
34
+ }
35
+ function isWritableDir(dir) {
36
+ try {
37
+ accessSync(dir, constants.W_OK);
38
+ return true;
39
+ }
40
+ catch (err) {
41
+ const code = err.code;
42
+ if (code === "ENOENT") {
43
+ const parent = path.dirname(dir);
44
+ if (parent === dir)
45
+ return false;
46
+ try {
47
+ accessSync(parent, constants.W_OK);
48
+ return true;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+ }
57
+ export function ensureWorkspaceCatalog(home) {
58
+ const wsPath = workspaceCatalogPath(home);
59
+ if (!isWritableDir(wsPath)) {
60
+ const result = {
61
+ path: wsPath,
62
+ ok: false,
63
+ reason: workspaceNotWritableReason(wsPath),
64
+ };
65
+ lastProvisionResult = result;
66
+ return result;
67
+ }
68
+ try {
69
+ mkdirSync(wsPath, { recursive: true });
70
+ const manifestPath = path.join(wsPath, "stageflow.yaml");
71
+ try {
72
+ accessSync(manifestPath, constants.F_OK);
73
+ }
74
+ catch {
75
+ writeFileSync(manifestPath, workspaceManifestYaml(), "utf8");
76
+ }
77
+ const result = { path: wsPath, ok: true };
78
+ lastProvisionResult = result;
79
+ return result;
80
+ }
81
+ catch (err) {
82
+ const message = err instanceof Error ? err.message : String(err);
83
+ const result = {
84
+ path: wsPath,
85
+ ok: false,
86
+ reason: `${workspaceNotWritableReason(wsPath)} (${message})`,
87
+ };
88
+ lastProvisionResult = result;
89
+ return result;
90
+ }
91
+ }
@@ -8,7 +8,8 @@ import { pipeline } from "node:stream/promises";
8
8
  import { createWriteStream } from "node:fs";
9
9
  import Database from "better-sqlite3";
10
10
  import { PACKAGE_VERSION } from "../package-meta.js";
11
- import { globalStageflowHome } from "../project/globalHome.js";
11
+ import { globalCredentialRoot, globalStageflowHome } from "../project/globalHome.js";
12
+ import { isUsableAuthFile } from "../runtime/credentialBinding.js";
12
13
  import { readFilesystemSize } from "./diskUsage.js";
13
14
  import { isInsideDir } from "./workspaceLayout.js";
14
15
  export class BackupError extends Error {
@@ -45,6 +46,14 @@ export function assertBackupOutPathAllowed(outPath, homeDir = globalStageflowHom
45
46
  }
46
47
  return resolved;
47
48
  }
49
+ function isCredentialRootDir(dir) {
50
+ return path.resolve(dir) === path.resolve(globalCredentialRoot());
51
+ }
52
+ function packAuthMember(homeDir, authPath, noCredentials) {
53
+ return (!noCredentials &&
54
+ isCredentialRootDir(homeDir) &&
55
+ isUsableAuthFile(authPath));
56
+ }
48
57
  function fingerprint(filePath) {
49
58
  if (!existsSync(filePath))
50
59
  return null;
@@ -203,7 +212,8 @@ export async function createBackup(options) {
203
212
  });
204
213
  contents.push("settings.json");
205
214
  }
206
- if (!noCredentials && existsSync(authPath)) {
215
+ const includeAuth = packAuthMember(homeDir, authPath, noCredentials);
216
+ if (includeAuth) {
207
217
  const stagedAuth = path.join(staging, "auth.json");
208
218
  copyFileSync(authPath, stagedAuth);
209
219
  chmodSync(stagedAuth, 0o600);
@@ -226,14 +236,14 @@ export async function createBackup(options) {
226
236
  schema_version: userVersion,
227
237
  contents,
228
238
  db_only: false,
229
- credentials_included: !noCredentials && existsSync(authPath),
239
+ credentials_included: includeAuth,
230
240
  a2a_artifacts_included: includeA2a,
231
241
  settings_fingerprint_before: settingsBefore,
232
242
  settings_fingerprint_after: settingsAfter,
233
243
  auth_fingerprint_before: authBefore,
234
244
  auth_fingerprint_after: authAfter,
235
245
  fingerprint_skew: fingerprintSkew,
236
- secret_warning: SECRET_WARNING,
246
+ secret_warning: includeAuth ? SECRET_WARNING : "",
237
247
  };
238
248
  const manifestPath = path.join(staging, "manifest.json");
239
249
  writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, {
@@ -99,6 +99,7 @@ export type StageSnapshot = {
99
99
  attempt_count: number;
100
100
  /** Total $ spent on this stage across every attempt; omitted when no attempt reported usage. */
101
101
  cost_usd?: number;
102
+ usage?: StageUsage;
102
103
  };
103
104
  export type StageExecution = {
104
105
  run_id: string;
@@ -470,12 +471,21 @@ export interface RunStore {
470
471
  upsertTrigger(input: UpsertTriggerInput): Promise<TriggerRecord>;
471
472
  getTrigger(id: string): Promise<TriggerRecord | null>;
472
473
  listTriggers(): Promise<TriggerRecord[]>;
473
- /** Record a fire: stamps last_fired_at/last_run_id. Throws if the trigger is missing. */
474
+ /** Record a fire: stamps last_fired_at/last_run_id and appends trigger_fires. Throws if the trigger is missing. */
474
475
  recordTriggerFired(id: string, runId: string): Promise<void>;
476
+ /** Newest first. */
477
+ listTriggerFires(triggerId: string, limit: number): Promise<Array<{
478
+ fired_at: string;
479
+ run_id: string;
480
+ }>>;
475
481
  /** Persist the next computed fire time for a schedule-kind trigger. Throws if the trigger is missing. */
476
482
  setTriggerNextRun(id: string, nextRunAt: string): Promise<void>;
477
483
  /** Read a single adapter-owned state value for a trigger, or null if unset. */
478
484
  getTriggerAdapterState(triggerId: string, key: string): Promise<string | null>;
485
+ getTriggerAdapterStateMeta(triggerId: string, key: string): Promise<{
486
+ value: string;
487
+ updated_at: string;
488
+ } | null>;
479
489
  /** Upsert a single adapter-owned state value for a trigger. */
480
490
  setTriggerAdapterState(triggerId: string, key: string, value: string): Promise<void>;
481
491
  readRun(runId: string): Promise<RunDetail>;
@@ -7,7 +7,8 @@ import { pipeline } from "node:stream/promises";
7
7
  import { createWriteStream } from "node:fs";
8
8
  import Database from "better-sqlite3";
9
9
  import { PACKAGE_VERSION } from "../package-meta.js";
10
- import { globalStageflowHome } from "../project/globalHome.js";
10
+ import { globalCredentialRoot, globalStageflowHome } from "../project/globalHome.js";
11
+ import { isUsableAuthFile } from "../runtime/credentialBinding.js";
11
12
  import { CURRENT_SCHEMA_VERSION } from "./sqlite/migrations/index.js";
12
13
  import { StoreSchemaError } from "./sqlite/storeSchemaError.js";
13
14
  import { assertStoreQuickCheck } from "./sqlite/applyStorePragmas.js";
@@ -234,7 +235,9 @@ export async function applyRestoreArchive(options) {
234
235
  if (existsSync(settingsSrc)) {
235
236
  copyFileSync(settingsSrc, path.join(homeDir, "settings.json"));
236
237
  }
237
- if (existsSync(authSrc)) {
238
+ const restoreAuth = path.resolve(homeDir) === path.resolve(globalCredentialRoot()) &&
239
+ isUsableAuthFile(authSrc);
240
+ if (restoreAuth) {
238
241
  const agentDir = path.join(homeDir, "agent");
239
242
  mkdirSync(agentDir, { recursive: true });
240
243
  try {
@@ -82,8 +82,16 @@ export declare class SqliteRunStore implements RunStore {
82
82
  getTrigger(id: string): Promise<TriggerRecord | null>;
83
83
  listTriggers(): Promise<TriggerRecord[]>;
84
84
  recordTriggerFired(id: string, runId: string): Promise<void>;
85
+ listTriggerFires(triggerId: string, limit: number): Promise<Array<{
86
+ fired_at: string;
87
+ run_id: string;
88
+ }>>;
85
89
  setTriggerNextRun(id: string, nextRunAt: string): Promise<void>;
86
90
  getTriggerAdapterState(triggerId: string, key: string): Promise<string | null>;
91
+ getTriggerAdapterStateMeta(triggerId: string, key: string): Promise<{
92
+ value: string;
93
+ updated_at: string;
94
+ } | null>;
87
95
  setTriggerAdapterState(triggerId: string, key: string, value: string): Promise<void>;
88
96
  readRun(runId: string): Promise<RunDetail>;
89
97
  createFeedbackLoop(runId: string, input: CreateFeedbackLoopInput): Promise<FeedbackLoopRecord>;
@@ -942,13 +942,29 @@ export class SqliteRunStore {
942
942
  async recordTriggerFired(id, runId) {
943
943
  await this.ready();
944
944
  const now = new Date().toISOString();
945
- const result = this.db
946
- .prepare(`UPDATE triggers SET last_fired_at = @last_fired_at, last_run_id = @last_run_id, updated_at = @updated_at
947
- WHERE id = @id`)
948
- .run({ id, last_fired_at: now, last_run_id: runId, updated_at: now });
949
- if (result.changes === 0) {
950
- throw new Error(`Trigger not found: ${id}`);
951
- }
945
+ const apply = this.db.transaction(() => {
946
+ const result = this.db
947
+ .prepare(`UPDATE triggers SET last_fired_at = @last_fired_at, last_run_id = @last_run_id, updated_at = @updated_at
948
+ WHERE id = @id`)
949
+ .run({ id, last_fired_at: now, last_run_id: runId, updated_at: now });
950
+ if (result.changes === 0) {
951
+ throw new Error(`Trigger not found: ${id}`);
952
+ }
953
+ this.db
954
+ .prepare(`INSERT INTO trigger_fires (trigger_id, run_id, fired_at)
955
+ VALUES (@trigger_id, @run_id, @fired_at)`)
956
+ .run({ trigger_id: id, run_id: runId, fired_at: now });
957
+ });
958
+ apply();
959
+ }
960
+ async listTriggerFires(triggerId, limit) {
961
+ await this.ready();
962
+ return this.db
963
+ .prepare(`SELECT fired_at, run_id FROM trigger_fires
964
+ WHERE trigger_id = ?
965
+ ORDER BY fired_at DESC, id DESC
966
+ LIMIT ?`)
967
+ .all(triggerId, limit);
952
968
  }
953
969
  async setTriggerNextRun(id, nextRunAt) {
954
970
  await this.ready();
@@ -962,11 +978,15 @@ export class SqliteRunStore {
962
978
  }
963
979
  }
964
980
  async getTriggerAdapterState(triggerId, key) {
981
+ const row = await this.getTriggerAdapterStateMeta(triggerId, key);
982
+ return row?.value ?? null;
983
+ }
984
+ async getTriggerAdapterStateMeta(triggerId, key) {
965
985
  await this.ready();
966
986
  const row = this.db
967
- .prepare(`SELECT value FROM trigger_adapter_state WHERE trigger_id = ? AND key = ?`)
987
+ .prepare(`SELECT value, updated_at FROM trigger_adapter_state WHERE trigger_id = ? AND key = ?`)
968
988
  .get(triggerId, key);
969
- return row ? row.value : null;
989
+ return row ?? null;
970
990
  }
971
991
  async setTriggerAdapterState(triggerId, key, value) {
972
992
  await this.ready();
@@ -0,0 +1,7 @@
1
+ import type Database from "better-sqlite3";
2
+ export declare const MIGRATION_012: {
3
+ version: number;
4
+ name: string;
5
+ minStageflowVersion: string;
6
+ up(db: Database.Database): void;
7
+ };
@@ -0,0 +1,18 @@
1
+ import { PACKAGE_VERSION } from "../../../package-meta.js";
2
+ export const MIGRATION_012 = {
3
+ version: 12,
4
+ name: "012_trigger_fires",
5
+ minStageflowVersion: PACKAGE_VERSION,
6
+ up(db) {
7
+ db.exec(`
8
+ CREATE TABLE IF NOT EXISTS trigger_fires (
9
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
10
+ trigger_id TEXT NOT NULL,
11
+ run_id TEXT NOT NULL,
12
+ fired_at TEXT NOT NULL
13
+ );
14
+ CREATE INDEX IF NOT EXISTS trigger_fires_trigger_fired
15
+ ON trigger_fires (trigger_id, fired_at DESC);
16
+ `);
17
+ },
18
+ };
@@ -10,6 +10,7 @@ import { MIGRATION_008 } from "./008-triggers-table.js";
10
10
  import { MIGRATION_009 } from "./009-trigger-next-run.js";
11
11
  import { MIGRATION_010 } from "./010-trigger-adapter-state.js";
12
12
  import { MIGRATION_011 } from "./011-email-dispatch-key.js";
13
+ import { MIGRATION_012 } from "./012-trigger-fires.js";
13
14
  const DEFAULT_MIGRATIONS = [
14
15
  MIGRATION_001,
15
16
  MIGRATION_002,
@@ -22,6 +23,7 @@ const DEFAULT_MIGRATIONS = [
22
23
  MIGRATION_009,
23
24
  MIGRATION_010,
24
25
  MIGRATION_011,
26
+ MIGRATION_012,
25
27
  ];
26
28
  export const CURRENT_SCHEMA_VERSION = DEFAULT_MIGRATIONS[DEFAULT_MIGRATIONS.length - 1].version;
27
29
  function throwStoreSchemaTooNew(db, version, maxKnown) {
@@ -1,2 +1,4 @@
1
- import type { RunStore, StageSnapshot } from "./port.js";
1
+ import { type StageUsage } from "../types/usage.js";
2
+ import type { RunStore, StageExecution, StageSnapshot } from "./port.js";
2
3
  export declare function buildStageSnapshotFromStore(store: RunStore, runId: string, stageId: string, workspaceDir: string): Promise<StageSnapshot>;
4
+ export declare function mergeStageExecutionUsage(executions: StageExecution[]): StageUsage | undefined;
@@ -1,3 +1,4 @@
1
+ import { addModelUsage, emptyStageUsage } from "../types/usage.js";
1
2
  import { derivePendingPrompt } from "../hitl/qaTrail.js";
2
3
  import { stageStatusFromEvents } from "./port.js";
3
4
  import { listArtifactNames } from "./workspaceLayout.js";
@@ -19,6 +20,7 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
19
20
  let envelope;
20
21
  let attempt_count;
21
22
  let cost_usd;
23
+ let usage;
22
24
  if (latest !== null) {
23
25
  events = await store.listStageEvents(runId, stageId, latest.attempt);
24
26
  status = stageStatusFromEvents(events);
@@ -26,6 +28,7 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
26
28
  attempt_count = await store.countStageAttempts(runId, stageId);
27
29
  const executions = await store.listStageExecutions(runId, stageId);
28
30
  cost_usd = sumStageExecutionCost(executions);
31
+ usage = mergeStageExecutionUsage(executions);
29
32
  }
30
33
  else {
31
34
  events = await store.listStageEvents(runId, stageId);
@@ -46,8 +49,24 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
46
49
  attempt_count,
47
50
  ...(pending ? { pending_prompt: pending } : {}),
48
51
  ...(cost_usd !== undefined ? { cost_usd } : {}),
52
+ ...(usage !== undefined ? { usage } : {}),
49
53
  };
50
54
  }
55
+ export function mergeStageExecutionUsage(executions) {
56
+ const usages = executions
57
+ .map((e) => e.usage)
58
+ .filter((u) => u !== undefined);
59
+ if (usages.length === 0)
60
+ return undefined;
61
+ const merged = emptyStageUsage();
62
+ for (const u of usages) {
63
+ for (const [model, breakdown] of Object.entries(u.models)) {
64
+ addModelUsage(merged, model, breakdown);
65
+ }
66
+ }
67
+ merged.costUsd = usages.reduce((sum, u) => sum + u.costUsd, 0);
68
+ return merged;
69
+ }
51
70
  /** Sum cost across every attempt — retries are real spend too, so a stage's total isn't just its latest attempt. */
52
71
  function sumStageExecutionCost(executions) {
53
72
  const costs = executions.map((e) => e.cost_usd).filter((c) => c !== undefined);
@@ -1,5 +1,8 @@
1
1
  import { access, lstat, mkdir, readdir, realpath } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { globalCredentialRoot } from "../project/globalHome.js";
4
+ import { STAGEFLOW_AGENT_AUTH_PATH_ENV } from "../runtime/credentialBinding.js";
5
+ import { SF_STAGE_WORKER } from "../runtime/stageWorkerProtocol.js";
3
6
  export { stageDir } from "./paths.js";
4
7
  export function guardStageId(stageId) {
5
8
  if (stageId.includes("..") ||
@@ -101,6 +104,15 @@ export async function resolveEffectiveRealPath(candidatePath) {
101
104
  }
102
105
  }
103
106
  }
107
+ function credentialRootForFileToolDenial() {
108
+ if (process.env[SF_STAGE_WORKER] === "1") {
109
+ const stamped = process.env[STAGEFLOW_AGENT_AUTH_PATH_ENV]?.trim();
110
+ if (!stamped)
111
+ return undefined;
112
+ return path.resolve(path.dirname(stamped), "..");
113
+ }
114
+ return globalCredentialRoot();
115
+ }
104
116
  export async function durableRootFileToolDenial(candidatePath, runWorkspaceDir, durableRoot, allowlistedRoots) {
105
117
  const fileReal = await resolveEffectiveRealPath(candidatePath);
106
118
  if (fileReal === undefined) {
@@ -137,6 +149,21 @@ export async function durableRootFileToolDenial(candidatePath, runWorkspaceDir,
137
149
  if (isInsideDir(fileReal, durableReal)) {
138
150
  return STAGEFLOW_PATH_DENIED;
139
151
  }
152
+ const credentialRoot = credentialRootForFileToolDenial();
153
+ if (credentialRoot !== undefined) {
154
+ let credentialReal;
155
+ try {
156
+ credentialReal = await realpath(credentialRoot);
157
+ }
158
+ catch {
159
+ credentialReal = path.resolve(credentialRoot);
160
+ }
161
+ if (credentialReal !== durableReal &&
162
+ !isInsideDir(credentialReal, durableReal) &&
163
+ isInsideDir(fileReal, credentialReal)) {
164
+ return STAGEFLOW_PATH_DENIED;
165
+ }
166
+ }
140
167
  return undefined;
141
168
  }
142
169
  export function resolveArtifactTarget(runWorkspaceDir, stageId, attempt, relativePath) {
@@ -0,0 +1 @@
1
+ export declare function writeContainedArtifact(runWorkspaceDir: string, artifactsDirectory: string, absolutePath: string, content: string): Promise<void>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Atomic write of one file under a stage attempt's artifacts directory.
3
+ * Refuses symlinked or hard-linked targets and any path that resolves outside
4
+ * the artifacts directory.
5
+ */
6
+ import { randomBytes } from "node:crypto";
7
+ import { lstat, realpath, rename, unlink, writeFile } from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { ensureRealArtifactsDir, isInsideDir, resolveContainedParent, } from "./workspaceLayout.js";
10
+ async function assertSafeWriteTarget(targetPath) {
11
+ try {
12
+ const st = await lstat(targetPath);
13
+ if (st.isSymbolicLink()) {
14
+ throw new Error("path escapes the stage artifacts directory");
15
+ }
16
+ if (st.nlink > 1) {
17
+ throw new Error("refusing to overwrite hard-linked file");
18
+ }
19
+ }
20
+ catch (err) {
21
+ if (err.code !== "ENOENT") {
22
+ throw err;
23
+ }
24
+ }
25
+ }
26
+ export async function writeContainedArtifact(runWorkspaceDir, artifactsDirectory, absolutePath, content) {
27
+ const { realArtifacts } = await ensureRealArtifactsDir(runWorkspaceDir, artifactsDirectory);
28
+ const relativeToArtifacts = path.relative(artifactsDirectory, absolutePath);
29
+ const parent = await resolveContainedParent(realArtifacts, relativeToArtifacts);
30
+ const baseName = path.basename(absolutePath);
31
+ const targetPath = path.join(parent, baseName);
32
+ await assertSafeWriteTarget(targetPath);
33
+ const tmpName = `.sf-artifact-${randomBytes(8).toString("hex")}.tmp`;
34
+ const tmpPath = path.join(parent, tmpName);
35
+ try {
36
+ await writeFile(tmpPath, content, "utf8");
37
+ await rename(tmpPath, targetPath);
38
+ }
39
+ catch (err) {
40
+ try {
41
+ await unlink(tmpPath);
42
+ }
43
+ catch {
44
+ // best-effort cleanup
45
+ }
46
+ throw err;
47
+ }
48
+ const realWritten = await realpath(targetPath);
49
+ if (!isInsideDir(realWritten, realArtifacts)) {
50
+ throw new Error("path escapes the stage artifacts directory");
51
+ }
52
+ }
@@ -1,12 +1,12 @@
1
- import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { AsyncLocalStorage } from "node:async_hooks";
3
3
  import path from "node:path";
4
- import { ensureGlobalHome } from "../project/globalHome.js";
4
+ import { ensureGlobalHome, globalCredentialRoot } from "../project/globalHome.js";
5
5
  import { resolveProjectContext, } from "../project/resolveProjectContext.js";
6
6
  import { parseCredentialSource, readCredentialSourceFromContext, writeCredentialSourceToContext, } from "./settingsFile.js";
7
7
  export { parseCredentialSource, readCredentialSourceFromContext, writeCredentialSourceToContext, };
8
8
  export function sfOwnedAgentDir() {
9
- return path.join(ensureGlobalHome(), "agent");
9
+ return path.join(globalCredentialRoot(), "agent");
10
10
  }
11
11
  export function sfOwnedAuthPath() {
12
12
  return path.join(sfOwnedAgentDir(), "auth.json");
@@ -42,7 +42,15 @@ export function isUsableAuthFile(authPath) {
42
42
  }
43
43
  export function ensureSfOwnedAuthStore() {
44
44
  ensureGlobalHome();
45
- const authPath = sfOwnedAuthPath();
45
+ const agentDir = sfOwnedAgentDir();
46
+ mkdirSync(agentDir, { recursive: true });
47
+ try {
48
+ chmodSync(agentDir, 0o700);
49
+ }
50
+ catch {
51
+ // best-effort on non-POSIX
52
+ }
53
+ const authPath = path.join(agentDir, "auth.json");
46
54
  if (!existsSync(authPath)) {
47
55
  writeFileSync(authPath, "{}\n", { encoding: "utf8", mode: 0o600 });
48
56
  }
@@ -63,12 +71,12 @@ export function currentExplicitAuthPath() {
63
71
  return explicitAuthPath.getStore();
64
72
  }
65
73
  export function resolveCredentialBinding(ctx, _options = {}) {
74
+ const explicit = currentExplicitAuthPath();
66
75
  const projectCtx = typeof ctx === "string" ? resolveProjectContext(ctx) : ctx;
67
76
  const persisted = readCredentialSourceFromContext(projectCtx);
68
- const authPath = ensureSfOwnedAuthStore();
69
77
  return {
70
78
  source: "sf_owned",
71
- authPath,
79
+ authPath: explicit ?? sfOwnedAuthPath(),
72
80
  provisional: persisted === undefined,
73
81
  };
74
82
  }