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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Pi implementation of AgentPort.
2
+ * Pi implementation of StagePort.
3
3
  *
4
4
  * One stage = one `createAgentSession` (single-shot SDK path, not
5
5
  * AgentSessionRuntime / interactive / RPC). The session is sealed from host
@@ -35,19 +35,25 @@ import path from "node:path";
35
35
  import { BROWSER_SKILL_NAME } from "../config/builtinSkills.js";
36
36
  import { defineTool, ModelRuntime, resolveCliModel, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
37
37
  import { StageMcpError } from "../config/resolveStageMcpServers.js";
38
- import { attachIsolatedMcp, STAGEFLOW_PI_MCP_EXTENSION_NAME, } from "./piIsolatedMcp.js";
38
+ import { attachIsolatedMcp, STAGEFLOW_PI_MCP_EXTENSION_NAME, UNREQUESTED_MCP_ADAPTER_TOOLS, } from "./piIsolatedMcp.js";
39
+ import { warmIsolatedMcpMetadata } from "./piIsolatedMcpProbe.js";
40
+ import { createToolOutputBudgetExtension, STAGEFLOW_TOOL_OUTPUT_EXTENSION_NAME, } from "./toolOutputBudgetExtension.js";
41
+ import { TOOL_OUTPUT_ARTIFACT_DIR } from "./toolOutputSpill.js";
39
42
  import { createPiAgentSession, createSealedResourceLoader, } from "./piSessionFactory.js";
40
43
  import { isAdvancingEnvelope } from "../envelope/check.js";
41
44
  import { formatFeedbackLoopContext } from "../prompt/feedbackLoopContext.js";
42
45
  import { formatPriorEnvelope } from "../prompt/priorEnvelope.js";
43
46
  import { attemptContext, noAttemptContext, } from "../runtime/stageAttemptContext.js";
44
47
  import { createEmitStageEnvelopeTool, } from "../tools/emitStageEnvelope.js";
48
+ import { remindUntilEmitted } from "./stageTurnDriver.js";
45
49
  import { createAskOperatorTool, } from "../tools/askOperator.js";
46
50
  import { createWriteStageArtifactTool } from "../tools/writeStageArtifact.js";
47
51
  import { createSendEmailTool } from "../tools/sendEmail.js";
52
+ import { createSendMessageTool } from "../tools/sendMessage.js";
48
53
  import { createReplyEmailTool } from "../tools/replyEmail.js";
49
54
  import { createGetEmailTool, createSearchEmailTool, createDownloadEmailAttachmentTool } from "../tools/readEmail.js";
50
55
  import "./cursorProvider.js";
56
+ import { isCursorModelRef, readCursorApiKey } from "./cursorProvider.js";
51
57
  import { findProviderSupport } from "./providerSupport.js";
52
58
  import { mapSessionEventToActivity, readActivityVerbose } from "./activity.js";
53
59
  import { createStageActivityObserver, } from "./activityObserver.js";
@@ -55,6 +61,8 @@ import { DEFAULT_STAGE_TIMEOUT_MS, runtimeStageId, runStageViaOpen } from "./por
55
61
  import { addModelUsage, emptyStageUsage } from "../types/usage.js";
56
62
  import { TIMEOUT_ABORT_TOOL_RESULT, composeTimeoutResumePrompt, stageTimeoutReason, } from "./stageTimeout.js";
57
63
  import { globalStageflowHome } from "../project/globalHome.js";
64
+ import { isUsableAuthFile, STAGEFLOW_AGENT_AUTH_PATH_ENV, } from "../runtime/credentialBinding.js";
65
+ import { SF_STAGE_WORKER } from "../runtime/stageWorkerProtocol.js";
58
66
  import { durableRootFileToolDenial, STAGEFLOW_PATH_DENIED, } from "../runstore/workspaceLayout.js";
59
67
  export { STAGEFLOW_PATH_DENIED };
60
68
  export { createSealedResourceLoader } from "./piSessionFactory.js";
@@ -462,6 +470,10 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
462
470
  ].filter((dir) => dir !== undefined);
463
471
  const skillBaseDir = skillBaseDirs.length > 0 ? skillBaseDirs.join(", ") : undefined;
464
472
  const browserGuidance = formatBrowserGuidance(input);
473
+ const toolOutputGuidance = Object.keys(input.resolvedMcpServers ?? {}).length > 0 ||
474
+ (input.stage.email?.length ?? 0) > 0
475
+ ? `Large tool results are saved automatically as stage artifacts under ${attemptArtifactsPath}${TOOL_OUTPUT_ARTIFACT_DIR}/. The tool result names the path. Read or grep that file; do not copy tool output into ${artifactToolName ?? "an artifact"}.`
476
+ : "";
465
477
  const artifactGuidance = input.roots.mode === "bound" && artifactToolName !== undefined
