stageflow 0.32.0 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (335) hide show
  1. package/README.md +4 -3
  2. package/dist/agent/adapterSpill.d.ts +14 -0
  3. package/dist/agent/adapterSpill.js +83 -0
  4. package/dist/agent/agentBackend.d.ts +2 -2
  5. package/dist/agent/agentBackend.js +2 -2
  6. package/dist/agent/claudeAdapter.d.ts +13 -2
  7. package/dist/agent/claudeAdapter.js +50 -6
  8. package/dist/agent/cursorProvider.d.ts +3 -0
  9. package/dist/agent/cursorProvider.js +23 -6
  10. package/dist/agent/fakeAgent.d.ts +3 -3
  11. package/dist/agent/fakeAgent.js +0 -6
  12. package/dist/agent/piAdapter.d.ts +2 -2
  13. package/dist/agent/piAdapter.js +116 -15
  14. package/dist/agent/piIsolatedMcp.d.ts +1 -0
  15. package/dist/agent/piIsolatedMcp.js +19 -1
  16. package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
  17. package/dist/agent/piIsolatedMcpProbe.js +20 -0
  18. package/dist/agent/port.d.ts +2 -2
  19. package/dist/agent/providerAuth.d.ts +6 -0
  20. package/dist/agent/providerAuth.js +86 -17
  21. package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -6
  22. package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +13 -9
  23. package/dist/agent/stageTurnDriver.d.ts +33 -0
  24. package/dist/agent/stageTurnDriver.js +64 -0
  25. package/dist/agent/toolOutputBudget.d.ts +46 -0
  26. package/dist/agent/toolOutputBudget.js +97 -0
  27. package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
  28. package/dist/agent/toolOutputBudgetExtension.js +141 -0
  29. package/dist/agent/toolOutputSpill.d.ts +34 -0
  30. package/dist/agent/toolOutputSpill.js +85 -0
  31. package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
  32. package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
  33. package/dist/browser/anchor.d.ts +2 -0
  34. package/dist/browser/anchor.js +2 -1
  35. package/dist/browser/auditSink.d.ts +8 -0
  36. package/dist/browser/browserHost.d.ts +29 -0
  37. package/dist/browser/browserSweep.d.ts +4 -1
  38. package/dist/browser/browserSweep.js +7 -0
  39. package/dist/browser/browserTeardown.d.ts +1 -0
  40. package/dist/browser/browserTeardown.js +32 -2
  41. package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
  42. package/dist/browser/containerBrowserEndpoint.js +57 -0
  43. package/dist/browser/containerBrowserHost.d.ts +24 -0
  44. package/dist/browser/containerBrowserHost.js +147 -0
  45. package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
  46. package/dist/browser/dockerSandboxOrchestrator.js +142 -0
  47. package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
  48. package/dist/browser/fakeLiveViewRelay.js +94 -0
  49. package/dist/browser/fakeLiveViewSource.d.ts +6 -0
  50. package/dist/browser/fakeLiveViewSource.js +16 -0
  51. package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
  52. package/dist/browser/fakeRemoteBrowserHost.js +2 -1
  53. package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
  54. package/dist/browser/fakeSandboxOrchestrator.js +44 -0
  55. package/dist/browser/gateHandoff.d.ts +0 -2
  56. package/dist/browser/gateHandoff.js +5 -11
  57. package/dist/browser/hostCapabilities.d.ts +23 -0
  58. package/dist/browser/hostCapabilities.js +23 -0
  59. package/dist/browser/humanLogin.d.ts +2 -2
  60. package/dist/browser/humanLogin.js +11 -2
  61. package/dist/browser/liveViewPersisted.d.ts +11 -0
  62. package/dist/browser/liveViewPersisted.js +22 -0
  63. package/dist/browser/liveViewRelay.d.ts +76 -0
  64. package/dist/browser/liveViewRelay.js +1 -0
  65. package/dist/browser/liveViewSessions.d.ts +25 -0
  66. package/dist/browser/liveViewSessions.js +139 -0
  67. package/dist/browser/liveViewSource.d.ts +25 -0
  68. package/dist/browser/liveViewSource.js +7 -0
  69. package/dist/browser/liveViewTickets.d.ts +46 -0
  70. package/dist/browser/liveViewTickets.js +122 -0
  71. package/dist/browser/localBrowserHost.d.ts +10 -0
  72. package/dist/browser/localBrowserHost.js +53 -7
  73. package/dist/browser/ownerScope.d.ts +10 -0
  74. package/dist/browser/ownerScope.js +6 -0
  75. package/dist/browser/profileStore.d.ts +5 -2
  76. package/dist/browser/profileStore.js +3 -5
  77. package/dist/browser/sandboxOrchestrator.d.ts +60 -0
  78. package/dist/browser/sandboxOrchestrator.js +8 -0
  79. package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
  80. package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
  81. package/dist/browser/stageBrowserEnv.d.ts +7 -0
  82. package/dist/browser/stageBrowserEnv.js +53 -15
  83. package/dist/browser/stageHandoff.d.ts +47 -0
  84. package/dist/browser/stageHandoff.js +101 -0
  85. package/dist/browser/stageProfileLock.d.ts +1 -0
  86. package/dist/browser/stageProfileLock.js +1 -3
  87. package/dist/browser/volumeProfileStore.d.ts +7 -0
  88. package/dist/browser/volumeProfileStore.js +35 -0
  89. package/dist/cli/browserCommand.d.ts +3 -1
  90. package/dist/cli/browserCommand.js +25 -20
  91. package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
  92. package/dist/cli/resolveCliProjectRoot.js +27 -0
  93. package/dist/cli/runCommand.d.ts +1 -1
  94. package/dist/cli/runCommand.js +59 -17
  95. package/dist/cli/skillsCommand.d.ts +20 -0
  96. package/dist/cli/skillsCommand.js +74 -31
  97. package/dist/cli/validateCommand.d.ts +1 -1
  98. package/dist/cli/validateCommand.js +22 -4
  99. package/dist/cli.js +4 -4
  100. package/dist/config/browseCatalog.d.ts +2 -0
  101. package/dist/config/browseCatalog.js +6 -0
  102. package/dist/config/catalogRelativePath.d.ts +4 -0
  103. package/dist/config/catalogRelativePath.js +37 -9
  104. package/dist/config/createStage.d.ts +1 -0
  105. package/dist/config/createStage.js +11 -2
  106. package/dist/config/createTask.d.ts +30 -0
  107. package/dist/config/createTask.js +161 -0
  108. package/dist/config/createTrigger.d.ts +15 -0
  109. package/dist/config/createTrigger.js +32 -25
  110. package/dist/config/draftPackage.d.ts +41 -0
  111. package/dist/config/draftPackage.js +216 -23
  112. package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
  113. package/dist/config/ensureCatalogScanRoot.js +6 -2
  114. package/dist/config/findTaskInCatalog.d.ts +33 -0
  115. package/dist/config/findTaskInCatalog.js +69 -0
  116. package/dist/config/hostConfig.d.ts +21 -0
  117. package/dist/config/hostConfig.js +113 -1
  118. package/dist/config/importTask.d.ts +19 -0
  119. package/dist/config/importTask.js +133 -0
  120. package/dist/config/installSkillRequest.d.ts +40 -0
  121. package/dist/config/installSkillRequest.js +286 -0
  122. package/dist/config/listConfig.d.ts +0 -16
  123. package/dist/config/listConfig.js +0 -3
  124. package/dist/config/loadOutcome.d.ts +11 -6
  125. package/dist/config/loadPipeline.js +0 -1
  126. package/dist/config/loadStage.js +2 -0
  127. package/dist/config/loadStageflowManifest.js +4 -1
  128. package/dist/config/loadTask.js +2 -0
  129. package/dist/config/loadTrigger.js +2 -0
  130. package/dist/config/mergePipelineIncludes.js +3 -0
  131. package/dist/config/normalizePipelineStageEntry.js +10 -1
  132. package/dist/config/printTargetYaml.js +0 -1
  133. package/dist/config/publishDraftPackage.d.ts +5 -1
  134. package/dist/config/publishDraftPackage.js +4 -1
  135. package/dist/config/resolveCatalogRoots.d.ts +4 -1
  136. package/dist/config/resolveCatalogRoots.js +18 -1
  137. package/dist/config/skillUsageIndex.d.ts +6 -0
  138. package/dist/config/skillUsageIndex.js +39 -0
  139. package/dist/config/stageBrowser.js +13 -0
  140. package/dist/config/triggerAdapterStatus.d.ts +11 -0
  141. package/dist/config/triggerAdapterStatus.js +90 -0
  142. package/dist/config/triggerCatalog.d.ts +41 -2
  143. package/dist/config/triggerCatalog.js +107 -15
  144. package/dist/config/updateTask.d.ts +30 -0
  145. package/dist/config/updateTask.js +119 -0
  146. package/dist/config/updateTrigger.d.ts +38 -0
  147. package/dist/config/updateTrigger.js +220 -0
  148. package/dist/config/validateCatalog.d.ts +6 -2
  149. package/dist/config/validateCatalog.js +48 -7
  150. package/dist/config/workshopCatalogFilenames.d.ts +37 -0
  151. package/dist/config/workshopCatalogFilenames.js +125 -0
  152. package/dist/config/workshopCatalogRepair.d.ts +7 -0
  153. package/dist/config/workshopCatalogRepair.js +144 -0
  154. package/dist/config/yamlParsePosition.d.ts +4 -0
  155. package/dist/config/yamlParsePosition.js +9 -0
  156. package/dist/hosted/containment.d.ts +3 -0
  157. package/dist/hosted/containment.js +36 -0
  158. package/dist/hosted/errors.d.ts +21 -0
  159. package/dist/hosted/errors.js +30 -0
  160. package/dist/hosted/packageSkills.d.ts +3 -0
  161. package/dist/hosted/packageSkills.js +80 -0
  162. package/dist/hosted/runtime.d.ts +12 -0
  163. package/dist/hosted/runtime.js +596 -0
  164. package/dist/hosted/sequencer.d.ts +28 -0
  165. package/dist/hosted/sequencer.js +129 -0
  166. package/dist/hosted/translate.d.ts +3 -0
  167. package/dist/hosted/translate.js +43 -0
  168. package/dist/hosted/types.d.ts +108 -0
  169. package/dist/hosted/types.js +10 -0
  170. package/dist/index.d.ts +9 -1
  171. package/dist/index.js +6 -0
  172. package/dist/mcp/getStartedTool.js +15 -6
  173. package/dist/mcp/triggerTools.js +2 -8
  174. package/dist/operatorAgent/agentPort.d.ts +11 -0
  175. package/dist/operatorAgent/agentPort.js +12 -0
  176. package/dist/operatorAgent/index.d.ts +1 -0
  177. package/dist/operatorAgent/index.js +1 -0
  178. package/dist/operatorAgent/piModel.js +26 -6
  179. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
  180. package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
  181. package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
  182. package/dist/package-meta.d.ts +1 -1
  183. package/dist/package-meta.js +1 -1
  184. package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
  185. package/dist/project/ensureProjectRootsFromEnv.js +22 -0
  186. package/dist/project/globalHome.d.ts +1 -0
  187. package/dist/project/globalHome.js +18 -0
  188. package/dist/project/workspaceCatalog.d.ts +10 -0
  189. package/dist/project/workspaceCatalog.js +91 -0
  190. package/dist/projection/projectRun.d.ts +0 -1
  191. package/dist/projection/projectRun.js +0 -1
  192. package/dist/runstore/backup.js +14 -4
  193. package/dist/runstore/port.d.ts +11 -1
  194. package/dist/runstore/restore.js +5 -2
  195. package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
  196. package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
  197. package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
  198. package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
  199. package/dist/runstore/sqlite/migrations/index.js +2 -0
  200. package/dist/runstore/stageSnapshot.d.ts +3 -1
  201. package/dist/runstore/stageSnapshot.js +19 -0
  202. package/dist/runstore/workspaceLayout.js +27 -0
  203. package/dist/runstore/writeContainedArtifact.d.ts +1 -0
  204. package/dist/runstore/writeContainedArtifact.js +52 -0
  205. package/dist/runtime/containerLimits.d.ts +0 -3
  206. package/dist/runtime/containerLimits.js +0 -5
  207. package/dist/runtime/credentialBinding.d.ts +3 -0
  208. package/dist/runtime/credentialBinding.js +23 -6
  209. package/dist/runtime/emailSource.js +20 -28
  210. package/dist/runtime/githubPollSource.js +20 -28
  211. package/dist/runtime/pipelineRunner.d.ts +4 -4
  212. package/dist/runtime/pipelineScheduler.d.ts +2 -2
  213. package/dist/runtime/pipelineScheduler.js +3 -0
  214. package/dist/runtime/resumeReconstruct.d.ts +2 -2
  215. package/dist/runtime/resumeTimedOut.d.ts +2 -2
  216. package/dist/runtime/runManager.d.ts +18 -2
  217. package/dist/runtime/runManager.js +95 -30
  218. package/dist/runtime/runRetentionSweep.d.ts +0 -2
  219. package/dist/runtime/runRetentionSweep.js +0 -8
  220. package/dist/runtime/runRetryCoordinator.d.ts +2 -2
  221. package/dist/runtime/scheduleSource.js +15 -24
  222. package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
  223. package/dist/runtime/stageAttemptBootstrap.js +10 -1
  224. package/dist/runtime/stageProcessLauncher.js +2 -2
  225. package/dist/runtime/stageRoots.js +12 -1
  226. package/dist/runtime/stageRunner.d.ts +2 -2
  227. package/dist/runtime/stageRunner.js +8 -2
  228. package/dist/runtime/stageWorker.js +2 -2
  229. package/dist/runtime/triggerPort.d.ts +5 -3
  230. package/dist/runtime/triggerRunner.d.ts +1 -0
  231. package/dist/runtime/triggerRunner.js +27 -20
  232. package/dist/runtime/webhookAccept.d.ts +25 -0
  233. package/dist/runtime/webhookAccept.js +56 -0
  234. package/dist/server/bootstrap.d.ts +7 -2
  235. package/dist/server/bootstrap.js +15 -1
  236. package/dist/server/controlToken.d.ts +1 -1
  237. package/dist/server/controlToken.js +18 -2
  238. package/dist/server/healthSurfaces.d.ts +12 -0
  239. package/dist/server/healthSurfaces.js +27 -6
  240. package/dist/server/http.d.ts +19 -10
  241. package/dist/server/http.js +605 -85
  242. package/dist/server/liveViewPath.d.ts +9 -0
  243. package/dist/server/liveViewPath.js +8 -0
  244. package/dist/server/liveViewRoutes.d.ts +58 -0
  245. package/dist/server/liveViewRoutes.js +565 -0
  246. package/dist/server/mcpHost.d.ts +4 -2
  247. package/dist/server/mcpHost.js +2 -0
  248. package/dist/server/readCatalogFile.d.ts +29 -0
  249. package/dist/server/readCatalogFile.js +215 -0
  250. package/dist/server/triggerRoutes.d.ts +1 -0
  251. package/dist/server/triggerRoutes.js +200 -73
  252. package/dist/tools/readEmail.d.ts +3 -3
  253. package/dist/tools/writeStageArtifact.d.ts +9 -0
  254. package/dist/tools/writeStageArtifact.js +2 -47
  255. package/dist/types/pipeline.d.ts +1 -1
  256. package/dist/types/stage.d.ts +1 -1
  257. package/dist/types/stageflowManifest.d.ts +1 -1
  258. package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  259. package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  260. package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  261. package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  262. package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  263. package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  264. package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  265. package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  266. package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  267. package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  268. package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  269. package/dist/ui/assets/index-BoFPkKFg.js +256 -0
  270. package/dist/ui/assets/{index-Bv4p1aPK.css → index-CIPbbBUi.css} +1 -1
  271. package/dist/ui/index.html +4 -4
  272. package/dist/workshop/autosave.d.ts +2 -2
  273. package/dist/workshop/autosave.js +4 -6
  274. package/dist/workshop/buildStore.d.ts +2 -0
  275. package/dist/workshop/chatAttachments.d.ts +27 -0
  276. package/dist/workshop/chatAttachments.js +98 -0
  277. package/dist/workshop/chatTurn.d.ts +5 -2
  278. package/dist/workshop/chatTurn.js +22 -18
  279. package/dist/workshop/sessionMutations.d.ts +17 -0
  280. package/dist/workshop/sessionMutations.js +107 -0
  281. package/dist/workshop/sessionStore.d.ts +10 -0
  282. package/dist/workshop/sessionStore.js +52 -0
  283. package/dist/workshop/workshopPin.d.ts +4 -3
  284. package/dist/workshop/workshopPin.js +26 -6
  285. package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
  286. package/dist/workshop/workshopTranscriptTurn.js +46 -0
  287. package/examples/README.md +1 -1
  288. package/examples/archify-on-pr/README.md +1 -1
  289. package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
  290. package/examples/archify-on-pr/author-diagrams.yaml +1 -0
  291. package/examples/conditional-fork/branch-a.yaml +1 -1
  292. package/examples/conditional-fork/decide.yaml +2 -1
  293. package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
  294. package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
  295. package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
  296. package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
  297. package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
  298. package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
  299. package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
  300. package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
  301. package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
  302. package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
  303. package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
  304. package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
  305. package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
  306. package/examples/hello-world/hello.pipeline.yaml +2 -2
  307. package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
  308. package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
  309. package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
  310. package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
  311. package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
  312. package/examples/stageflow.yaml +6 -0
  313. package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
  314. package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
  315. package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
  316. package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
  317. package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
  318. package/examples/triggers-readiness/README.md +48 -0
  319. package/examples/triggers-readiness/readiness-emit.yaml +18 -0
  320. package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
  321. package/examples/triggers-readiness/readiness.task.yaml +6 -0
  322. package/examples/triggers-readiness/stageflow.yaml +13 -0
  323. package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
  324. package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
  325. package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
  326. package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
  327. package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
  328. package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
  329. package/package.json +9 -1
  330. package/skills/stageflow/references/control-surface.md +1 -1
  331. package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
  332. package/skills/stageflow-run/SKILL.md +1 -1
  333. package/skills/stageflow-run/references/mcp-call.md +1 -1
  334. package/skills/stageflow-setup/SKILL.md +1 -1
  335. package/dist/ui/assets/index-59cjF0OA.js +0 -133
