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
@@ -0,0 +1,53 @@
1
+ import { readConnectionsText, writeConnectionsText } from "./configWriter.js";
2
+ /**
3
+ * One atomic change to connections: secrets and file together. Either both change or neither
4
+ * does. A secret that already existed is restored to its previous value, not deleted, and a
5
+ * failure while writing, validating or saving leaves nothing half-applied. Dropping the secrets
6
+ * of a removed connection happens only after the file has been saved, and a failure there only
7
+ * leaves an orphaned secret behind, never a broken connection.
8
+ */
9
+ export class ConnectionEdits {
10
+ options;
11
+ constructor(options) {
12
+ this.options = options;
13
+ }
14
+ apply(edit) {
15
+ const { store } = this.options;
16
+ const file = this.options.file();
17
+ const text = readConnectionsText(file);
18
+ const previous = new Map();
19
+ let drop = [];
20
+ try {
21
+ for (const [name, value] of Object.entries(edit.secrets ?? {})) {
22
+ previous.set(name, store.get(name));
23
+ store.set(name, value);
24
+ }
25
+ const next = edit.rewrite(text);
26
+ this.options.validate(next.text);
27
+ writeConnectionsText(file, next.text);
28
+ drop = next.dropSecretPrefixes ?? [];
29
+ }
30
+ catch (error) {
31
+ for (const [name, value] of previous) {
32
+ try {
33
+ if (value === undefined)
34
+ store.delete(name);
35
+ else
36
+ store.set(name, value);
37
+ }
38
+ catch {
39
+ /* restoring is best effort; the original error is the one to report */
40
+ }
41
+ }
42
+ throw error;
43
+ }
44
+ for (const prefix of drop) {
45
+ try {
46
+ store.deleteWithPrefix(prefix);
47
+ }
48
+ catch {
49
+ /* an orphaned secret is harmless and is overwritten if the id is reused */
50
+ }
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,18 @@
1
+ import type { ChannelErrorCode } from "./errors.js";
2
+ export type ChannelErrorInfo = {
3
+ /** One sentence for logs, preflight output and the status line. */
4
+ reason: string;
5
+ /** What to do about it, when there is something to do. */
6
+ hint?: string;
7
+ /** HTTP status when the code is returned by the connections API. */
8
+ status: number;
9
+ };
10
+ /**
11
+ * The one place a channel error code is explained. A `Record` over the code union makes a code
12
+ * without an entry a compile error. Whether a failure is retryable is not here: it depends on
13
+ * the operation (a 5xx is retryable in a check but an unknown outcome in a send), so it stays
14
+ * where the failure is classified.
15
+ */
16
+ export declare const CHANNEL_ERROR_INFO: Record<ChannelErrorCode, ChannelErrorInfo>;
17
+ export declare function channelErrorReason(code: string): string;
18
+ export declare function channelErrorStatus(code: string): number;
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The one place a channel error code is explained. A `Record` over the code union makes a code
3
+ * without an entry a compile error. Whether a failure is retryable is not here: it depends on
4
+ * the operation (a 5xx is retryable in a check but an unknown outcome in a send), so it stays
5
+ * where the failure is classified.
6
+ */
7
+ export const CHANNEL_ERROR_INFO = {
8
+ CHANNEL_INVALID_INPUT: {
9
+ reason: "The settings are not valid for this provider account",
10
+ hint: "Check the values (for example an unknown inbox or a different workspace) and try again. If the file is hand-edited, fix the entry and restart.",
11
+ status: 400,
12
+ },
13
+ CHANNEL_NOT_FOUND: { reason: "The connection is not defined", status: 404 },
14
+ CHANNEL_DISABLED: { reason: "The connection is disabled", hint: "Enable it, or remove it from the stage.", status: 400 },
15
+ CHANNEL_PROVIDER_UNSUPPORTED: {
16
+ reason: "This provider is not supported here, or its SDK is not installed",
17
+ hint: "iMessage and WhatsApp need the Spectrum SDK packages, which are optional dependencies.",
18
+ status: 400,
19
+ },
20
+ CHANNEL_SECRET_UNAVAILABLE: {
21
+ reason: "A secret for this connection is not set on the host",
22
+ hint: "Set the environment variable each secret names (or its _FILE form) on the host, or use Replace to store one, then check again.",
23
+ status: 500,
24
+ },
25
+ CHANNEL_RESOURCE_LIMIT: { reason: "The request or connections.yaml is too large", status: 413 },
26
+ CHANNEL_STORAGE_FAILED: {
27
+ reason: "Stageflow could not read or write connections.yaml or the secret store on this host",
28
+ hint: "Check that the data directory is writable.",
29
+ status: 500,
30
+ },
31
+ CHANNEL_UNAUTHORIZED: {
32
+ reason: "Credentials were rejected or lack a required permission",
33
+ hint: "The provider refused the credential: it is invalid, or it lacks a permission this provider needs (for AgentMail, reading the inbox and sending). Check the key and its permissions, then try again.",
34
+ status: 500,
35
+ },
36
+ CHANNEL_AUTH_FAILED: {
37
+ reason: "Credentials were rejected",
38
+ hint: "Replace the secret with a valid one, then check again.",
39
+ status: 500,
40
+ },
41
+ CHANNEL_CONNECTION_FAILED: {
42
+ reason: "Could not reach the provider",
43
+ hint: "This is usually temporary. Check the network and try again.",
44
+ status: 500,
45
+ },
46
+ CHANNEL_TIMEOUT: { reason: "The provider did not answer in time", hint: "Try again.", status: 500 },
47
+ CHANNEL_RATE_LIMIT: { reason: "The provider is rate limiting this account", hint: "Wait and try again.", status: 500 },
48
+ CHANNEL_WINDOW_CLOSED: {
49
+ reason: "WhatsApp's 24-hour reply window is closed",
50
+ hint: "Pin an approved template on the stage so it can message first.",
51
+ status: 500,
52
+ },
53
+ CHANNEL_TARGET_INVALID: { reason: "The pinned recipient is not valid for this provider", status: 400 },
54
+ CHANNEL_RECIPIENT_REJECTED: {
55
+ reason: "The provider will not message this recipient",
56
+ hint: "For iMessage on a Free or Pro Photon project, add them as a project user and have them text their assigned line (the \"Texts on\" number) once.",
57
+ status: 500,
58
+ },
59
+ CHANNEL_SEND_OUTCOME_UNKNOWN: {
60
+ reason: "The message may or may not have been sent",
61
+ hint: "Check the provider before sending again. Stageflow never resends it automatically.",
62
+ status: 500,
63
+ },
64
+ CHANNEL_OPERATION_CONFLICT: { reason: "That already exists, or the key was reused with different content", status: 409 },
65
+ };
66
+ export function channelErrorReason(code) {
67
+ return CHANNEL_ERROR_INFO[code]?.reason ?? code;
68
+ }
69
+ export function channelErrorStatus(code) {
70
+ return CHANNEL_ERROR_INFO[code]?.status ?? 500;
71
+ }
@@ -0,0 +1,12 @@
1
+ export type ChannelErrorCode = "CHANNEL_INVALID_INPUT" | "CHANNEL_NOT_FOUND" | "CHANNEL_DISABLED" | "CHANNEL_PROVIDER_UNSUPPORTED" | "CHANNEL_SECRET_UNAVAILABLE" | "CHANNEL_RESOURCE_LIMIT" | "CHANNEL_STORAGE_FAILED" | "CHANNEL_UNAUTHORIZED" | "CHANNEL_AUTH_FAILED" | "CHANNEL_CONNECTION_FAILED" | "CHANNEL_TIMEOUT" | "CHANNEL_RATE_LIMIT" | "CHANNEL_WINDOW_CLOSED" | "CHANNEL_TARGET_INVALID" | "CHANNEL_RECIPIENT_REJECTED" | "CHANNEL_SEND_OUTCOME_UNKNOWN" | "CHANNEL_OPERATION_CONFLICT";
2
+ export type ChannelErrorDetail = {
3
+ connectionId?: string;
4
+ field?: string;
5
+ };
6
+ /** The message is always the code. Detail carries only non-secret identifiers. */
7
+ export declare class ChannelError extends Error {
8
+ readonly code: ChannelErrorCode;
9
+ readonly retryable: boolean;
10
+ readonly detail?: ChannelErrorDetail | undefined;
11
+ constructor(code: ChannelErrorCode, retryable?: boolean, detail?: ChannelErrorDetail | undefined);
12
+ }
@@ -0,0 +1,13 @@
1
+ /** The message is always the code. Detail carries only non-secret identifiers. */
2
+ export class ChannelError extends Error {
3
+ code;
4
+ retryable;
5
+ detail;
6
+ constructor(code, retryable = false, detail) {
7
+ super(code);
8
+ this.code = code;
9
+ this.retryable = retryable;
10
+ this.detail = detail;
11
+ this.name = "ChannelError";
12
+ }
13
+ }
@@ -0,0 +1,41 @@
1
+ import type { ChannelRegistry } from "./registry.js";
2
+ import type { ConnectionHealth } from "./types.js";
3
+ export type ChannelHealthOptions = {
4
+ registry: ChannelRegistry;
5
+ intervalMs?: number;
6
+ jitter?: number;
7
+ timeoutMs?: number;
8
+ maxBackoffFactor?: number;
9
+ random?: () => number;
10
+ onResult?: (connectionId: string, health: ConnectionHealth) => void;
11
+ };
12
+ export declare const DEFAULT_HEALTH_INTERVAL_MS = 300000;
13
+ /**
14
+ * Host-only background check. It keeps the last result per connection in memory so the
15
+ * Connections page can show current status. It never sends a message and keeps no secrets.
16
+ * Failures back off up to `maxBackoffFactor` times the interval; successes return to it.
17
+ */
18
+ export declare class ChannelHealthMonitor {
19
+ private readonly registry;
20
+ private readonly intervalMs;
21
+ private readonly jitter;
22
+ private readonly timeoutMs;
23
+ private readonly maxBackoffFactor;
24
+ private readonly random;
25
+ private readonly onResult?;
26
+ private readonly results;
27
+ private readonly failures;
28
+ private readonly timers;
29
+ private readonly inflight;
30
+ private controller;
31
+ private running;
32
+ constructor(options: ChannelHealthOptions);
33
+ snapshot(): Record<string, ConnectionHealth>;
34
+ start(): void;
35
+ stop(): Promise<void>;
36
+ /** Runs one check now, outside the schedule. */
37
+ checkNow(connectionId: string): Promise<ConnectionHealth>;
38
+ private schedule;
39
+ private nextDelay;
40
+ private runCheck;
41
+ }
@@ -0,0 +1,99 @@
1
+ import { ChannelError } from "./errors.js";
2
+ export const DEFAULT_HEALTH_INTERVAL_MS = 300000;
3
+ function failed(code, retryable) {
4
+ return { state: "failed", checkedAt: new Date().toISOString(), error: { code, retryable } };
5
+ }
6
+ /**
7
+ * Host-only background check. It keeps the last result per connection in memory so the
8
+ * Connections page can show current status. It never sends a message and keeps no secrets.
9
+ * Failures back off up to `maxBackoffFactor` times the interval; successes return to it.
10
+ */
11
+ export class ChannelHealthMonitor {
12
+ registry;
13
+ intervalMs;
14
+ jitter;
15
+ timeoutMs;
16
+ maxBackoffFactor;
17
+ random;
18
+ onResult;
19
+ results = new Map();
20
+ failures = new Map();
21
+ timers = new Map();
22
+ inflight = new Set();
23
+ controller = new AbortController();
24
+ running = false;
25
+ constructor(options) {
26
+ this.registry = options.registry;
27
+ this.intervalMs = options.intervalMs ?? DEFAULT_HEALTH_INTERVAL_MS;
28
+ this.jitter = options.jitter ?? 0.2;
29
+ this.timeoutMs = options.timeoutMs ?? 8000;
30
+ this.maxBackoffFactor = options.maxBackoffFactor ?? 8;
31
+ this.random = options.random ?? Math.random;
32
+ this.onResult = options.onResult;
33
+ }
34
+ snapshot() {
35
+ return Object.fromEntries([...this.results].map(([id, health]) => [id, structuredClone(health)]));
36
+ }
37
+ start() {
38
+ if (this.running)
39
+ return;
40
+ this.running = true;
41
+ this.controller = new AbortController();
42
+ for (const connection of this.registry.list()) {
43
+ if (connection.enabled)
44
+ this.schedule(connection.id, Math.floor(this.random() * 2000));
45
+ }
46
+ }
47
+ async stop() {
48
+ this.running = false;
49
+ this.controller.abort();
50
+ for (const timer of this.timers.values())
51
+ clearTimeout(timer);
52
+ this.timers.clear();
53
+ await Promise.allSettled([...this.inflight]);
54
+ }
55
+ /** Runs one check now, outside the schedule. */
56
+ async checkNow(connectionId) {
57
+ const health = await this.runCheck(connectionId);
58
+ this.results.set(connectionId, health);
59
+ this.onResult?.(connectionId, structuredClone(health));
60
+ return structuredClone(health);
61
+ }
62
+ schedule(id, delay) {
63
+ if (!this.running)
64
+ return;
65
+ const timer = setTimeout(() => {
66
+ this.timers.delete(id);
67
+ const work = this.checkNow(id).then(health => {
68
+ const count = health.state === "ok" ? 0 : (this.failures.get(id) ?? 0) + 1;
69
+ this.failures.set(id, count);
70
+ this.schedule(id, this.nextDelay(count));
71
+ }).finally(() => { this.inflight.delete(work); });
72
+ this.inflight.add(work);
73
+ }, delay);
74
+ timer.unref?.();
75
+ this.timers.set(id, timer);
76
+ }
77
+ nextDelay(failureCount) {
78
+ const factor = Math.min(2 ** failureCount, this.maxBackoffFactor);
79
+ const base = this.intervalMs * (failureCount === 0 ? 1 : factor);
80
+ return Math.round(base * (1 + (this.random() * 2 - 1) * this.jitter));
81
+ }
82
+ async runCheck(id) {
83
+ let timer;
84
+ try {
85
+ const provider = this.registry.providerFor(id);
86
+ const connection = this.registry.resolve(id);
87
+ const timeout = new Promise(resolve => {
88
+ timer = setTimeout(() => resolve(failed("CHANNEL_TIMEOUT", true)), this.timeoutMs);
89
+ });
90
+ return await Promise.race([provider.check(connection, this.controller.signal), timeout]);
91
+ }
92
+ catch (error) {
93
+ return error instanceof ChannelError ? failed(error.code, error.retryable) : failed("CHANNEL_CONNECTION_FAILED", false);
94
+ }
95
+ finally {
96
+ clearTimeout(timer);
97
+ }
98
+ }
99
+ }
@@ -0,0 +1,55 @@
1
+ import type { StageConfig } from "../types/stage.js";
2
+ import { ChannelRegistry, type ChannelRegistryOptions } from "./registry.js";
3
+ import { type ChannelErrorCode } from "./errors.js";
4
+ import { ChannelSends } from "./ledger.js";
5
+ export type ChannelSendInput = {
6
+ channel?: string;
7
+ text: string;
8
+ subject?: string;
9
+ operationKey: string;
10
+ };
11
+ export type ChannelSendResult = {
12
+ channel: string;
13
+ connection: string;
14
+ providerMessageId?: string;
15
+ submittedAt: string;
16
+ warnings?: string[];
17
+ };
18
+ export type StageChannels = {
19
+ send(input: ChannelSendInput): Promise<ChannelSendResult>;
20
+ };
21
+ export declare const MAX_MESSAGE_TEXT = 65536;
22
+ export declare class ChannelHost {
23
+ private readonly cwd;
24
+ private readonly options;
25
+ registry: ChannelRegistry;
26
+ readonly sends: ChannelSends;
27
+ constructor(cwd: string, options?: Omit<ChannelRegistryOptions, "cwd">);
28
+ /** Re-reads connections.yaml after an edit. A file that no longer loads keeps the old registry. */
29
+ reload(): void;
30
+ }
31
+ export declare function channelHostFor(cwd: string): ChannelHost;
32
+ /** Re-reads connections.yaml in every cached host, so an edit made once reaches every project. */
33
+ export declare function reloadChannelHosts(): void;
34
+ export declare function releaseChannelHost(cwd: string): Promise<void>;
35
+ /** Fails with the first problem found. Static only: no network, no secret reads. */
36
+ export declare function validateStageChannelConnections(host: ChannelHost, stage: Pick<StageConfig, "id" | "channels">): void;
37
+ /**
38
+ * Removes every variable the loaded connections point at, and its `_FILE` form, from a worker
39
+ * environment. It reads the names from the same registry the host sends with, so the variables
40
+ * stripped always belong to the file the host actually loaded. A file that cannot be loaded
41
+ * leaves the environment unchanged.
42
+ */
43
+ export declare function channelsWorkerEnvironment(env: NodeJS.ProcessEnv, cwd: string): NodeJS.ProcessEnv;
44
+ /**
45
+ * Host-side authorization and delivery for one stage. The target comes from the stage's
46
+ * frozen `channels`, never from the caller. The caller's operation key is bound to the run,
47
+ * stage and channel before it reaches the ledger.
48
+ */
49
+ export declare function stageChannels(host: ChannelHost, stage: StageConfig, runId: string): StageChannels;
50
+ export declare function channelFault(error: unknown): {
51
+ code: ChannelErrorCode;
52
+ retryable: boolean;
53
+ };
54
+ /** The inherited IPC channel identifies the child; no caller-provided run or stage identity is accepted. */
55
+ export declare function workerStageChannels(): StageChannels;
@@ -0,0 +1,185 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import path from "node:path";
3
+ import { z } from "zod";
4
+ import { ChannelRegistry } from "./registry.js";
5
+ import { ChannelError } from "./errors.js";
6
+ import { ChannelSends } from "./ledger.js";
7
+ import { channelKey, channelTarget, stageChannelLimit, validateStageChannels } from "./stageChannels.js";
8
+ export const MAX_MESSAGE_TEXT = 65536;
9
+ const WORKER_TIMEOUT_MS = 65000;
10
+ const sendSchema = z.object({
11
+ channel: z.string().min(1).max(200).optional(),
12
+ text: z.string().min(1).max(MAX_MESSAGE_TEXT),
13
+ subject: z.string().min(1).max(998).regex(/^[^\r\n]*$/).optional(),
14
+ operationKey: z.string().min(1).max(120),
15
+ }).strict();
16
+ export class ChannelHost {
17
+ cwd;
18
+ options;
19
+ registry;
20
+ sends;
21
+ constructor(cwd, options = {}) {
22
+ this.cwd = cwd;
23
+ this.options = options;
24
+ this.registry = new ChannelRegistry({ cwd, ...options });
25
+ this.sends = new ChannelSends(this.registry.scope);
26
+ }
27
+ /** Re-reads connections.yaml after an edit. A file that no longer loads keeps the old registry. */
28
+ reload() {
29
+ this.registry = new ChannelRegistry({ cwd: this.cwd, ...this.options });
30
+ }
31
+ }
32
+ const hosts = new Map();
33
+ export function channelHostFor(cwd) {
34
+ const key = path.resolve(cwd);
35
+ let entry = hosts.get(key);
36
+ if (entry?.releasing)
37
+ throw new ChannelError("CHANNEL_CONNECTION_FAILED", true);
38
+ if (!entry) {
39
+ entry = { host: new ChannelHost(key) };
40
+ hosts.set(key, entry);
41
+ }
42
+ return entry.host;
43
+ }
44
+ /** Re-reads connections.yaml in every cached host, so an edit made once reaches every project. */
45
+ export function reloadChannelHosts() {
46
+ for (const entry of hosts.values()) {
47
+ try {
48
+ entry.host.reload();
49
+ }
50
+ catch {
51
+ /* a host whose file no longer loads keeps its previous registry until the file is fixed */
52
+ }
53
+ }
54
+ }
55
+ export async function releaseChannelHost(cwd) {
56
+ const key = path.resolve(cwd);
57
+ const entry = hosts.get(key);
58
+ if (!entry)
59
+ return;
60
+ entry.releasing ??= (async () => {
61
+ entry.host.sends.setAccepting(false);
62
+ await entry.host.sends.drain();
63
+ })();
64
+ await entry.releasing;
65
+ if (hosts.get(key) === entry)
66
+ hosts.delete(key);
67
+ }
68
+ /** Fails with the first problem found. Static only: no network, no secret reads. */
69
+ export function validateStageChannelConnections(host, stage) {
70
+ const [first] = validateStageChannels(host.registry, stage);
71
+ if (first)
72
+ throw new ChannelError(first.code, false, { connectionId: stage.channels?.find(channel => channelKey(channel) === first.key)?.connection });
73
+ }
74
+ /**
75
+ * Removes every variable the loaded connections point at, and its `_FILE` form, from a worker
76
+ * environment. It reads the names from the same registry the host sends with, so the variables
77
+ * stripped always belong to the file the host actually loaded. A file that cannot be loaded
78
+ * leaves the environment unchanged.
79
+ */
80
+ export function channelsWorkerEnvironment(env, cwd) {
81
+ const result = { ...env };
82
+ let names;
83
+ try {
84
+ names = channelHostFor(cwd).registry.envSecretNames();
85
+ }
86
+ catch {
87
+ return result;
88
+ }
89
+ for (const name of names) {
90
+ delete result[name];
91
+ delete result[`${name}_FILE`];
92
+ }
93
+ return result;
94
+ }
95
+ function pick(channels, requested) {
96
+ if (requested !== undefined) {
97
+ const found = channels.find(channel => channelKey(channel) === requested);
98
+ if (!found)
99
+ throw new ChannelError("CHANNEL_UNAUTHORIZED");
100
+ return found;
101
+ }
102
+ if (channels.length === 1)
103
+ return channels[0];
104
+ throw new ChannelError("CHANNEL_INVALID_INPUT", false, { field: "channel" });
105
+ }
106
+ function hashOf(parts) {
107
+ return createHash("sha256").update(JSON.stringify(parts)).digest("hex");
108
+ }
109
+ /**
110
+ * Host-side authorization and delivery for one stage. The target comes from the stage's
111
+ * frozen `channels`, never from the caller. The caller's operation key is bound to the run,
112
+ * stage and channel before it reaches the ledger.
113
+ */
114
+ export function stageChannels(host, stage, runId) {
115
+ const declared = structuredClone(stage.channels ?? []);
116
+ const stageId = stage.id;
117
+ const tooMany = declared.length > stageChannelLimit();
118
+ return {
119
+ async send(raw) {
120
+ if (tooMany)
121
+ throw new ChannelError("CHANNEL_INVALID_INPUT");
122
+ const parsed = sendSchema.safeParse(raw);
123
+ if (!parsed.success)
124
+ throw new ChannelError("CHANNEL_INVALID_INPUT");
125
+ const input = parsed.data;
126
+ const channel = pick(declared, input.channel);
127
+ if (!channel.operations.includes("send"))
128
+ throw new ChannelError("CHANNEL_UNAUTHORIZED", false, { connectionId: channel.connection });
129
+ const key = channelKey(channel);
130
+ const target = host.registry.validateTarget(channel.connection, channelTarget(channel));
131
+ const provider = host.registry.providerFor(channel.connection);
132
+ const connection = host.registry.resolve(channel.connection);
133
+ const message = { text: input.text, ...(input.subject !== undefined ? { subject: input.subject } : {}) };
134
+ const operationKey = hashOf([runId, stageId, key, input.operationKey]);
135
+ const receipt = await host.sends.send({ connectionId: channel.connection, operationKey, hash: hashOf([key, channel.connection, channelTarget(channel), message]) }, () => provider.send(connection, target, message, { operationKey, runId, stageId }));
136
+ return {
137
+ channel: key,
138
+ connection: channel.connection,
139
+ ...(receipt.providerMessageId !== undefined ? { providerMessageId: receipt.providerMessageId } : {}),
140
+ submittedAt: receipt.submittedAt,
141
+ ...(receipt.warnings?.length ? { warnings: receipt.warnings } : {}),
142
+ };
143
+ },
144
+ };
145
+ }
146
+ export function channelFault(error) {
147
+ return error instanceof ChannelError
148
+ ? { code: error.code, retryable: error.retryable }
149
+ : { code: "CHANNEL_CONNECTION_FAILED", retryable: false };
150
+ }
151
+ /** The inherited IPC channel identifies the child; no caller-provided run or stage identity is accepted. */
152
+ export function workerStageChannels() {
153
+ return {
154
+ send(input) {
155
+ return new Promise((resolve, reject) => {
156
+ if (!process.connected || !process.send) {
157
+ reject(new ChannelError("CHANNEL_UNAUTHORIZED"));
158
+ return;
159
+ }
160
+ const requestId = randomUUID();
161
+ const finish = (error, result) => {
162
+ clearTimeout(timer);
163
+ process.off("message", receive);
164
+ process.off("disconnect", disconnected);
165
+ if (error)
166
+ reject(error);
167
+ else
168
+ resolve(result);
169
+ };
170
+ const disconnected = () => finish(new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN"));
171
+ const receive = (message) => {
172
+ const response = message;
173
+ if (response?.type !== "channel.response" || response.requestId !== requestId)
174
+ return;
175
+ finish(response.error ? new ChannelError(response.error.code, response.error.retryable) : undefined, response.receipt);
176
+ };
177
+ const timer = setTimeout(disconnected, WORKER_TIMEOUT_MS);
178
+ process.on("message", receive);
179
+ process.once("disconnect", disconnected);
180
+ process.send({ type: "channel.send", requestId, input }, error => { if (error)
181
+ disconnected(); });
182
+ });
183
+ },
184
+ };
185
+ }
@@ -0,0 +1,31 @@
1
+ import { ChannelError } from "./errors.js";
2
+ export type HttpMode = "check" | "send";
3
+ export type HttpCall = {
4
+ fetch: typeof fetch;
5
+ url: string;
6
+ method: "GET" | "POST";
7
+ headers: Record<string, string>;
8
+ body?: unknown;
9
+ timeoutMs: number;
10
+ signal?: AbortSignal;
11
+ mode: HttpMode;
12
+ connectionId?: string;
13
+ };
14
+ export type HttpResult = {
15
+ status: number;
16
+ text: string;
17
+ };
18
+ /**
19
+ * Maps a transport failure. For a send, only failures that happen before the request leaves
20
+ * the machine are definite; everything else may have reached the provider, so it is unknown.
21
+ */
22
+ export declare function networkFailure(error: unknown, mode: HttpMode, timedOut: boolean, connectionId?: string): ChannelError;
23
+ /** Maps an HTTP status to a channel error. `retryable` is true only for transient conditions. */
24
+ export declare function statusFailure(status: number, mode: HttpMode, connectionId?: string): ChannelError;
25
+ /**
26
+ * One bounded HTTP call. Redirects are refused so a credential header never follows a
27
+ * redirect to another host. Response bodies are returned to the provider but never put in
28
+ * an error, so provider messages cannot carry a secret into a log or a tool result.
29
+ */
30
+ export declare function httpCall(call: HttpCall): Promise<HttpResult>;
31
+ export declare function parseJson(text: string): unknown;
@@ -0,0 +1,79 @@
1
+ import { ChannelError } from "./errors.js";
2
+ const NOT_SENT_CODES = new Set(["ENOTFOUND", "ECONNREFUSED", "EAI_AGAIN", "UND_ERR_CONNECT_TIMEOUT", "ERR_TLS_CERT_ALTNAME_INVALID", "CERT_HAS_EXPIRED", "DEPTH_ZERO_SELF_SIGNED_CERT", "UNABLE_TO_VERIFY_LEAF_SIGNATURE"]);
3
+ function causeCode(error) {
4
+ const cause = error?.cause;
5
+ return typeof cause?.code === "string" ? cause.code : undefined;
6
+ }
7
+ /**
8
+ * Maps a transport failure. For a send, only failures that happen before the request leaves
9
+ * the machine are definite; everything else may have reached the provider, so it is unknown.
10
+ */
11
+ export function networkFailure(error, mode, timedOut, connectionId) {
12
+ const detail = connectionId ? { connectionId } : undefined;
13
+ if (timedOut)
14
+ return new ChannelError(mode === "send" ? "CHANNEL_SEND_OUTCOME_UNKNOWN" : "CHANNEL_TIMEOUT", mode !== "send", detail);
15
+ if (mode === "check")
16
+ return new ChannelError("CHANNEL_CONNECTION_FAILED", true, detail);
17
+ const code = causeCode(error);
18
+ if (code !== undefined && NOT_SENT_CODES.has(code))
19
+ return new ChannelError("CHANNEL_CONNECTION_FAILED", true, detail);
20
+ return new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN", false, detail);
21
+ }
22
+ /** Maps an HTTP status to a channel error. `retryable` is true only for transient conditions. */
23
+ export function statusFailure(status, mode, connectionId) {
24
+ const detail = connectionId ? { connectionId } : undefined;
25
+ if (status === 401)
26
+ return new ChannelError("CHANNEL_AUTH_FAILED", false, detail);
27
+ if (status === 403)
28
+ return new ChannelError("CHANNEL_UNAUTHORIZED", false, detail);
29
+ if (status === 429)
30
+ return new ChannelError("CHANNEL_RATE_LIMIT", true, detail);
31
+ if (status >= 500) {
32
+ return mode === "send"
33
+ ? new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN", false, detail)
34
+ : new ChannelError("CHANNEL_CONNECTION_FAILED", true, detail);
35
+ }
36
+ if (status === 408)
37
+ return new ChannelError(mode === "send" ? "CHANNEL_SEND_OUTCOME_UNKNOWN" : "CHANNEL_TIMEOUT", mode !== "send", detail);
38
+ return new ChannelError("CHANNEL_INVALID_INPUT", false, detail);
39
+ }
40
+ /**
41
+ * One bounded HTTP call. Redirects are refused so a credential header never follows a
42
+ * redirect to another host. Response bodies are returned to the provider but never put in
43
+ * an error, so provider messages cannot carry a secret into a log or a tool result.
44
+ */
45
+ export async function httpCall(call) {
46
+ const timeout = AbortSignal.timeout(call.timeoutMs);
47
+ const signal = call.signal ? AbortSignal.any([call.signal, timeout]) : timeout;
48
+ let response;
49
+ try {
50
+ response = await call.fetch(call.url, {
51
+ method: call.method,
52
+ headers: { ...call.headers, ...(call.body !== undefined ? { "content-type": "application/json" } : {}) },
53
+ ...(call.body !== undefined ? { body: JSON.stringify(call.body) } : {}),
54
+ redirect: "error",
55
+ signal,
56
+ });
57
+ }
58
+ catch (error) {
59
+ throw networkFailure(error, call.mode, timeout.aborted, call.connectionId);
60
+ }
61
+ let text;
62
+ try {
63
+ text = await response.text();
64
+ }
65
+ catch (error) {
66
+ if (response.ok && call.mode === "send")
67
+ return { status: response.status, text: "" };
68
+ throw networkFailure(error, call.mode, timeout.aborted, call.connectionId);
69
+ }
70
+ return { status: response.status, text };
71
+ }
72
+ export function parseJson(text) {
73
+ try {
74
+ return JSON.parse(text);
75
+ }
76
+ catch {
77
+ return undefined;
78
+ }
79
+ }