466
478
  ? [
467
479
  `Project checkout (agent cwd): ${input.roots.checkoutRoot}`,
@@ -482,6 +494,9 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
482
494
  ].join("\n")
483
495
  : `Create factory stage artifacts under ${attemptArtifactsPath} relative to the run folder.`;
484
496
  return [
497
+ ...(input.stage.channels?.length ? [
498
+ `Stageflow channels: ${JSON.stringify(input.stage.channels.map(channel => ({ channel: channel.alias ?? channel.connection, operations: channel.operations })))}. Use send_message with the channel name. Recipients are fixed by configuration and you cannot change them. Use a stable operationKey for each intended message. Provider acceptance does not prove delivery. Never resend an unknown submission automatically.`,
499
+ ] : []),
485
500
  ...(input.stage.email?.length ? [
486
501
  `Stageflow email permissions: ${JSON.stringify(input.stage.email)}. Use only declared accounts and operations. Use a stable operationKey for each intended message. Provider acceptance does not prove delivery. Never resend an unknown submission automatically.`,
487
502
  ] : []),
@@ -499,6 +514,7 @@ function buildUserPrompt(input, emitToolName, emitHintOverride, artifactToolName
499
514
  : "",
500
515
  "",
501
516
  artifactGuidance,
517
+ toolOutputGuidance,
502
518
  browserGuidance,
503
519
  emitHint,
504
520
  ]
@@ -543,15 +559,26 @@ export function composeFeedbackResumePrompt(input) {
543
559
  formatFeedbackLoopContext(input.feedbackLoopContext, input.sessionMode ?? "feedback_resume"),
544
560
  ].join("\n\n");
545
561
  }
