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,10 @@
1
+ export { ChannelError, type ChannelErrorCode, type ChannelErrorDetail } from "./errors.js";
2
+ export { CONNECTIONS_FILE_NAME, ChannelRegistry, type ChannelRegistryOptions } from "./registry.js";
3
+ export { loadConnectionsFile, parseConnectionsYaml, type ConnectionsFile } from "./config.js";
4
+ export { isSecretRef, redactSecrets, resolveSecretRef, resolveSecrets } from "./secrets.js";
5
+ export type * from "./types.js";
6
+ export { MAX_CHANNEL_TARGETS, MAX_CHANNELS_PER_STAGE, MAX_STAGE_CHANNELS, channelLimitMessage, setStageChannelLimitForTests, stageChannelLimit, channelKey, channelTarget, mergeChannelDefaults, parseChannels, validateStageChannels, type ParsedChannels, type StageChannelIssue, } from "./stageChannels.js";
7
+ export { ChannelHost, MAX_MESSAGE_TEXT, channelFault, channelHostFor, channelsWorkerEnvironment, releaseChannelHost, stageChannels, validateStageChannelConnections, workerStageChannels, type ChannelSendInput, type ChannelSendResult, type StageChannels, } from "./host.js";
8
+ export { ChannelSends, type ChannelSendRecord } from "./ledger.js";
9
+ export { channelProviders, registerChannelProvider, unregisterChannelProvider } from "./providers.js";
10
+ export { ChannelHealthMonitor, DEFAULT_HEALTH_INTERVAL_MS, type ChannelHealthOptions } from "./health.js";
@@ -0,0 +1,9 @@
1
+ export { ChannelError } from "./errors.js";
2
+ export { CONNECTIONS_FILE_NAME, ChannelRegistry } from "./registry.js";
3
+ export { loadConnectionsFile, parseConnectionsYaml } from "./config.js";
4
+ export { isSecretRef, redactSecrets, resolveSecretRef, resolveSecrets } from "./secrets.js";
5
+ export { MAX_CHANNEL_TARGETS, MAX_CHANNELS_PER_STAGE, MAX_STAGE_CHANNELS, channelLimitMessage, setStageChannelLimitForTests, stageChannelLimit, channelKey, channelTarget, mergeChannelDefaults, parseChannels, validateStageChannels, } from "./stageChannels.js";
6
+ export { ChannelHost, MAX_MESSAGE_TEXT, channelFault, channelHostFor, channelsWorkerEnvironment, releaseChannelHost, stageChannels, validateStageChannelConnections, workerStageChannels, } from "./host.js";
7
+ export { ChannelSends } from "./ledger.js";
8
+ export { channelProviders, registerChannelProvider, unregisterChannelProvider } from "./providers.js";
9
+ export { ChannelHealthMonitor, DEFAULT_HEALTH_INTERVAL_MS } from "./health.js";
@@ -0,0 +1,37 @@
1
+ import { type ChannelErrorCode } from "./errors.js";
2
+ import type { SendReceipt } from "./types.js";
3
+ export type ChannelSendRecord = {
4
+ operationId: string;
5
+ connectionId: string;
6
+ operationKey: string;
7
+ hash: string;
8
+ state: "pending" | "submitted" | "failed" | "unknown";
9
+ createdAt: string;
10
+ receipt?: SendReceipt;
11
+ error?: {
12
+ code: ChannelErrorCode;
13
+ retryable: boolean;
14
+ };
15
+ };
16
+ export type ChannelSendIdentity = {
17
+ connectionId: string;
18
+ operationKey: string;
19
+ hash: string;
20
+ };
21
+ /**
22
+ * Durable record of send attempts, so a retried tool call or a resumed stage never sends twice.
23
+ * It stores a hash of the request, never the message text. The host is the single writer.
24
+ * A pending record that survives a crash becomes an uncertain outcome, never a resend.
25
+ */
26
+ export declare class ChannelSends {
27
+ private readonly file;
28
+ private records;
29
+ private readonly active;
30
+ private accepting;
31
+ constructor(scope: string);
32
+ list(): ChannelSendRecord[];
33
+ drain(): Promise<void>;
34
+ setAccepting(accepting: boolean): void;
35
+ send(identity: ChannelSendIdentity, submit: () => Promise<SendReceipt>): Promise<SendReceipt>;
36
+ private persist;
37
+ }
@@ -0,0 +1,95 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
3
+ import path from "node:path";
4
+ import { storeRootFor } from "../runstore/paths.js";
5
+ import { ChannelError } from "./errors.js";
6
+ const RETENTION_MS = 30 * 86400000;
7
+ const MAX_RECORDS = 10000;
8
+ /**
9
+ * Durable record of send attempts, so a retried tool call or a resumed stage never sends twice.
10
+ * It stores a hash of the request, never the message text. The host is the single writer.
11
+ * A pending record that survives a crash becomes an uncertain outcome, never a resend.
12
+ */
13
+ export class ChannelSends {
14
+ file;
15
+ records;
16
+ active = new Map();
17
+ accepting = true;
18
+ constructor(scope) {
19
+ this.file = path.join(storeRootFor(scope), "channel-sends.json");
20
+ this.records = existsSync(this.file) ? JSON.parse(readFileSync(this.file, "utf8")) : [];
21
+ for (const record of this.records)
22
+ if (record.state === "pending")
23
+ record.state = "unknown";
24
+ }
25
+ list() {
26
+ return structuredClone(this.records);
27
+ }
28
+ async drain() {
29
+ await Promise.allSettled(this.active.values());
30
+ }
31
+ setAccepting(accepting) {
32
+ this.accepting = accepting;
33
+ }
34
+ async send(identity, submit) {
35
+ if (!this.accepting)
36
+ throw new ChannelError("CHANNEL_CONNECTION_FAILED", true, { connectionId: identity.connectionId });
37
+ const key = `${identity.connectionId}\0${identity.operationKey}`;
38
+ const found = this.records.find(record => record.connectionId === identity.connectionId && record.operationKey === identity.operationKey);
39
+ if (found) {
40
+ if (found.hash !== identity.hash)
41
+ throw new ChannelError("CHANNEL_OPERATION_CONFLICT", false, { connectionId: identity.connectionId });
42
+ const active = this.active.get(key);
43
+ if (active)
44
+ return active;
45
+ if (found.receipt)
46
+ return structuredClone(found.receipt);
47
+ if (found.state === "failed" && found.error)
48
+ throw new ChannelError(found.error.code, found.error.retryable, { connectionId: identity.connectionId });
49
+ throw new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN", false, { connectionId: identity.connectionId });
50
+ }
51
+ const cutoff = Date.now() - RETENTION_MS;
52
+ this.records = this.records.filter(record => !["submitted", "failed"].includes(record.state) || Date.parse(record.createdAt) >= cutoff);
53
+ if (this.records.length >= MAX_RECORDS)
54
+ throw new ChannelError("CHANNEL_RESOURCE_LIMIT", false, { connectionId: identity.connectionId });
55
+ const record = {
56
+ operationId: randomUUID(), connectionId: identity.connectionId, operationKey: identity.operationKey,
57
+ hash: identity.hash, state: "pending", createdAt: new Date().toISOString(),
58
+ };
59
+ this.records.push(record);
60
+ this.persist();
61
+ const work = Promise.resolve().then(submit).then(receipt => {
62
+ record.state = "submitted";
63
+ record.receipt = receipt;
64
+ try {
65
+ this.persist();
66
+ }
67
+ catch {
68
+ return { ...structuredClone(receipt), warnings: [...(receipt.warnings ?? []), "CHANNEL_STORAGE_FAILED"] };
69
+ }
70
+ return structuredClone(receipt);
71
+ }).catch(error => {
72
+ if (record.state === "submitted")
73
+ throw error;
74
+ const fault = error instanceof ChannelError ? error : new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN", false, { connectionId: identity.connectionId });
75
+ record.state = fault.code === "CHANNEL_SEND_OUTCOME_UNKNOWN" ? "unknown" : "failed";
76
+ record.error = { code: fault.code, retryable: fault.retryable };
77
+ try {
78
+ this.persist();
79
+ }
80
+ catch {
81
+ record.state = "unknown";
82
+ throw new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN", false, { connectionId: identity.connectionId });
83
+ }
84
+ throw new ChannelError(fault.code, fault.retryable, { connectionId: identity.connectionId });
85
+ }).finally(() => { this.active.delete(key); });
86
+ this.active.set(key, work);
87
+ return work;
88
+ }
89
+ persist() {
90
+ mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
91
+ const temporary = `${this.file}.${randomUUID()}.tmp`;
92
+ writeFileSync(temporary, JSON.stringify(this.records), { mode: 0o600 });
93
+ renameSync(temporary, this.file);
94
+ }
95
+ }
@@ -0,0 +1,26 @@
1
+ import type { ChannelProvider, ProviderMeta } from "../types.js";
2
+ export type AgentMailConfig = {
3
+ inbox: string;
4
+ region: "us" | "eu";
5
+ };
6
+ export type AgentMailTarget = {
7
+ to: string[];
8
+ cc?: string[];
9
+ };
10
+ export declare const AGENTMAIL_BASE_URLS: {
11
+ readonly us: "https://api.agentmail.to";
12
+ readonly eu: "https://api.agentmail.eu";
13
+ };
14
+ export declare const AGENTMAIL_DEFAULT_SUBJECT = "Message from Stageflow";
15
+ export declare const AGENTMAIL_META: ProviderMeta;
16
+ export type AgentMailOptions = {
17
+ fetch?: typeof fetch;
18
+ baseUrls?: Partial<Record<"us" | "eu", string>>;
19
+ sendTimeoutMs?: number;
20
+ checkTimeoutMs?: number;
21
+ };
22
+ /**
23
+ * Sends mail from one existing AgentMail inbox through the REST API. Needs a key that can
24
+ * read the inbox and send messages. Receiving is not part of this phase.
25
+ */
26
+ export declare function createAgentMailProvider(options?: AgentMailOptions): ChannelProvider<AgentMailConfig, AgentMailTarget>;
@@ -0,0 +1,81 @@
1
+ import { z } from "zod";
2
+ import { ChannelError } from "../errors.js";
3
+ import { httpCall, parseJson, statusFailure } from "../http.js";
4
+ import { healthOf, receiptFor, zodValidator } from "./kit.js";
5
+ export const AGENTMAIL_BASE_URLS = { us: "https://api.agentmail.to", eu: "https://api.agentmail.eu" };
6
+ export const AGENTMAIL_DEFAULT_SUBJECT = "Message from Stageflow";
7
+ export const AGENTMAIL_META = {
8
+ label: "AgentMail",
9
+ blurb: "Not connected · send email from an inbox",
10
+ icon: "mail",
11
+ fields: [
12
+ { key: "inbox", label: "Inbox address", hint: "An existing AgentMail inbox. Stageflow does not create one.", target: "config", input: "text", required: true, placeholder: "research@agentmail.to" },
13
+ { key: "region", label: "Region", target: "config", input: "select", required: false, defaultValue: "us", options: [{ value: "us", label: "United States" }, { value: "eu", label: "Europe" }] },
14
+ { key: "apiKey", label: "API key", hint: "Needs to read the inbox and send messages.", target: "secret", input: "password", required: true, placeholder: "am_..." },
15
+ ],
16
+ recipient: { label: "To", placeholder: "oncall@acme.com, lead@acme.com", multiple: true, cc: true, template: false },
17
+ envNames: ["AGENTMAIL_API_KEY"],
18
+ yamlExample: `connections:
19
+ - id: support-inbox
20
+ provider: agentmail
21
+ config:
22
+ inbox: support@agentmail.to
23
+ secrets:
24
+ apiKey: env:AGENTMAIL_API_KEY
25
+ `,
26
+ stageExample: " to: [oncall@acme.com]",
27
+ };
28
+ const address = z.string().min(3).max(320).regex(/^[^\s@<>,;]+@[^\s@<>,;]+$/);
29
+ const configSchema = z.object({ inbox: address, region: z.enum(["us", "eu"]).default("us") }).strict();
30
+ const targetSchema = z.object({
31
+ to: z.union([address, z.array(address).min(1).max(20)]),
32
+ cc: z.array(address).min(1).max(20).optional(),
33
+ }).strict();
34
+ const validateConfig = zodValidator(configSchema, "config");
35
+ const validateTarget = zodValidator(targetSchema, "target", (data) => ({
36
+ to: Array.isArray(data.to) ? data.to : [data.to],
37
+ ...(data.cc !== undefined ? { cc: data.cc } : {}),
38
+ }));
39
+ /**
40
+ * Sends mail from one existing AgentMail inbox through the REST API. Needs a key that can
41
+ * read the inbox and send messages. Receiving is not part of this phase.
42
+ */
43
+ export function createAgentMailProvider(options = {}) {
44
+ const doFetch = options.fetch ?? fetch;
45
+ const baseUrl = (config) => options.baseUrls?.[config.region] ?? AGENTMAIL_BASE_URLS[config.region];
46
+ const inboxUrl = (connection) => `${baseUrl(connection.config)}/v0/inboxes/${encodeURIComponent(connection.config.inbox)}`;
47
+ const auth = (connection) => ({ authorization: `Bearer ${connection.secrets.apiKey}`, accept: "application/json" });
48
+ return {
49
+ id: "agentmail",
50
+ meta: AGENTMAIL_META,
51
+ operations: ["send"],
52
+ secrets: { required: ["apiKey"] },
53
+ validateConfig,
54
+ validateTarget,
55
+ describe: config => ({ identity: config.inbox }),
56
+ check: (connection, signal) => healthOf(async () => {
57
+ const result = await httpCall({
58
+ fetch: doFetch, url: inboxUrl(connection), method: "GET", headers: auth(connection),
59
+ timeoutMs: options.checkTimeoutMs ?? 10000, signal, mode: "check", connectionId: connection.id,
60
+ });
61
+ if (result.status >= 200 && result.status < 300)
62
+ return;
63
+ throw result.status === 404 ? new ChannelError("CHANNEL_INVALID_INPUT") : statusFailure(result.status, "check", connection.id);
64
+ }),
65
+ async send(connection, target, message, _context) {
66
+ const result = await httpCall({
67
+ fetch: doFetch, url: `${inboxUrl(connection)}/messages/send`, method: "POST", headers: auth(connection),
68
+ body: {
69
+ to: target.to, ...(target.cc !== undefined ? { cc: target.cc } : {}),
70
+ subject: message.subject ?? AGENTMAIL_DEFAULT_SUBJECT, text: message.text,
71
+ },
72
+ timeoutMs: options.sendTimeoutMs ?? 30000, mode: "send", connectionId: connection.id,
73
+ });
74
+ if (result.status < 200 || result.status >= 300) {
75
+ throw result.status === 404 ? new ChannelError("CHANNEL_INVALID_INPUT", false, { connectionId: connection.id }) : statusFailure(result.status, "send", connection.id);
76
+ }
77
+ const body = parseJson(result.text);
78
+ return receiptFor(typeof body?.message_id === "string" ? body.message_id : undefined);
79
+ },
80
+ };
81
+ }
@@ -0,0 +1,40 @@
1
+ import type { z } from "zod";
2
+ import type { ConnectionHealth, SendReceipt, ValidationResult } from "../types.js";
3
+ /**
4
+ * Adapter helpers that every provider shares. Each one owns a rule that used to be copied into
5
+ * each adapter and had started to drift (for example how an unexpected error in a check is
6
+ * retried). Adapters keep only what makes them different: the protocol of their own send.
7
+ */
8
+ /** Turns a zod schema into a `ValidationResult`, naming the failing paths or `root` when there is none. */
9
+ export declare function zodValidator<S extends z.ZodType, V = z.infer<S>>(schema: S, root: string, normalize?: (data: z.infer<S>) => V): (input: unknown) => ValidationResult<V>;
10
+ /**
11
+ * Runs a live check. `run` returns when the connection works and throws a `ChannelError` for a
12
+ * definite failure. Any other error is treated as a transient connection failure, for every
13
+ * provider alike, because run start retries only failures marked retryable.
14
+ */
15
+ export declare function healthOf(run: () => Promise<void>): Promise<ConnectionHealth>;
16
+ /** The receipt for an accepted send. A provider that accepted it but returned no id still counts as accepted, with a warning. */
17
+ export declare function receiptFor(messageId: string | undefined): SendReceipt;
18
+ /** Bounds a send that has no timeout of its own. Expiry is an unknown outcome: the send may have gone out. */
19
+ export declare function withSendTimeout<T>(ms: number, connectionId: string, work: Promise<T>): Promise<T>;
20
+ export type SpectrumImporter = (specifier: string) => Promise<any>;
21
+ export declare const importModule: SpectrumImporter;
22
+ export type SpectrumPlatformRef = {
23
+ /** npm package of the platform, such as `@spectrum-ts/imessage`. */
24
+ pkg: string;
25
+ /** Name of its export, such as `imessage`. */
26
+ exportName: string;
27
+ };
28
+ /**
29
+ * The Spectrum SDK lifecycle for one send: load the packages on first use (so a host that never
30
+ * sends over Photon does not need them), create the app, hand the platform to `run`, and always
31
+ * stop the app. Nothing stays connected between sends. A missing package is
32
+ * `CHANNEL_PROVIDER_UNSUPPORTED`; a failure to stop never hides the send's own result.
33
+ */
34
+ export declare function withSpectrumApp<T>(importer: SpectrumImporter, credentials: {
35
+ projectId: string;
36
+ projectSecret: string;
37
+ }, platform: SpectrumPlatformRef, run: (app: {
38
+ platform: any;
39
+ module: any;
40
+ }) => Promise<T>): Promise<T>;
@@ -0,0 +1,78 @@
1
+ import { ChannelError } from "../errors.js";
2
+ /**
3
+ * Adapter helpers that every provider shares. Each one owns a rule that used to be copied into
4
+ * each adapter and had started to drift (for example how an unexpected error in a check is
5
+ * retried). Adapters keep only what makes them different: the protocol of their own send.
6
+ */
7
+ /** Turns a zod schema into a `ValidationResult`, naming the failing paths or `root` when there is none. */
8
+ export function zodValidator(schema, root, normalize) {
9
+ return input => {
10
+ const parsed = schema.safeParse(input);
11
+ if (!parsed.success)
12
+ return { ok: false, issues: parsed.error.issues.map(issue => issue.path.join(".") || root) };
13
+ return { ok: true, value: normalize ? normalize(parsed.data) : parsed.data };
14
+ };
15
+ }
16
+ /**
17
+ * Runs a live check. `run` returns when the connection works and throws a `ChannelError` for a
18
+ * definite failure. Any other error is treated as a transient connection failure, for every
19
+ * provider alike, because run start retries only failures marked retryable.
20
+ */
21
+ export async function healthOf(run) {
22
+ const checkedAt = new Date().toISOString();
23
+ try {
24
+ await run();
25
+ return { state: "ok", checkedAt };
26
+ }
27
+ catch (error) {
28
+ const failure = error instanceof ChannelError ? error : new ChannelError("CHANNEL_CONNECTION_FAILED", true);
29
+ return { state: "failed", checkedAt, error: { code: failure.code, retryable: failure.retryable } };
30
+ }
31
+ }
32
+ /** The receipt for an accepted send. A provider that accepted it but returned no id still counts as accepted, with a warning. */
33
+ export function receiptFor(messageId) {
34
+ return {
35
+ ...(messageId !== undefined ? { providerMessageId: messageId } : {}),
36
+ submittedAt: new Date().toISOString(),
37
+ ...(messageId === undefined ? { warnings: ["CHANNEL_RESPONSE_UNPARSEABLE"] } : {}),
38
+ };
39
+ }
40
+ /** Bounds a send that has no timeout of its own. Expiry is an unknown outcome: the send may have gone out. */
41
+ export async function withSendTimeout(ms, connectionId, work) {
42
+ let timer;
43
+ const expired = new Promise((_, reject) => {
44
+ timer = setTimeout(() => reject(new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN", false, { connectionId })), ms);
45
+ });
46
+ try {
47
+ return await Promise.race([work, expired]);
48
+ }
49
+ finally {
50
+ clearTimeout(timer);
51
+ }
52
+ }
53
+ export const importModule = specifier => import(specifier);
54
+ /**
55
+ * The Spectrum SDK lifecycle for one send: load the packages on first use (so a host that never
56
+ * sends over Photon does not need them), create the app, hand the platform to `run`, and always
57
+ * stop the app. Nothing stays connected between sends. A missing package is
58
+ * `CHANNEL_PROVIDER_UNSUPPORTED`; a failure to stop never hides the send's own result.
59
+ */
60
+ export async function withSpectrumApp(importer, credentials, platform, run) {
61
+ let core;
62
+ let module;
63
+ try {
64
+ core = await importer("@spectrum-ts/core");
65
+ module = await importer(platform.pkg);
66
+ }
67
+ catch {
68
+ throw new ChannelError("CHANNEL_PROVIDER_UNSUPPORTED");
69
+ }
70
+ const exported = module[platform.exportName];
71
+ const app = await core.Spectrum({ projectId: credentials.projectId, projectSecret: credentials.projectSecret, providers: [exported.config()] });
72
+ try {
73
+ return await run({ platform: exported(app), module });
74
+ }
75
+ finally {
76
+ await app.stop().catch(() => undefined);
77
+ }
78
+ }
@@ -0,0 +1,45 @@
1
+ import { ChannelError } from "../errors.js";
2
+ import { type SpectrumImporter } from "./kit.js";
3
+ import type { ChannelProvider, ProviderField, ProviderMeta, ResolvedConnection } from "../types.js";
4
+ export type PhotonImessageConfig = Record<string, never>;
5
+ export type PhotonImessageTarget = {
6
+ recipient: string;
7
+ };
8
+ export type PhotonCredentials = {
9
+ projectId: string;
10
+ projectSecret: string;
11
+ };
12
+ /** Sends one iMessage text. Implementations throw `ChannelError` for failures they can classify. */
13
+ export interface PhotonImessageTransport {
14
+ sendText(credentials: PhotonCredentials, recipient: string, text: string): Promise<{
15
+ messageId?: string;
16
+ }>;
17
+ }
18
+ export declare const PHOTON_BASE_URL = "https://spectrum.photon.codes";
19
+ export declare const PHOTON_FIELDS: ProviderField[];
20
+ export declare const PHOTON_CREDENTIAL_YAML = "credentials:\n - id: photon-main\n provider: photon\n secrets:\n projectId: env:PHOTON_PROJECT_ID\n projectSecret: env:PHOTON_PROJECT_SECRET\n";
21
+ export declare const IMESSAGE_META: ProviderMeta;
22
+ export type PhotonImessageOptions = {
23
+ fetch?: typeof fetch;
24
+ baseUrl?: string;
25
+ transport?: PhotonImessageTransport;
26
+ sendTimeoutMs?: number;
27
+ checkTimeoutMs?: number;
28
+ };
29
+ export declare function credentialsOf(connection: ResolvedConnection<unknown>): PhotonCredentials;
30
+ export declare function photonHeaders({ projectId, projectSecret }: PhotonCredentials): Record<string, string>;
31
+ /** Photon wraps responses as `{ succeed, data }`. Older shapes without the wrapper are still read. */
32
+ export declare function photonData(body: unknown): Record<string, unknown> | undefined;
33
+ /** Maps what the Spectrum SDK throws. Unclassified errors may have happened after the send, so they are unknown. */
34
+ export declare function classifySdkError(error: unknown, connectionId: string): ChannelError;
35
+ /**
36
+ * The Spectrum SDK send for iMessage. The lifecycle (lazy import, create, stop) is shared with
37
+ * WhatsApp in `withSpectrumApp`.
38
+ */
39
+ export declare function spectrumTransport(importer?: SpectrumImporter): PhotonImessageTransport;
40
+ /**
41
+ * iMessage through a Photon (Spectrum) project, DM only. Credentials come from a shared `photon`
42
+ * credential. Free and Pro projects can message only users registered in the project, which the
43
+ * target check looks up; Stageflow never registers users. Receiving is not part of this phase.
44
+ */
45
+ export declare function createPhotonImessageProvider(options?: PhotonImessageOptions): ChannelProvider<PhotonImessageConfig, PhotonImessageTarget>;
@@ -0,0 +1,152 @@
1
+ import { z } from "zod";
2
+ import { ChannelError } from "../errors.js";
3
+ import { httpCall, parseJson, statusFailure } from "../http.js";
4
+ import { healthOf, importModule, receiptFor, withSendTimeout, withSpectrumApp, zodValidator } from "./kit.js";
5
+ export const PHOTON_BASE_URL = "https://spectrum.photon.codes";
6
+ export const PHOTON_FIELDS = [
7
+ { key: "projectId", label: "Photon project ID", hint: "Project settings in app.photon.codes", target: "credential", input: "password", required: true, placeholder: "3f9c21aa-..." },
8
+ { key: "projectSecret", label: "Photon project secret", hint: "Stored on this host. Never written to connections.yaml.", target: "credential", input: "password", required: true },
9
+ ];
10
+ export const PHOTON_CREDENTIAL_YAML = `credentials:
11
+ - id: photon-main
12
+ provider: photon
13
+ secrets:
14
+ projectId: env:PHOTON_PROJECT_ID
15
+ projectSecret: env:PHOTON_PROJECT_SECRET
16
+ `;
17
+ export const IMESSAGE_META = {
18
+ label: "iMessage",
19
+ blurb: "Not connected · via Photon",
20
+ icon: "message-circle",
21
+ via: "Photon",
22
+ note: "Sending iMessage uses the Spectrum SDK packages (optional dependencies). On a Free or Pro Photon project, each recipient must be a project user and must text their assigned line once before Stageflow can message them.",
23
+ fields: PHOTON_FIELDS,
24
+ recipient: { label: "Recipient", placeholder: "+14155550123 or an Apple ID email", multiple: false, cc: false, template: false },
25
+ envNames: ["PHOTON_PROJECT_ID", "PHOTON_PROJECT_SECRET"],
26
+ yamlExample: `${PHOTON_CREDENTIAL_YAML}connections:
27
+ - id: alerts-imessage
28
+ provider: imessage
29
+ credential: photon-main
30
+ `,
31
+ stageExample: ' to: "+14155550123"',
32
+ };
33
+ const e164 = /^\+[1-9]\d{6,14}$/;
34
+ const email = /^[^\s@<>,;]+@[^\s@<>,;]+$/;
35
+ const targetSchema = z.object({ to: z.string().max(320).refine(value => e164.test(value) || email.test(value)) }).strict();
36
+ const validateConfig = zodValidator(z.object({}).strict(), "config", () => ({}));
37
+ const validateTarget = zodValidator(targetSchema, "target", (data) => ({ recipient: data.to }));
38
+ export function credentialsOf(connection) {
39
+ const secrets = connection.credential?.secrets;
40
+ if (!secrets?.projectId || !secrets.projectSecret)
41
+ throw new ChannelError("CHANNEL_SECRET_UNAVAILABLE", false, { connectionId: connection.id });
42
+ return { projectId: secrets.projectId, projectSecret: secrets.projectSecret };
43
+ }
44
+ export function photonHeaders({ projectId, projectSecret }) {
45
+ return { authorization: `Basic ${Buffer.from(`${projectId}:${projectSecret}`).toString("base64")}`, accept: "application/json" };
46
+ }
47
+ /** Photon wraps responses as `{ succeed, data }`. Older shapes without the wrapper are still read. */
48
+ export function photonData(body) {
49
+ if (!body || typeof body !== "object")
50
+ return undefined;
51
+ const wrapped = body.data;
52
+ return wrapped && typeof wrapped === "object" ? wrapped : body;
53
+ }
54
+ function containsString(value, wanted) {
55
+ if (typeof value === "string")
56
+ return value.toLowerCase() === wanted.toLowerCase();
57
+ if (Array.isArray(value))
58
+ return value.some(item => containsString(item, wanted));
59
+ if (value && typeof value === "object")
60
+ return Object.values(value).some(item => containsString(item, wanted));
61
+ return false;
62
+ }
63
+ /** Maps what the Spectrum SDK throws. Unclassified errors may have happened after the send, so they are unknown. */
64
+ export function classifySdkError(error, connectionId) {
65
+ if (error instanceof ChannelError)
66
+ return error;
67
+ const detail = { connectionId };
68
+ const status = error?.status;
69
+ const message = error instanceof Error ? error.message : "";
70
+ if (/target not allowed/i.test(message))
71
+ return new ChannelError("CHANNEL_RECIPIENT_REJECTED", false, detail);
72
+ if (status === 401)
73
+ return new ChannelError("CHANNEL_AUTH_FAILED", false, detail);
74
+ if (status === 403)
75
+ return new ChannelError("CHANNEL_UNAUTHORIZED", false, detail);
76
+ if (status === 429)
77
+ return new ChannelError("CHANNEL_RATE_LIMIT", true, detail);
78
+ return new ChannelError("CHANNEL_SEND_OUTCOME_UNKNOWN", false, detail);
79
+ }
80
+ /**
81
+ * The Spectrum SDK send for iMessage. The lifecycle (lazy import, create, stop) is shared with
82
+ * WhatsApp in `withSpectrumApp`.
83
+ */
84
+ export function spectrumTransport(importer = importModule) {
85
+ return {
86
+ sendText: (credentials, recipient, text) => withSpectrumApp(importer, credentials, { pkg: "@spectrum-ts/imessage", exportName: "imessage" }, async ({ platform }) => {
87
+ const user = await platform.user(recipient);
88
+ const space = await platform.space.create(user);
89
+ const sent = await space.send(text);
90
+ return typeof sent?.id === "string" ? { messageId: sent.id } : {};
91
+ }),
92
+ };
93
+ }
94
+ /**
95
+ * iMessage through a Photon (Spectrum) project, DM only. Credentials come from a shared `photon`
96
+ * credential. Free and Pro projects can message only users registered in the project, which the
97
+ * target check looks up; Stageflow never registers users. Receiving is not part of this phase.
98
+ */
99
+ export function createPhotonImessageProvider(options = {}) {
100
+ const doFetch = options.fetch ?? fetch;
101
+ const base = options.baseUrl ?? PHOTON_BASE_URL;
102
+ const transport = options.transport ?? spectrumTransport();
103
+ return {
104
+ id: "imessage",
105
+ meta: IMESSAGE_META,
106
+ operations: ["send"],
107
+ secrets: { required: [] },
108
+ credentialProvider: "photon",
109
+ validateConfig,
110
+ validateTarget,
111
+ check: (connection, signal) => healthOf(async () => {
112
+ const credentials = credentialsOf(connection);
113
+ const result = await httpCall({
114
+ fetch: doFetch, url: `${base}/projects/${encodeURIComponent(credentials.projectId)}/imessage/tokens`, method: "POST",
115
+ headers: photonHeaders(credentials), body: {}, timeoutMs: options.checkTimeoutMs ?? 10000, signal, mode: "check", connectionId: connection.id,
116
+ });
117
+ if (result.status < 200 || result.status >= 300)
118
+ throw statusFailure(result.status, "check", connection.id);
119
+ }),
120
+ checkTargets: (connection, targets, signal) => healthOf(async () => {
121
+ const credentials = credentialsOf(connection);
122
+ const project = encodeURIComponent(credentials.projectId);
123
+ const call = (url, init) => httpCall({
124
+ fetch: doFetch, url, method: init.method, headers: photonHeaders(credentials), ...(init.body !== undefined ? { body: init.body } : {}),
125
+ timeoutMs: options.checkTimeoutMs ?? 10000, signal, mode: "check", connectionId: connection.id,
126
+ });
127
+ const tokens = await call(`${base}/projects/${project}/imessage/tokens`, { method: "POST", body: {} });
128
+ if (tokens.status < 200 || tokens.status >= 300)
129
+ throw statusFailure(tokens.status, "check", connection.id);
130
+ if (photonData(parseJson(tokens.text))?.type === "dedicated")
131
+ return;
132
+ for (const target of targets) {
133
+ const found = await call(`${base}/projects/${project}/users/?search=${encodeURIComponent(target.recipient)}`, { method: "GET" });
134
+ if (found.status < 200 || found.status >= 300)
135
+ throw statusFailure(found.status, "check", connection.id);
136
+ if (!containsString(parseJson(found.text), target.recipient))
137
+ throw new ChannelError("CHANNEL_RECIPIENT_REJECTED", false, { connectionId: connection.id });
138
+ }
139
+ }),
140
+ async send(connection, target, message, _context) {
141
+ const credentials = credentialsOf(connection);
142
+ const text = message.subject ? `${message.subject}\n\n${message.text}` : message.text;
143
+ try {
144
+ const sent = await withSendTimeout(options.sendTimeoutMs ?? 45000, connection.id, transport.sendText(credentials, target.recipient, text));
145
+ return receiptFor(sent.messageId);
146
+ }
147
+ catch (error) {
148
+ throw classifySdkError(error, connection.id);
149
+ }
150
+ },
151
+ };
152
+ }
@@ -0,0 +1,49 @@
1
+ import { ChannelError } from "../errors.js";
2
+ import { type SpectrumImporter } from "./kit.js";
3
+ import type { ChannelProvider, OutboundMessage, ProviderMeta } from "../types.js";
4
+ import { type PhotonCredentials } from "./photonImessage.js";
5
+ export type WhatsappTemplate = {
6
+ name: string;
7
+ languageCode: string;
8
+ bodyParams?: string[];
9
+ };
10
+ export type WhatsappTarget = {
11
+ recipient: string;
12
+ template?: WhatsappTemplate;
13
+ };
14
+ export type WhatsappConfig = Record<string, never>;
15
+ export declare const WHATSAPP_META: ProviderMeta;
16
+ export declare const MESSAGE_PLACEHOLDER = "{{message}}";
17
+ export declare const MAX_TEMPLATE_PARAM = 1024;
18
+ /** One outbound message. `template` is sent as an approved template, otherwise `text` is free-form. */
19
+ export type WhatsappOutbound = {
20
+ text: string;
21
+ } | {
22
+ template: WhatsappTemplate;
23
+ };
24
+ export interface PhotonWhatsappTransport {
25
+ send(credentials: PhotonCredentials, recipient: string, outbound: WhatsappOutbound): Promise<{
26
+ messageId?: string;
27
+ }>;
28
+ }
29
+ export type PhotonWhatsappOptions = {
30
+ fetch?: typeof fetch;
31
+ baseUrl?: string;
32
+ transport?: PhotonWhatsappTransport;
33
+ sendTimeoutMs?: number;
34
+ checkTimeoutMs?: number;
35
+ };
36
+ /** Template parameters cannot hold newlines, tabs or long runs of spaces. */
37
+ export declare function templateParam(text: string): string;
38
+ /** Fills `{{message}}` in the pinned parameters. Fails before sending if a result is empty or too long. */
39
+ export declare function fillTemplate(template: WhatsappTemplate, message: OutboundMessage, connectionId: string): WhatsappTemplate;
40
+ /** Maps SDK and Meta errors. Unclassified errors may have happened after the send, so they are unknown. */
41
+ export declare function classifyWhatsappError(error: unknown, connectionId: string): ChannelError;
42
+ /** The Spectrum SDK send for WhatsApp, on the same shared lifecycle as iMessage. */
43
+ export declare function spectrumWhatsappTransport(importer?: SpectrumImporter): PhotonWhatsappTransport;
44
+ /**
45
+ * WhatsApp Business through a Photon project (cloud mode), 1:1 only. Free-form text works only
46
+ * inside the 24-hour window after the recipient last wrote to the business, so a stage that
47
+ * writes first must pin an approved `template`. Receiving is not part of this phase.
48
+ */
49
+ export declare function createPhotonWhatsappProvider(options?: PhotonWhatsappOptions): ChannelProvider<WhatsappConfig, WhatsappTarget>;