@@ -1,3 +1,4 @@
1
+ import { addModelUsage, emptyStageUsage } from "../types/usage.js";
1
2
  import { derivePendingPrompt } from "../hitl/qaTrail.js";
2
3
  import { stageStatusFromEvents } from "./port.js";
3
4
  import { listArtifactNames } from "./workspaceLayout.js";
@@ -19,6 +20,7 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
19
20
  let envelope;
20
21
  let attempt_count;
21
22
  let cost_usd;
23
+ let usage;
22
24
  if (latest !== null) {
23
25
  events = await store.listStageEvents(runId, stageId, latest.attempt);
24
26
  status = stageStatusFromEvents(events);
@@ -26,6 +28,7 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
26
28
  attempt_count = await store.countStageAttempts(runId, stageId);
27
29
  const executions = await store.listStageExecutions(runId, stageId);
28
30
  cost_usd = sumStageExecutionCost(executions);
31
+ usage = mergeStageExecutionUsage(executions);
29
32
  }
30
33
  else {
31
34
  events = await store.listStageEvents(runId, stageId);
@@ -46,8 +49,24 @@ export async function buildStageSnapshotFromStore(store, runId, stageId, workspa
46
49
  attempt_count,
47
50
  ...(pending ? { pending_prompt: pending } : {}),
48
51
  ...(cost_usd !== undefined ? { cost_usd } : {}),
52
+ ...(usage !== undefined ? { usage } : {}),
49
53
  };
50
54
  }