546
- function collectMcpExtensionToolNames(loader) {
562
+ function mcpExtensionTools(loader) {
547
563
  const inlinePath = `<inline:${STAGEFLOW_PI_MCP_EXTENSION_NAME}>`;
564
+ return loader
565
+ .getExtensions()
566
+ .extensions.filter((ext) => ext.path === inlinePath)
567
+ .flatMap((ext) => [...ext.tools.keys()]);
568
+ }
569
+ function mcpToolMetadataMissing(loader) {
570
+ return mcpExtensionTools(loader).includes("mcp");
571
+ }
572
+ function collectMcpExtensionToolNames(loader) {
548
573
  const names = [];
549
- for (const ext of loader.getExtensions().extensions) {
550
- if (ext.path !== inlinePath)
551
- continue;
552
- names.push(...ext.tools.keys());
553
- }
554
- return names;
574
+ const unrequested = [];
575
+ for (const name of mcpExtensionTools(loader)) {
576
+ if (UNREQUESTED_MCP_ADAPTER_TOOLS.has(name))
577
+ unrequested.push(name);
578
+ else
579
+ names.push(name);
580
+ }
581
+ return unrequested.length > 0 ? { ok: false, unrequested } : { ok: true, names };
555
582
  }
556
583
  async function shutdownSession(session) {
557
584
  if (!session) {
@@ -574,7 +601,10 @@ async function shutdownSession(session) {
574
601
  // ignore dispose errors during teardown
575
602
  }
576
603
  }
577
- function resultFromCapture(capture) {
604
+ function resultFromCapture(capture, providerError) {
605
+ if (providerError !== undefined && !capture.envelope) {
606
+ return { ok: false, reason: `provider error: ${providerError}` };
607
+ }
578
608
  if (capture.error && !capture.envelope) {
579
609
  return { ok: false, reason: capture.error };
580
610
  }
@@ -800,6 +830,26 @@ export function injectOpaqueAnswerIntoSession(sessionManager, answer) {
800
830
  async function prepareStageSessionWiring(input, sessionManager, existingAskWaitChannel) {
801
831
  const { roots } = input;
802
832
  const provider = findProviderSupport(input.stage.model);
833
+ if (process.env[SF_STAGE_WORKER] === "1" && !provider) {
834
+ if (!roots.authPath || !isUsableAuthFile(roots.authPath)) {
835
+ const authPath = roots.authPath;
836
+ return {
837
+ ok: false,
838
+ reason: authPath
839
+ ? `Stage provider auth is not configured (auth file missing or empty: ${authPath}).`
840
+ : `Stage provider auth is not configured (${STAGEFLOW_AGENT_AUTH_PATH_ENV} is unset).`,
841
+ };
842
+ }
843
+ }
844
+ if (process.env[SF_STAGE_WORKER] === "1" &&
845
+ provider &&
846
+ isCursorModelRef(input.stage.model) &&
847
+ !readCursorApiKey()) {
848
+ return {
849
+ ok: false,
850
+ reason: "Stage provider auth is not configured (Cursor API key missing from the credential root).",
851
+ };
852
+ }
803
853
  const capture = {};
804
854
  const usage = emptyStageUsage();
805
855
  const askWaitChannel = existingAskWaitChannel ?? new AskOperatorWaitChannel();
@@ -859,7 +909,9 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
859
909
  const settingsManager = SettingsManager.inMemory({
860
910
  compaction: { enabled: false },
861
911
  });
862
- attached = await attachIsolatedMcp(input.resolvedMcpServers);
912
+ attached = await attachIsolatedMcp(input.resolvedMcpServers, {
913
+ lifecycle: "eager",
914
+ });
863
915
  const failAfterAttach = (reason) => {
864
916
  attached?.cancel?.();
865
917
  restoreProvider?.();
@@ -876,6 +928,21 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
876
928
  : {}),
877
929
  }),
878
930
  },
931
+ {
932
+ name: STAGEFLOW_TOOL_OUTPUT_EXTENSION_NAME,
933
+ factory: createToolOutputBudgetExtension({
934
+ runWorkspaceDir: roots.runWorkspaceDir,
935
+ stageId: runtimeStageId(input),
936
+ attempt: roots.attempt ?? 1,
937
+ exempt: new Set([
938
+ "read",
939
+ "bash",
940
+ emitDef.name,
941
+ artifactDef.name,
942
+ askDef?.name ?? "ask_operator",
943
+ ]),
944
+ }),
945
+ },
879
946
  ...(attached.extensionFactories ?? []),
880
947
  ];
881
948
  const loader = createSealedResourceLoader({
@@ -889,6 +956,23 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
889
956
  ...(attached.eventBus !== undefined ? { eventBus: attached.eventBus } : {}),
890
957
  });
891
958
  await loader.reload();
959
+ if (attached.extensionFactories !== undefined &&
960
+ input.resolvedMcpServers !== undefined &&
961
+ mcpToolMetadataMissing(loader)) {
962
+ // A cold adapter metadata cache registers only the `mcp` gateway at load,
963
+ // and the session allowlist is fixed at load. Warm the cache, then reload
964
+ // so the declared servers' direct tools exist when the allowlist is built.
965
+ try {
966
+ await warmIsolatedMcpMetadata(input.resolvedMcpServers, { cwd: roots.cwd });
967
+ }
968
+ catch (err) {
969
+ return failAfterAttach(err instanceof Error ? err.message : String(err));
970
+ }
971
+ await loader.reload();
972
+ if (mcpToolMetadataMissing(loader)) {
973
+ return failAfterAttach(`MCP tool metadata unavailable for: ${Object.keys(input.resolvedMcpServers).join(", ")}`);
974
+ }
975
+ }
892
976
  const extensionErrors = loader.getExtensions().errors;
893
977
  if (extensionErrors.length > 0) {
894
978
  return failAfterAttach(`Failed to load Pi extension(s): ${extensionErrors
@@ -912,7 +996,16 @@ async function prepareStageSessionWiring(input, sessionManager, existingAskWaitC
912
996
  : [emitTool, artifactTool];
913
997
  const tools = resolveStageToolNames(emitDef.name, artifactDef.name, askDef?.name ?? "ask_operator", gateKinds);
914
998
  if (attached.extensionFactories !== undefined) {
915
- tools.push(...collectMcpExtensionToolNames(loader));
999
+ const mcpTools = collectMcpExtensionToolNames(loader);
1000
+ if (!mcpTools.ok) {
1001
+ return failAfterAttach(`MCP adapter registered unrequested tool(s): ${mcpTools.unrequested.join(", ")}`);
1002
+ }
1003
+ tools.push(...mcpTools.names);
1004
+ }
1005
+ if (input.channels && input.stage.channels?.some(channel => channel.operations.includes("send"))) {
1006
+ const definition = createSendMessageTool(input.channels);
1007
+ customTools.push(defineTool(definition));
1008
+ tools.push(definition.name);
916
1009
  }
917
1010
  if (input.email) {
918
1011
  const permitted = new Set(input.stage.email?.flatMap(permission => permission.operations));
@@ -1178,6 +1271,7 @@ export class PiAgentAdapter {
1178
1271
  let wiring;
1179
1272
  let unsubscribeProgress;
1180
1273
  let prepareError;
1274
+ let closing = false;
1181
1275
  const preparePromise = (async () => {
1182
1276
  if (forceNewSession) {
1183
1277
  await mkdir(path.dirname(sessionFile), { recursive: true });
@@ -1213,15 +1307,30 @@ export class PiAgentAdapter {
1213
1307
  const controller = new AbortController();
1214
1308
  const timer = setTimeout(() => controller.abort(), timeoutMs);
1215
1309
  try {
1216
- const workPromise = work();
1310
+ const live = session;
1311
+ const capture = wiring.capture;
1312
+ const emitToolName = wiring.emitDefName;
1313
+ const turns = {
1314
+ prompt: (text) => live.prompt(text),
1315
+ get messages() {
1316
+ return live.messages ?? live.agent?.state?.messages ?? [];
1317
+ },
1318
+ };
1319
+ const workPromise = (async () => {
1320
+ await work();
1321
+ return remindUntilEmitted(turns, capture, {
1322
+ emitToolName,
1323
+ shouldStop: () => closing || controller.signal.aborted,
1324
+ });
1325
+ })();
1217
1326
  const abortPromise = new Promise((_, reject) => {
1218
1327
  controller.signal.addEventListener("abort", () => {
1219
1328
  void session?.abort();
1220
1329
  reject(new Error(stageTimeoutReason(timeoutMs)));
1221
1330
  });
1222
1331
  });
1223
- await Promise.race([workPromise, abortPromise]);
1224
- return { ...resultFromCapture(wiring.capture), usage: wiring.usage };
1332
+ const outcome = await Promise.race([workPromise, abortPromise]);
1333
+ return { ...resultFromCapture(capture, outcome.providerError), usage: wiring.usage };
1225
1334
  }
1226
1335
  catch (err) {
1227
1336
  if (sessionManager) {
@@ -1292,6 +1401,7 @@ export class PiAgentAdapter {
1292
1401
  }
1293
1402
  : undefined,
1294
1403
  onClose: async (closeOptions) => {
1404
+ closing = true;
1295
1405
  await preparePromise.catch(() => undefined);
1296
1406
  if (closeOptions?.park) {
1297
1407
  if (sessionManager) {
@@ -17,6 +17,7 @@ export type IsolatedMcpAttach = {
17
17
  connecting: Promise<void> | undefined;
18
18
  cancel?: () => void;
19
19
  };
20
+ export declare const UNREQUESTED_MCP_ADAPTER_TOOLS: ReadonlySet<string>;
20
21
  export declare function emitIsolatedMcpStatus(eventBus: EventBus, servers: ReadonlyArray<{
21
22
  name: string;
22
23
  status: string;
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
  import { createEventBus, } from "@earendil-works/pi-coding-agent";
3
3
  import { StageMcpError, } from "../config/resolveStageMcpServers.js";
4
4
  import { globalStageflowHome } from "../project/globalHome.js";
5
+ import { TOOL_OUTPUT_HARD_CEILING_BYTES } from "./toolOutputBudget.js";
5
6
  export const STAGEFLOW_PI_MCP_EXTENSION_NAME = "stageflow-mcp";
6
7
  const MCP_STATUS_EVENT = "pi-mcp-adapter/status/v1";
7
8
  const DEFAULT_ISOLATED_MCP_CONNECT_TIMEOUT_MS = 30_000;
@@ -13,11 +14,25 @@ export function jitiFsCacheDir() {
13
14
  export function resetCreateMcpAdapterCacheForTests() {
14
15
  cachedCreateMcpAdapter = undefined;
15
16
  }
17
+ // Stages get only the declared servers' direct tools: no mcpScript sandbox and
18
+ // no `mcp` gateway. The adapter's output guard is a backstop; the stage budget
19
+ // lives in toolOutputBudgetExtension.
16
20
  const ISOLATED_MCP_SETTINGS = {
17
21
  directTools: true,
18
22
  elicitation: false,
19
23
  hostConfigDiscovery: "off",
24
+ scriptMode: false,
25
+ disableProxyTool: true,
26
+ outputGuard: {
27
+ maxBytes: TOOL_OUTPUT_HARD_CEILING_BYTES,
28
+ maxLines: Number.MAX_SAFE_INTEGER,
29
+ detailsMaxBytes: 16 * 1024,
30
+ },
20
31
  };
32
+ export const UNREQUESTED_MCP_ADAPTER_TOOLS = new Set([
33
+ "mcpScript",
34
+ "mcp",
35
+ ]);
21
36
  const SUCCESS_STATUSES = new Set([
22
37
  "connected",
23
38
  "cached",
@@ -48,7 +63,10 @@ function toIsolatedMcpConfig(snapshot, lifecycle = "lazy") {
48
63
  }
49
64
  return {
50
65
  mcpServers,
51
- settings: { ...ISOLATED_MCP_SETTINGS },
66
+ settings: {
67
+ ...ISOLATED_MCP_SETTINGS,
68
+ outputGuard: { ...ISOLATED_MCP_SETTINGS.outputGuard },
69
+ },
52
70
  };
53
71
  }
54
72
  async function loadCreateMcpAdapter() {
@@ -1,3 +1,4 @@
1
+ import { type ResolvedMcpServers } from "../config/resolveStageMcpServers.js";
1
2
  export declare const PROJECT_MCP_PROBE_TIMEOUT_MS = 30000;
2
3
  export type ProjectMcpProbeStatus = "connected" | "needs_auth" | "connect_failed" | "unresolved_var" | "invalid_config" | "missing_catalog" | "cancelled";
3
4
  export type ProjectMcpProbeResult = {
@@ -13,3 +14,13 @@ export type ProbeProjectMcpServerOptions = {
13
14
  timeoutMs?: number;
14
15
  };
15
16
  export declare function probeProjectMcpServer(options: ProbeProjectMcpServerOptions): Promise<ProjectMcpProbeResult>;
17
+ /**
18
+ * Connects the snapshot's servers headlessly so the adapter writes its tool
19
+ * metadata cache into the current Pi agent dir. A sealed loader reloaded after
20
+ * this registers the servers' direct tools at load time, which is when the
21
+ * stage tool allowlist is computed.
22
+ */
23
+ export declare function warmIsolatedMcpMetadata(snapshot: ResolvedMcpServers, options: {
24
+ cwd: string;
25
+ timeoutMs?: number;
26
+ }): Promise<void>;
@@ -290,3 +290,23 @@ export async function probeProjectMcpServer(options) {
290
290
  await dispose();
291
291
  }
292
292
  }
293
+ /**
294
+ * Connects the snapshot's servers headlessly so the adapter writes its tool
295
+ * metadata cache into the current Pi agent dir. A sealed loader reloaded after
296
+ * this registers the servers' direct tools at load time, which is when the
297
+ * stage tool allowlist is computed.
298
+ */
299
+ export async function warmIsolatedMcpMetadata(snapshot, options) {
300
+ const attached = await piIsolatedMcp.attachIsolatedMcp(snapshot, {
301
+ lifecycle: "eager",
302
+ ...(options.timeoutMs !== undefined ? { timeoutMs: options.timeoutMs } : {}),
303
+ });
304
+ const shutdown = instantiateProbeFactories(attached, options.cwd);
305
+ try {
306
+ await attached.connecting;
307
+ }
308
+ finally {
309
+ attached.cancel?.();
310
+ await shutdown();
311
+ }
312
+ }
@@ -8,6 +8,7 @@ import type { StageActivityEvent } from "./activity.js";
8
8
  import type { ResolvedMcpServers } from "../config/resolveStageMcpServers.js";
9
9
  import type { StageRoots } from "../runtime/stageRoots.js";
10
10
  import type { StageEmail } from "../email/host.js";
11
+ import type { StageChannels } from "../channels/host.js";
11
12
  import type { QaExchange } from "../hitl/qaTrail.js";
12
13
  import type { StageUsage } from "../types/usage.js";
13
14
  /** Opaque to runtime; adapters interpret. */
@@ -65,6 +66,7 @@ export type StageRunInput = {
65
66
  onAssistantTextDelta?: (delta: string) => void;
66
67
  skillFilePath?: string;
67
68
  email?: StageEmail;
69
+ channels?: StageChannels;
68
70
  /** Bundled browser skill; set by the runtime when the stage has `browser`. */
69
71
  browserSkillFilePath?: string;
70
72
  forkEmitContext?: ForkEmitContext;
@@ -122,12 +124,12 @@ export interface StageHandle {
122
124
  deliverAnswer(answer: OpaqueAnswer): void;
123
125
  close(options?: StageHandleCloseOptions): Promise<void>;
124
126
  }
125
- export interface AgentPort {
127
+ export interface StagePort {
126
128
  openStage(input: StageRunInput): StageHandle;
127
129
  runStage(input: StageRunInput): Promise<StageRunResult>;
128
130
  }
129
131
  /** Thin non-HITL wrapper: open → next; unexpected wait fails closed. */
130
- export declare function runStageViaOpen(port: Pick<AgentPort, "openStage">, input: StageRunInput): Promise<StageRunResult>;
132
+ export declare function runStageViaOpen(port: Pick<StagePort, "openStage">, input: StageRunInput): Promise<StageRunResult>;
131
133
  /** Wrap a single-shot runner as a handle that never waits (Pi U2 shim). */
132
134
  export declare function createCompletedOnlyStageHandle(options: {
133
135
  stageId: string;
@@ -1,6 +1,8 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { mkdir, writeFile, chmod, access } from "node:fs/promises";
2
3
  import path from "node:path";
3
4
  import { CredentialSynchronizationError, ModelRuntime, } from "@earendil-works/pi-coding-agent";
5
+ import { builtinProviders } from "@earendil-works/pi-ai/providers/all";
4
6
  import { parseCredentialSource, resolveCredentialBinding, writeCredentialSourceToContext, isUsableAuthFile, } from "../runtime/credentialBinding.js";
5
7
  import { getCursorHostStatus } from "./cursorProvider.js";
6
8
  import { readPersistedCredentialSourceFromContext, } from "../runtime/settingsFile.js";
@@ -23,7 +25,13 @@ export class ProviderAuthError extends Error {
23
25
  this.status = status;
24
26
  }
25
27
  }
26
- const defaultCreateRuntime = async (authPath) => ModelRuntime.create({ authPath, refreshOnCreate: false });
28
+ const defaultCreateRuntime = async (authPath) => ModelRuntime.create({
29
+ authPath,
30
+ // ModelRuntime otherwise reads models.json from PI_CODING_AGENT_DIR,
31
+ // which stays on the process data directory.
32
+ modelsPath: path.join(path.dirname(authPath), "models.json"),
33
+ refreshOnCreate: false,
34
+ });
27
35
  async function ensurePiAuthFile(authPath) {
28
36
  const resolved = path.resolve(authPath);
29
37
  await mkdir(path.dirname(resolved), { recursive: true });
@@ -49,6 +57,31 @@ export async function openRuntime(cwd, ctx = defaultContext) {
49
57
  const binding = resolveCredentialBinding(cwd);
50
58
  return ctx.createRuntime(binding.authPath);
51
59
  }
60
+ function catalogRuntime() {
61
+ const providers = builtinProviders();
62
+ return {
63
+ getProviders: () => providers,
64
+ getProvider: (id) => providers.find((provider) => provider.id === id),
65
+ getProviderAuthStatus: () => ({ configured: false }),
66
+ listCredentials: async () => [],
67
+ checkAuth: async () => undefined,
68
+ login: async () => {
69
+ throw new ProviderAuthError("Provider login failed", 500);
70
+ },
71
+ logout: async () => {
72
+ throw new ProviderAuthError("Provider logout failed", 500);
73
+ },
74
+ };
75
+ }
76
+ async function openReadRuntime(cwd, ctx) {
77
+ const binding = resolveCredentialBinding(cwd);
78
+ // ModelRuntime.create writes auth.json when that file is missing.
79
+ if (ctx.createRuntime === defaultCreateRuntime &&
80
+ !existsSync(binding.authPath)) {
81
+ return catalogRuntime();
82
+ }
83
+ return ctx.createRuntime(binding.authPath);
84
+ }
52
85
  export function isLoginCapable(provider) {
53
86
  return (provider.auth?.oauth != null || provider.auth?.apiKey?.login != null);
54
87
  }
@@ -133,7 +166,7 @@ export async function statusForProvider(runtime, providerId) {
133
166
  return mapped;
134
167
  }
135
168
  export async function listProviders(cwd, ctx = defaultContext) {
136
- const runtime = await openRuntime(cwd, ctx);
169
+ const runtime = await openReadRuntime(cwd, ctx);
137
170
  return {
138
171
  authShell: "pi",
139
172
  via: "pi",
@@ -144,7 +177,7 @@ export async function listProviders(cwd, ctx = defaultContext) {
144
177
  };
145
178
  }
146
179
  export async function getAuthStatus(cwd, providerId, ctx = defaultContext) {
147
- const runtime = await openRuntime(cwd, ctx);
180
+ const runtime = await openReadRuntime(cwd, ctx);
148
181
  if (providerId !== undefined) {
149
182
  if (!runtime.getProvider(providerId)) {
150
183
  throw new ProviderAuthError("Provider not found", 404);
@@ -1,5 +1,5 @@
1
1
  import { type AgentBackendId } from "./agentBackend.js";
2
- import type { AgentPort } from "./port.js";
2
+ import type { StagePort } from "./port.js";
3
3
  import type { LoadedManifest } from "../types/stageflowManifest.js";
4
4
  export type AgentSelection = {
5
5
  /** stageflow.yaml top-level `agent`. */
@@ -10,14 +10,14 @@ export type AgentSelection = {
10
10
  stage?: AgentBackendId;
11
11
  };
12
12
  /** stage > pipeline > global > "pi", with stage gated off for now. */
13
- export declare function resolveAgentBackend(selection?: AgentSelection): AgentBackendId;
14
- /** Existing Pi AgentPort. Hosted runs pass this into createHostedRuntime. */
15
- export declare function createPiAgentPort(): AgentPort;
16
- export declare function resolveAgentPort(selection?: AgentSelection): AgentPort;
13
+ export declare function resolveStageBackend(selection?: AgentSelection): AgentBackendId;
14
+ /** Existing Pi StagePort. Hosted runs pass this into createHostedRuntime. */
15
+ export declare function createPiStagePort(): StagePort;
16
+ export declare function resolveStagePort(selection?: AgentSelection): StagePort;
17
17
  /** The global tier's backend id from an already-loaded manifest, or undefined for missing/invalid/unset. */
18
- export declare function globalAgentBackendFromManifest(manifest: LoadedManifest | null): AgentBackendId | undefined;
18
+ export declare function globalStageBackendFromManifest(manifest: LoadedManifest | null): AgentBackendId | undefined;
19
19
  /**
20
- * Resolves an AgentPort using only the global (stageflow.yaml) tier — for
20
+ * Resolves an StagePort using only the global (stageflow.yaml) tier — for
21
21
  * the three call sites that construct an agent before any pipeline is
22
22
  * known (`cli.ts`'s ui/mcp servers, `runCommand.ts`, `runsCommand.ts`).
23
23
  * Named explicitly so it's clear these sites are scoped to the global tier
@@ -25,4 +25,4 @@ export declare function globalAgentBackendFromManifest(manifest: LoadedManifest
25
25
  * through `stageWorker.ts`, which resolves the full stage > pipeline >
26
26
  * global chain once it has loaded the specific pipeline and stage.
27
27
  */
28
- export declare function resolveGlobalOnlyAgentPort(projectRoot: string): Promise<AgentPort>;
28
+ export declare function resolveGlobalOnlyStagePort(projectRoot: string): Promise<StagePort>;
@@ -3,31 +3,31 @@ import { PiAgentAdapter } from "./piAdapter.js";
3
3
  import { ClaudeAgentAdapter } from "./claudeAdapter.js";
4
4
  import { loadStageflowManifestOutcome } from "../config/loadStageflowManifest.js";
5
5
  /** stage > pipeline > global > "pi", with stage gated off for now. */
6
- export function resolveAgentBackend(selection = {}) {
6
+ export function resolveStageBackend(selection = {}) {
7
7
  if (STAGE_LEVEL_AGENT_OVERRIDE_ENABLED && selection.stage !== undefined) {
8
8
  return selection.stage;
9
9
  }
10
10
  return selection.pipeline ?? selection.global ?? "pi";
11
11
  }
12
- /** Existing Pi AgentPort. Hosted runs pass this into createHostedRuntime. */
13
- export function createPiAgentPort() {
12
+ /** Existing Pi StagePort. Hosted runs pass this into createHostedRuntime. */
13
+ export function createPiStagePort() {
14
14
  return new PiAgentAdapter();
15
15
  }
16
- export function resolveAgentPort(selection = {}) {
17
- const backend = resolveAgentBackend(selection);
16
+ export function resolveStagePort(selection = {}) {
17
+ const backend = resolveStageBackend(selection);
18
18
  switch (backend) {
19
19
  case "pi":
20
- return createPiAgentPort();
20
+ return createPiStagePort();
21
21
  case "claude":
22
22
  return new ClaudeAgentAdapter();
23
23
  }
24
24
  }
25
25
  /** The global tier's backend id from an already-loaded manifest, or undefined for missing/invalid/unset. */
26
- export function globalAgentBackendFromManifest(manifest) {
26
+ export function globalStageBackendFromManifest(manifest) {
27
27
  return manifest ? asAgentBackendId(manifest.manifest.agent) : undefined;
28
28
  }
29
29
  /**
30
- * Resolves an AgentPort using only the global (stageflow.yaml) tier — for
30
+ * Resolves an StagePort using only the global (stageflow.yaml) tier — for
31
31
  * the three call sites that construct an agent before any pipeline is
32
32
  * known (`cli.ts`'s ui/mcp servers, `runCommand.ts`, `runsCommand.ts`).
33
33
  * Named explicitly so it's clear these sites are scoped to the global tier
@@ -35,9 +35,9 @@ export function globalAgentBackendFromManifest(manifest) {
35
35
  * through `stageWorker.ts`, which resolves the full stage > pipeline >
36
36
  * global chain once it has loaded the specific pipeline and stage.
37
37
  */
38
- export async function resolveGlobalOnlyAgentPort(projectRoot) {
38
+ export async function resolveGlobalOnlyStagePort(projectRoot) {
39
39
  const outcome = await loadStageflowManifestOutcome(projectRoot);
40
- return resolveAgentPort({
41
- global: outcome.ok ? globalAgentBackendFromManifest(outcome.value) : undefined,
40
+ return resolveStagePort({
41
+ global: outcome.ok ? globalStageBackendFromManifest(outcome.value) : undefined,
42
42
  });
43
43
  }
@@ -0,0 +1,33 @@
1
+ import type { EmitCapture } from "../tools/emitStageEnvelope.js";
2
+ /** Follow-up turns sent when the agent ends its turn without an accepted envelope. */
3
+ export declare const DEFAULT_EMIT_REMINDERS = 2;
4
+ /** The slice of a Pi session the driver needs; a fake in tests. */
5
+ export type StageTurnSession = {
6
+ prompt(text: string): Promise<void>;
7
+ readonly messages: readonly unknown[];
8
+ };
9
+ export type RemindOptions = {
10
+ emitToolName: string;
11
+ maxReminders?: number;
12
+ /** True once the stage is closing, parked, or timed out: never start another turn then. */
13
+ shouldStop: () => boolean;
14
+ };
15
+ export type RemindOutcome = {
16
+ reminders: number;
17
+ /** Set when the last turn ended on a provider error; no reminder is sent after one. */
18
+ providerError?: string;
19
+ };
20
+ /** Trimmed, length-capped provider error text for a stage failure reason. */
21
+ export declare function formatProviderError(text: string | undefined): string;
22
+ /**
23
+ * Error text of the last assistant turn when it ended on a provider error (bad key, auth,
24
+ * model unavailable, or a transient error Pi already gave up retrying). Undefined otherwise.
25
+ */
26
+ export declare function lastTurnProviderError(messages: readonly unknown[]): string | undefined;
27
+ export declare function emitReminderPrompt(emitToolName: string, attempt: number, max: number, rejectedEmit?: string): string;
28
+ /**
29
+ * Called after a turn returns. While no envelope was accepted, nudge the agent with a short
30
+ * follow-up turn, up to `maxReminders` times. Stops at once when an envelope is captured,
31
+ * the stage is stopping, the turn was aborted, or the turn ended on a provider error.
32
+ */
33
+ export declare function remindUntilEmitted(session: StageTurnSession, capture: EmitCapture, options: RemindOptions): Promise<RemindOutcome>;
@@ -0,0 +1,64 @@
1
+ /** Follow-up turns sent when the agent ends its turn without an accepted envelope. */
2
+ export const DEFAULT_EMIT_REMINDERS = 2;
3
+ const PROVIDER_ERROR_MAX_CHARS = 500;
4
+ function lastAssistant(messages) {
5
+ for (let i = messages.length - 1; i >= 0; i--) {
6
+ const m = messages[i];
7
+ if (m && typeof m === "object" && m.role === "assistant")
8
+ return m;
9
+ }
10
+ return undefined;
11
+ }
12
+ /** Trimmed, length-capped provider error text for a stage failure reason. */
13
+ export function formatProviderError(text) {
14
+ const t = text?.trim() ? text.trim() : "the model provider returned an error";
15
+ return t.length > PROVIDER_ERROR_MAX_CHARS ? `${t.slice(0, PROVIDER_ERROR_MAX_CHARS)}…` : t;
16
+ }
17
+ /**
18
+ * Error text of the last assistant turn when it ended on a provider error (bad key, auth,
19
+ * model unavailable, or a transient error Pi already gave up retrying). Undefined otherwise.
20
+ */
21
+ export function lastTurnProviderError(messages) {
22
+ const m = lastAssistant(messages);
23
+ if (!m || m.stopReason !== "error")
24
+ return undefined;
25
+ return formatProviderError(typeof m.errorMessage === "string" ? m.errorMessage : undefined);
26
+ }
27
+ function lastTurnAborted(messages) {
28
+ return lastAssistant(messages)?.stopReason === "aborted";
29
+ }
30
+ export function emitReminderPrompt(emitToolName, attempt, max, rejectedEmit) {
31
+ const lines = [
32
+ `Reminder ${attempt} of ${max}: your turn ended without an accepted ${emitToolName} call, so this stage has not finished.`,
33
+ ];
34
+ if (rejectedEmit !== undefined) {
35
+ lines.push(`Your last ${emitToolName} call was rejected: ${rejectedEmit}`);
36
+ lines.push(`Fix that and call ${emitToolName} again.`);
37
+ }
38
+ else {
39
+ lines.push(`Continue the stage's work if any is left, then call ${emitToolName}.`);
40
+ }
41
+ lines.push(`If the stage cannot be completed, call ${emitToolName} with status "failure" and a summary that says why. Do not end your turn without calling it.`);
42
+ return lines.join("\n");
43
+ }
44
+ /**
45
+ * Called after a turn returns. While no envelope was accepted, nudge the agent with a short
46
+ * follow-up turn, up to `maxReminders` times. Stops at once when an envelope is captured,
47
+ * the stage is stopping, the turn was aborted, or the turn ended on a provider error.
48
+ */
49
+ export async function remindUntilEmitted(session, capture, options) {
50
+ const max = options.maxReminders ?? DEFAULT_EMIT_REMINDERS;
51
+ let reminders = 0;
52
+ for (;;) {
53
+ if (capture.envelope)
54
+ return { reminders };
55
+ const providerError = lastTurnProviderError(session.messages);
56
+ if (providerError !== undefined)
57
+ return { reminders, providerError };
58
+ if (options.shouldStop() || lastTurnAborted(session.messages) || reminders >= max) {
59
+ return { reminders };
60
+ }
61
+ reminders++;
62
+ await session.prompt(emitReminderPrompt(options.emitToolName, reminders, max, capture.error));
63
+ }
64
+ }