55
+ export function mergeStageExecutionUsage(executions) {
56
+ const usages = executions
57
+ .map((e) => e.usage)
58
+ .filter((u) => u !== undefined);
59
+ if (usages.length === 0)
60
+ return undefined;
61
+ const merged = emptyStageUsage();
62
+ for (const u of usages) {
63
+ for (const [model, breakdown] of Object.entries(u.models)) {
64
+ addModelUsage(merged, model, breakdown);
65
+ }
66
+ }
67
+ merged.costUsd = usages.reduce((sum, u) => sum + u.costUsd, 0);
68
+ return merged;
69
+ }
51
70
  /** Sum cost across every attempt — retries are real spend too, so a stage's total isn't just its latest attempt. */
52
71
  function sumStageExecutionCost(executions) {
53
72
  const costs = executions.map((e) => e.cost_usd).filter((c) => c !== undefined);
@@ -1,5 +1,8 @@
1
1
  import { access, lstat, mkdir, readdir, realpath } from "node:fs/promises";
2
2
  import path from "node:path";
3
+ import { globalCredentialRoot } from "../project/globalHome.js";
4
+ import { STAGEFLOW_AGENT_AUTH_PATH_ENV } from "../runtime/credentialBinding.js";
5
+ import { SF_STAGE_WORKER } from "../runtime/stageWorkerProtocol.js";
3
6
  export { stageDir } from "./paths.js";
4
7
  export function guardStageId(stageId) {
5
8
  if (stageId.includes("..") ||
@@ -101,6 +104,15 @@ export async function resolveEffectiveRealPath(candidatePath) {
101
104
  }
102
105
  }
103
106
  }
107
+ function credentialRootForFileToolDenial() {
108
+ if (process.env[SF_STAGE_WORKER] === "1") {
109
+ const stamped = process.env[STAGEFLOW_AGENT_AUTH_PATH_ENV]?.trim();
110
+ if (!stamped)
111
+ return undefined;
112
+ return path.resolve(path.dirname(stamped), "..");
113
+ }
114
+ return globalCredentialRoot();
115
+ }
104
116
  export async function durableRootFileToolDenial(candidatePath, runWorkspaceDir, durableRoot, allowlistedRoots) {
105
117
  const fileReal = await resolveEffectiveRealPath(candidatePath);
106
118
  if (fileReal === undefined) {
@@ -137,6 +149,21 @@ export async function durableRootFileToolDenial(candidatePath, runWorkspaceDir,
137
149
  if (isInsideDir(fileReal, durableReal)) {
138
150
  return STAGEFLOW_PATH_DENIED;
139
151
  }
152
+ const credentialRoot = credentialRootForFileToolDenial();
153
+ if (credentialRoot !== undefined) {
154
+ let credentialReal;
155
+ try {
156
+ credentialReal = await realpath(credentialRoot);
157
+ }
158
+ catch {
159
+ credentialReal = path.resolve(credentialRoot);
160
+ }
161
+ if (credentialReal !== durableReal &&
162
+ !isInsideDir(credentialReal, durableReal) &&
163
+ isInsideDir(fileReal, credentialReal)) {
164
+ return STAGEFLOW_PATH_DENIED;
165
+ }
166
+ }
140
167
  return undefined;
141
168
  }
142
169
  export function resolveArtifactTarget(runWorkspaceDir, stageId, attempt, relativePath) {
@@ -0,0 +1 @@
1
+ export declare function writeContainedArtifact(runWorkspaceDir: string, artifactsDirectory: string, absolutePath: string, content: string): Promise<void>;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Atomic write of one file under a stage attempt's artifacts directory.
3
+ * Refuses symlinked or hard-linked targets and any path that resolves outside
4
+ * the artifacts directory.
5
+ */
6
+ import { randomBytes } from "node:crypto";
7
+ import { lstat, realpath, rename, unlink, writeFile } from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { ensureRealArtifactsDir, isInsideDir, resolveContainedParent, } from "./workspaceLayout.js";
10
+ async function assertSafeWriteTarget(targetPath) {
11
+ try {
12
+ const st = await lstat(targetPath);
13
+ if (st.isSymbolicLink()) {
14
+ throw new Error("path escapes the stage artifacts directory");
15
+ }
16
+ if (st.nlink > 1) {
17
+ throw new Error("refusing to overwrite hard-linked file");
18
+ }
19
+ }
20
+ catch (err) {
21
+ if (err.code !== "ENOENT") {
22
+ throw err;
23
+ }
24
+ }
25
+ }
26
+ export async function writeContainedArtifact(runWorkspaceDir, artifactsDirectory, absolutePath, content) {
27
+ const { realArtifacts } = await ensureRealArtifactsDir(runWorkspaceDir, artifactsDirectory);
28
+ const relativeToArtifacts = path.relative(artifactsDirectory, absolutePath);
29
+ const parent = await resolveContainedParent(realArtifacts, relativeToArtifacts);
30
+ const baseName = path.basename(absolutePath);
31
+ const targetPath = path.join(parent, baseName);
32
+ await assertSafeWriteTarget(targetPath);
33
+ const tmpName = `.sf-artifact-${randomBytes(8).toString("hex")}.tmp`;
34
+ const tmpPath = path.join(parent, tmpName);
35
+ try {
36
+ await writeFile(tmpPath, content, "utf8");
37
+ await rename(tmpPath, targetPath);
38
+ }
39
+ catch (err) {
40
+ try {
41
+ await unlink(tmpPath);
42
+ }
43
+ catch {
44
+ // best-effort cleanup
45
+ }
46
+ throw err;
47
+ }
48
+ const realWritten = await realpath(targetPath);
49
+ if (!isInsideDir(realWritten, realArtifacts)) {
50
+ throw new Error("path escapes the stage artifacts directory");
51
+ }
52
+ }
@@ -1,4 +1,3 @@
1
- import os from "node:os";
2
1
  export type ContainerLimits = {
3
2
  memoryLimitBytes: number | undefined;
4
3
  maxActiveStageProcesses: number;
@@ -12,5 +11,3 @@ export declare function readContainerMemoryLimit(roots?: readonly string[]): {
12
11
  export declare function deriveContainerLimits(memoryLimitBytes: number | undefined, source?: ContainerLimits["source"]): ContainerLimits;
13
12
  export declare function getContainerLimits(): ContainerLimits;
14
13
  export declare function resetContainerLimitsForTests(): void;
15
- /** Test hook: prove sizing never consults os.totalmem. */
16
- export declare function assertNoOsTotalmemInSizing(): typeof os.totalmem;
@@ -1,5 +1,4 @@
1
1
  import { readFileSync, existsSync } from "node:fs";
2
- import os from "node:os";
3
2
  const HOST_RESERVE_MIB = 512;
4
3
  const WORKER_BUDGET_MIB = 512;
5
4
  const MAX_CAP = 8;
@@ -78,7 +77,3 @@ export function getContainerLimits() {
78
77
  export function resetContainerLimitsForTests() {
79
78
  cached = undefined;
80
79
  }
81
- /** Test hook: prove sizing never consults os.totalmem. */
82
- export function assertNoOsTotalmemInSizing() {
83
- return os.totalmem;
84
- }
@@ -19,5 +19,8 @@ export declare function stageflowAgentAuthPath(override?: string): string;
19
19
  export declare function piHomeAuthPath(override?: string): string;
20
20
  export declare function isUsableAuthFile(authPath: string): boolean;
21
21
  export declare function ensureSfOwnedAuthStore(): string;
22
+ /** Use this auth file for credential binding and do not create the operator home. */
23
+ export declare function runWithExplicitAuthPath<T>(authPath: string, fn: () => Promise<T>): Promise<T>;
24
+ export declare function currentExplicitAuthPath(): string | undefined;
22
25
  export declare function resolveCredentialBinding(ctx: ProjectContext | string, _options?: ResolveCredentialBindingOptions): CredentialBinding;
23
26
  export { readCredentialSourceFromFile, writeCredentialSourceToFile, } from "./settingsFile.js";
@@ -1,11 +1,12 @@
1
- import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
2
3
  import path from "node:path";
3
- import { ensureGlobalHome } from "../project/globalHome.js";
4
+ import { ensureGlobalHome, globalCredentialRoot } from "../project/globalHome.js";
4
5
  import { resolveProjectContext, } from "../project/resolveProjectContext.js";
5
6
  import { parseCredentialSource, readCredentialSourceFromContext, writeCredentialSourceToContext, } from "./settingsFile.js";
6
7
  export { parseCredentialSource, readCredentialSourceFromContext, writeCredentialSourceToContext, };
7
8
  export function sfOwnedAgentDir() {
8
- return path.join(ensureGlobalHome(), "agent");
9
+ return path.join(globalCredentialRoot(), "agent");
9
10
  }
10
11
  export function sfOwnedAuthPath() {
11
12
  return path.join(sfOwnedAgentDir(), "auth.json");
@@ -41,7 +42,15 @@ export function isUsableAuthFile(authPath) {
41
42
  }
42
43
  export function ensureSfOwnedAuthStore() {
43
44
  ensureGlobalHome();
44
- const authPath = sfOwnedAuthPath();
45
+ const agentDir = sfOwnedAgentDir();
46
+ mkdirSync(agentDir, { recursive: true });
47
+ try {
48
+ chmodSync(agentDir, 0o700);
49
+ }
50
+ catch {
51
+ // best-effort on non-POSIX
52
+ }
53
+ const authPath = path.join(agentDir, "auth.json");
45
54
  if (!existsSync(authPath)) {
46
55
  writeFileSync(authPath, "{}\n", { encoding: "utf8", mode: 0o600 });
47
56
  }
@@ -53,13 +62,21 @@ export function ensureSfOwnedAuthStore() {
53
62
  }
54
63
  return authPath;
55
64
  }
65
+ const explicitAuthPath = new AsyncLocalStorage();
66
+ /** Use this auth file for credential binding and do not create the operator home. */
67
+ export function runWithExplicitAuthPath(authPath, fn) {
68
+ return explicitAuthPath.run(path.resolve(authPath), fn);
69
+ }
70
+ export function currentExplicitAuthPath() {
71
+ return explicitAuthPath.getStore();
72
+ }
56
73
  export function resolveCredentialBinding(ctx, _options = {}) {
74
+ const explicit = currentExplicitAuthPath();
57
75
  const projectCtx = typeof ctx === "string" ? resolveProjectContext(ctx) : ctx;
58
76
  const persisted = readCredentialSourceFromContext(projectCtx);
59
- const authPath = ensureSfOwnedAuthStore();
60
77
  return {
61
78
  source: "sf_owned",
62
- authPath,
79
+ authPath: explicit ?? sfOwnedAuthPath(),
63
80
  provisional: persisted === undefined,
64
81
  };
65
82
  }
@@ -1,10 +1,6 @@
1
- import path from "node:path";
2
1
  import { ImapFlow } from "imapflow";
3
- import { getCatalogScanPaths } from "../config/browseCatalog.js";
4
- import { loadTriggerOutcome } from "../config/loadTrigger.js";
5
- import { catalogContextFromStageflow } from "../config/resolveCatalogContext.js";
2
+ import { listCatalogTriggerMatches, triggerStoreId, } from "../config/triggerCatalog.js";
6
3
  import { readSecretFromEnvOrFile } from "../config/secretFromEnvOrFile.js";
7
- import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
8
4
  import { matchesEventFilter } from "./triggerEventMatch.js";
9
5
  export const DEFAULT_EMAIL_RECONNECT_BASE_DELAY_MS = 1_000;
10
6
  export const DEFAULT_EMAIL_RECONNECT_MAX_DELAY_MS = 30_000;
@@ -122,20 +118,17 @@ export class EmailSource {
122
118
  }
123
119
  async start(onFire) {
124
120
  this.stopped = false;
125
- const ctx = catalogContextFromStageflow(await resolveStageflowContext(this.cwd));
126
- const scanPaths = await getCatalogScanPaths(ctx);
127
- if (!scanPaths)
128
- return;
129
- const projectRoot = ctx.projectRoot ?? undefined;
130
- const groups = await this.discover(scanPaths.triggerPaths);
121
+ const catalogTriggers = await listCatalogTriggerMatches({
122
+ store: this.store,
123
+ bootCwd: this.cwd,
124
+ sourceRootsOnly: true,
125
+ });
126
+ const groups = this.discover(catalogTriggers);
131
127
  for (const [mailboxKey, group] of groups) {
132
128
  for (const match of group.matches) {
133
- const definitionRef = projectRoot !== undefined
134
- ? path.relative(projectRoot, match.path).replace(/\\/g, "/")
135
- : match.path;
136
129
  await this.store.upsertTrigger({
137
- id: match.definition.id,
138
- definitionRef,
130
+ id: triggerStoreId(match.project_root, match.definition.id),
131
+ definitionRef: match.definition_ref,
139
132
  enabled: true,
140
133
  });
141
134
  }
@@ -161,13 +154,10 @@ export class EmailSource {
161
154
  }
162
155
  this.connections.clear();
163
156
  }
164
- async discover(triggerPaths) {
157
+ discover(catalogTriggers) {
165
158
  const groups = new Map();
166
- for (const triggerPath of triggerPaths) {
167
- const outcome = await loadTriggerOutcome(triggerPath);
168
- if (!outcome.ok)
169
- continue;
170
- const definition = outcome.value;
159
+ for (const match of catalogTriggers) {
160
+ const definition = match.definition;
171
161
  if (definition.kind !== "event" || !definition.enabled)
172
162
  continue;
173
163
  const source = definition.event?.source;
@@ -181,7 +171,7 @@ export class EmailSource {
181
171
  }
182
172
  const mailboxKey = mailboxKeyFor(config);
183
173
  const group = groups.get(mailboxKey) ?? { config, matches: [] };
184
- group.matches.push({ path: triggerPath, definition });
174
+ group.matches.push(match);
185
175
  groups.set(mailboxKey, group);
186
176
  }
187
177
  return groups;
@@ -274,20 +264,22 @@ export class EmailSource {
274
264
  date: message.date,
275
265
  text: message.text,
276
266
  };
277
- for (const { definition } of conn.group.matches) {
267
+ for (const match of conn.group.matches) {
268
+ const { definition } = match;
278
269
  if (!matchesEventFilter(definition.event?.match, summary))
279
270
  continue;
280
- await this.fireSafely(conn.onFire, definition, message, summary);
271
+ await this.fireSafely(conn.onFire, match, message, summary);
281
272
  }
282
273
  }
283
274
  if (maxUid > sinceUid) {
284
275
  await this.store.setTriggerAdapterState(mailboxKey, "uid", String(maxUid));
285
276
  }
286
277
  }
287
- async fireSafely(onFire, definition, message, summary) {
278
+ async fireSafely(onFire, match, message, summary) {
279
+ const definition = match.definition;
288
280
  try {
289
281
  if (definition.task !== undefined) {
290
- await onFire({ triggerId: definition.id });
282
+ await onFire({ triggerId: definition.id, project_root: match.project_root });
291
283
  return;
292
284
  }
293
285
  const task = {
@@ -295,7 +287,7 @@ export class EmailSource {
295
287
  goal: `Handle email: ${message.subject}`,
296
288
  input: summary,
297
289
  };
298
- await onFire({ triggerId: definition.id, task });
290
+ await onFire({ triggerId: definition.id, project_root: match.project_root, task });
299
291
  }
300
292
  catch (err) {
301
293
  this.logError(`email: firing trigger "${definition.id}" failed: ${errMsg(err)}`);
@@ -1,10 +1,6 @@
1
- import path from "node:path";
2
1
  import { Octokit } from "octokit";
3
- import { getCatalogScanPaths } from "../config/browseCatalog.js";
4
- import { loadTriggerOutcome } from "../config/loadTrigger.js";
5
- import { catalogContextFromStageflow } from "../config/resolveCatalogContext.js";
2
+ import { listCatalogTriggerMatches, triggerStoreId, } from "../config/triggerCatalog.js";
6
3
  import { readSecretFromEnvOrFile } from "../config/secretFromEnvOrFile.js";
7
- import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
8
4
  import { matchesEventFilter } from "./triggerEventMatch.js";
9
5
  export const DEFAULT_GITHUB_POLL_INTERVAL_MS = 60_000;
10
6
  export const DEFAULT_GITHUB_TOKEN_SECRET = "GITHUB_TOKEN";
@@ -166,33 +162,27 @@ export class GithubPollSource {
166
162
  * instead of waiting on real interval timers.
167
163
  */
168
164
  async tick(onFire) {
169
- const ctx = catalogContextFromStageflow(await resolveStageflowContext(this.cwd));
170
- const scanPaths = await getCatalogScanPaths(ctx);
171
- if (!scanPaths)
172
- return;
173
- const projectRoot = ctx.projectRoot ?? undefined;
174
- const groups = await this.discover(scanPaths.triggerPaths);
165
+ const catalogTriggers = await listCatalogTriggerMatches({
166
+ store: this.store,
167
+ bootCwd: this.cwd,
168
+ sourceRootsOnly: true,
169
+ });
170
+ const groups = this.discover(catalogTriggers);
175
171
  for (const [repo, matches] of groups) {
176
172
  for (const match of matches) {
177
- const definitionRef = projectRoot !== undefined
178
- ? path.relative(projectRoot, match.path).replace(/\\/g, "/")
179
- : match.path;
180
173
  await this.store.upsertTrigger({
181
- id: match.definition.id,
182
- definitionRef,
174
+ id: triggerStoreId(match.project_root, match.definition.id),
175
+ definitionRef: match.definition_ref,
183
176
  enabled: true,
184
177
  });
185
178
  }
186
179
  await this.pollRepo(repo, matches, onFire);
187
180
  }
188
181
  }
189
- async discover(triggerPaths) {
182
+ discover(catalogTriggers) {
190
183
  const groups = new Map();
191
- for (const triggerPath of triggerPaths) {
192
- const outcome = await loadTriggerOutcome(triggerPath);
193
- if (!outcome.ok)
194
- continue;
195
- const definition = outcome.value;
184
+ for (const match of catalogTriggers) {
185
+ const definition = match.definition;
196
186
  if (definition.kind !== "event" || !definition.enabled)
197
187
  continue;
198
188
  const source = definition.event?.source;
@@ -205,7 +195,7 @@ export class GithubPollSource {
205
195
  continue;
206
196
  }
207
197
  const list = groups.get(repo) ?? [];
208
- list.push({ path: triggerPath, definition });
198
+ list.push(match);
209
199
  groups.set(repo, list);
210
200
  }
211
201
  return groups;
@@ -256,18 +246,20 @@ export class GithubPollSource {
256
246
  const newItems = response.items.filter((item) => isNewerThanCursor(item, cursor)).reverse();
257
247
  for (const item of newItems) {
258
248
  const summary = normalizeGithubItem(item, repo);
259
- for (const { definition } of matches) {
249
+ for (const match of matches) {
250
+ const { definition } = match;
260
251
  if (!matchesEventFilter(definition.event?.match, summary))
261
252
  continue;
262
- await this.fireSafely(onFire, definition, summary);
253
+ await this.fireSafely(onFire, match, summary);
263
254
  }
264
255
  }
265
256
  await this.store.setTriggerAdapterState(adapterId, "cursor", JSON.stringify({ updatedAt: newest.updated_at, number: newest.number }));
266
257
  }
267
- async fireSafely(onFire, definition, summary) {
258
+ async fireSafely(onFire, match, summary) {
259
+ const definition = match.definition;
268
260
  try {
269
261
  if (definition.task !== undefined) {
270
- await onFire({ triggerId: definition.id });
262
+ await onFire({ triggerId: definition.id, project_root: match.project_root });
271
263
  return;
272
264
  }
273
265
  const task = {
@@ -275,7 +267,7 @@ export class GithubPollSource {
275
267
  goal: `Handle PR #${summary.number}: ${summary.title}`,
276
268
  input: summary,
277
269
  };
278
- await onFire({ triggerId: definition.id, task });
270
+ await onFire({ triggerId: definition.id, project_root: match.project_root, task });
279
271
  }
280
272
  catch (err) {
281
273
  this.logError(`github poll: firing trigger "${definition.id}" failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -1,5 +1,5 @@
1
1
  import type { RunSubmission } from "../runstore/submission.js";
2
- import type { AgentPort } from "../agent/port.js";
2
+ import type { StagePort } from "../agent/port.js";
3
3
  import { type ValidationFinding } from "../config/validateCatalog.js";
4
4
  import type { RunStore } from "../runstore/port.js";
5
5
  import type { StageEnvelope } from "../types/envelope.js";
@@ -54,7 +54,7 @@ export type PreparedPipeline = {
54
54
  workspaceDir: string;
55
55
  created?: boolean;
56
56
  };
57
- agent: AgentPort;
57
+ agent: StagePort;
58
58
  store: RunStore;
59
59
  cwd: string;
60
60
  projectRoot: string;
@@ -81,7 +81,7 @@ export type ExecuteStagesOptions = {
81
81
  };
82
82
  export declare function executeStages(prepared: PreparedPipeline, options?: ExecuteStagesOptions): Promise<PipelineRunResult>;
83
83
  export declare function runPipeline(options: {
84
- agent: AgentPort;
84
+ agent: StagePort;
85
85
  store: RunStore;
86
86
  taskPath?: string;
87
87
  taskYaml?: string;
@@ -101,7 +101,7 @@ export declare function runPipeline(options: {
101
101
  export declare function startPipeline(options: {
102
102
  dispatchKey?: string;
103
103
  submission?: RunSubmission;
104
- agent: AgentPort;
104
+ agent: StagePort;
105
105
  store: RunStore;
106
106
  taskPath?: string;
107
107
  taskYaml?: string;
@@ -1,4 +1,4 @@
1
- import type { AgentPort } from "../agent/port.js";
1
+ import type { StagePort } from "../agent/port.js";
2
2
  import type { RunPipelineDagSnapshot, RunStore, StageSnapshot } from "../runstore/port.js";
3
3
  import type { StageBrowserSupport } from "../browser/browserHost.js";
4
4
  import type { StageEnvelope } from "../types/envelope.js";
@@ -17,7 +17,7 @@ type SchedulerPreparedPipeline = {
17
17
  runId: string;
18
18
  workspaceDir: string;
19
19
  };
20
- agent: AgentPort;
20
+ agent: StagePort;
21
21
  store: RunStore;
22
22
  cwd: string;
23
23
  projectRoot?: string;
@@ -2,6 +2,7 @@ import { normalizeForkChoice } from "../envelope/forkChoice.js";
2
2
  import { refreshRunDiskUsage } from "../runstore/diskUsage.js";
3
3
  import { buildPipelineDagSnapshotFromLoaded } from "../runstore/pipelineDagSnapshot.js";
4
4
  import { defaultStageBrowserSupport, resolveStageBrowserEnv, } from "../browser/stageBrowserEnv.js";
5
+ import { runOwnerScope } from "../browser/ownerScope.js";
5
6
  import { teardownRunBrowsers, teardownStageBrowser, } from "../browser/browserTeardown.js";
6
7
  import { isHumanLoginStage } from "../browser/humanLogin.js";
7
8
  import { createRunLiveness } from "../browser/runLiveness.js";
@@ -856,6 +857,7 @@ export async function runPipelineDag(options) {
856
857
  let acquired;
857
858
  try {
858
859
  acquired = await acquireStageProfile(prepared.browser ?? defaultStageBrowserSupport(), {
860
+ scope: runOwnerScope(prepared.browser ?? defaultStageBrowserSupport(), { runId: run.runId }),
859
861
  profile,
860
862
  owner: { runId: run.runId, stageId },
861
863
  isRunLive: createRunLiveness(store),
@@ -944,6 +946,7 @@ export async function runPipelineDag(options) {
944
946
  browserEnv = await resolveStageBrowserEnv(prepared.browser ?? defaultStageBrowserSupport(), {
945
947
  runId: run.runId,
946
948
  stageId,
949
+ scope: runOwnerScope(prepared.browser ?? defaultStageBrowserSupport(), { runId: run.runId }),
947
950
  runDir: run.workspaceDir,
948
951
  browser: stage.browser,
949
952
  attempt,
@@ -1,4 +1,4 @@
1
- import type { AgentPort, OpaqueAnswer } from "../agent/port.js";
1
+ import type { StagePort, OpaqueAnswer } from "../agent/port.js";
2
2
  import type { RunStore, RunMeta } from "../runstore/port.js";
3
3
  import type { LoadedPipeline } from "../types/pipeline.js";
4
4
  import type { TaskFile } from "../types/task.js";
@@ -10,7 +10,7 @@ export type PreparedResumeContext = {
10
10
  runId: string;
11
11
  stageId: string;
12
12
  opaqueAnswer: OpaqueAnswer;
13
- agent: AgentPort;
13
+ agent: StagePort;
14
14
  store: RunStore;
15
15
  hitl: StageHitlController;
16
16
  executionMode: StageExecutionMode;
@@ -1,4 +1,4 @@
1
- import type { AgentPort } from "../agent/port.js";
1
+ import type { StagePort } from "../agent/port.js";
2
2
  import type { RunStore } from "../runstore/port.js";
3
3
  import type { StageExecutionMode } from "./stageConcurrency.js";
4
4
  import type { StageProcessLauncher } from "./stageProcessLauncher.js";
@@ -7,7 +7,7 @@ import { type OperatorCatalog } from "./stageAttemptBootstrap.js";
7
7
  export type ResumeTimedOutContext = {
8
8
  runId: string;
9
9
  stageId: string;
10
- agent: AgentPort;
10
+ agent: StagePort;
11
11
  store: RunStore;
12
12
  hitl: StageHitlController;
13
13
  executionMode: StageExecutionMode;
@@ -1,5 +1,5 @@
1
1
  import { type RunSubmission } from "../runstore/submission.js";
2
- import type { AgentPort, OpaqueAnswer } from "../agent/port.js";
2
+ import type { StagePort, OpaqueAnswer } from "../agent/port.js";
3
3
  import type { InlinePipelineDefinition } from "../types/pipeline.js";
4
4
  import { type DiskBreakdown, type FreeSpaceReader } from "../runstore/diskUsage.js";
5
5
  import { type RunStore } from "../runstore/port.js";
@@ -155,6 +155,9 @@ export declare class RunManager {
155
155
  private readonly queuedDone;
156
156
  private admissionDrainInFlight;
157
157
  private readonly resumeInFlight;
158
+ private readonly gateClosedListeners;
159
+ private readonly beforeBrowserTeardownListeners;
160
+ private readonly shutdownListeners;
158
161
  private readonly retryInFlight;
159
162
  private readonly retryStartOwner;
160
163
  private readonly retryStartWaiters;
@@ -176,7 +179,7 @@ export declare class RunManager {
176
179
  private readonly isGitProject;
177
180
  private acceptingWork;
178
181
  constructor(options: {
179
- agent: AgentPort;
182
+ agent: StagePort;
180
183
  store: RunStore;
181
184
  cwd?: string;
182
185
  projectRoot?: string;
@@ -239,6 +242,7 @@ export declare class RunManager {
239
242
  hasActiveWorker(runId: string, stageId: string): boolean;
240
243
  sweepBrowserSessions(): Promise<{
241
244
  closed: string[];
245
+ released: string[];
242
246
  }>;
243
247
  reconcileOrphanedStages(): Promise<{
244
248
  reconciled: Array<{
@@ -267,6 +271,18 @@ export declare class RunManager {
267
271
  }>>;
268
272
  /** Rebuild in-memory admission queue from persisted `queued` rows (R27). */
269
273
  reenqueuePersistedQueuedRuns(): Promise<void>;
274
+ /** Called when a stage's gate stops accepting operator input (answered, abandoned) or a run is cancelled (no stage id). */
275
+ onGateClosed(listener: (runId: string, stageId?: string) => void): () => void;
276
+ /** Awaited (bounded by the browser support) before a stage's or run's browser is torn down; stageId absent means the whole run. */
277
+ beforeBrowserTeardown(listener: (input: {
278
+ runId: string;
279
+ stageId?: string;
280
+ }) => Promise<void> | void): () => void;
281
+ /** Called once the Host stops accepting work (drain start). */
282
+ onShutdown(listener: () => void): () => void;
283
+ private browserSupport;
284
+ private browserOption;
285
+ private notifyGateClosed;
270
286
  abandonStage(runId: string, stageId: string): Promise<AbandonStageResult>;
271
287
  cancelRun(runId: string, reason: string): Promise<CancelRunResult>;
272
288
  setA2aStore(store: A2aStore): void;