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
@@ -0,0 +1,94 @@
1
+ const REPLAYED = ["status", "tabs", "url", "frame"];
2
+ export function createFakeLiveViewRelay() {
3
+ const sessions = [];
4
+ return {
5
+ sessions,
6
+ async open(_request) {
7
+ const subscribers = new Set();
8
+ const latest = new Map();
9
+ const input = [];
10
+ const answers = [];
11
+ const openDialogs = new Map();
12
+ const session = {
13
+ input,
14
+ answers,
15
+ closed: false,
16
+ reopens: 0,
17
+ reopenResult: { ok: true },
18
+ emit(message) {
19
+ if (message.type === "dialog") {
20
+ const dialog = message.data;
21
+ openDialogs.set(dialog.id, { dialog, answered: false });
22
+ }
23
+ else if (message.type === "dialog_closed") {
24
+ openDialogs.delete(message.data.id);
25
+ }
26
+ else if (message.type !== "closed" && message.type !== "retarget") {
27
+ latest.set(message.type, message);
28
+ }
29
+ for (const subscriber of subscribers)
30
+ subscriber(message);
31
+ },
32
+ subscribe(subscriber) {
33
+ for (const type of REPLAYED) {
34
+ if (type === "frame") {
35
+ for (const { dialog } of openDialogs.values())
36
+ subscriber({ type: "dialog", data: dialog });
37
+ }
38
+ const message = latest.get(type);
39
+ if (message !== undefined)
40
+ subscriber(message);
41
+ }
42
+ subscribers.add(subscriber);
43
+ return () => {
44
+ subscribers.delete(subscriber);
45
+ };
46
+ },
47
+ async sendInput(events) {
48
+ if (session.closed)
49
+ return { ok: false, reason: "closed" };
50
+ input.push(...events);
51
+ return { ok: true, accepted: events.length };
52
+ },
53
+ async answerDialog(answer) {
54
+ if (session.closed)
55
+ return { ok: false, reason: "closed" };
56
+ if (typeof answer?.id !== "string" ||
57
+ typeof answer.accept !== "boolean" ||
58
+ (answer.promptText !== undefined && typeof answer.promptText !== "string")) {
59
+ return { ok: false, reason: "invalid" };
60
+ }
61
+ const open = openDialogs.get(answer.id);
62
+ if (open === undefined || open.answered)
63
+ return { ok: false, reason: "no_dialog" };
64
+ if (!open.dialog.answerable)
65
+ return { ok: false, reason: "not_answerable" };
66
+ open.answered = true;
67
+ answers.push(answer);
68
+ return { ok: true };
69
+ },
70
+ async reopenTab() {
71
+ if (session.closed)
72
+ return { ok: false, reason: "closed" };
73
+ session.reopens += 1;
74
+ return session.reopenResult;
75
+ },
76
+ clearFrame() {
77
+ latest.delete("frame");
78
+ },
79
+ async close() {
80
+ if (session.closed)
81
+ return;
82
+ session.closed = true;
83
+ for (const subscriber of subscribers) {
84
+ subscriber({ type: "closed", data: { reason: "closed" } });
85
+ }
86
+ subscribers.clear();
87
+ openDialogs.clear();
88
+ },
89
+ };
90
+ sessions.push(session);
91
+ return session;
92
+ },
93
+ };
94
+ }
@@ -0,0 +1,6 @@
1
+ import type { LiveViewSource } from "./liveViewSource.js";
2
+ export declare function createFakeLiveViewSource(options?: {
3
+ embedOrigin?: string;
4
+ ttlMs?: number;
5
+ now?: () => number;
6
+ }): LiveViewSource;
@@ -0,0 +1,16 @@
1
+ export function createFakeLiveViewSource(options = {}) {
2
+ const embedOrigin = options.embedOrigin ?? "https://viewer.example";
3
+ const ttlMs = options.ttlMs ?? 60_000;
4
+ const now = options.now ?? Date.now;
5
+ let counter = 0;
6
+ return {
7
+ async viewerAddress(request) {
8
+ counter += 1;
9
+ return {
10
+ url: `${embedOrigin}/view/${encodeURIComponent(request.sandboxId)}?t=${counter}`,
11
+ embedOrigin,
12
+ expiresAt: now() + ttlMs,
13
+ };
14
+ },
15
+ };
16
+ }
@@ -1,2 +1,3 @@
1
1
  import { type BrowserHost } from "./browserHost.js";
2
- export declare function createFakeRemoteBrowserHost(address: string): BrowserHost;
2
+ import type { BrowserHostCapabilityRecord } from "./hostCapabilities.js";
3
+ export declare function createFakeRemoteBrowserHost(address: string, capabilities?: BrowserHostCapabilityRecord): BrowserHost;
@@ -1,6 +1,7 @@
1
1
  import { shortHash, } from "./browserHost.js";
2
- export function createFakeRemoteBrowserHost(address) {
2
+ export function createFakeRemoteBrowserHost(address, capabilities = { attach: "cdp" }) {
3
3
  return {
4
+ capabilities,
4
5
  async ensureProfileBrowser(request) {
5
6
  return {
6
7
  cdpAddress: address,
@@ -0,0 +1,9 @@
1
+ import type { BrowserSandboxOrchestrator } from "./sandboxOrchestrator.js";
2
+ export type FakeSandboxOrchestrator = BrowserSandboxOrchestrator & {
3
+ readonly gracefulStops: readonly string[];
4
+ readonly releases: readonly string[];
5
+ };
6
+ export declare function createFakeSandboxOrchestrator(options?: {
7
+ adapterId?: string;
8
+ address?: (n: number) => string;
9
+ }): FakeSandboxOrchestrator;
@@ -0,0 +1,44 @@
1
+ export function createFakeSandboxOrchestrator(options = {}) {
2
+ const adapterId = options.adapterId ?? "fake";
3
+ const addressOf = options.address ?? ((n) => `ws://127.0.0.1:${9000 + n}/devtools/browser/fake-${n}`);
4
+ const sandboxes = new Map();
5
+ const gracefulStops = [];
6
+ const releases = [];
7
+ let counter = 0;
8
+ return {
9
+ gracefulStops,
10
+ releases,
11
+ async start(request) {
12
+ counter += 1;
13
+ const info = {
14
+ ref: { id: `sbx-${counter}`, adapter: { id: adapterId, version: 1 } },
15
+ labels: { ...request.labels },
16
+ status: "running",
17
+ attachAddress: addressOf(counter),
18
+ };
19
+ sandboxes.set(info.ref.id, info);
20
+ return structuredClone(info);
21
+ },
22
+ async stopGracefully(ref) {
23
+ const info = sandboxes.get(ref.id);
24
+ if (info === undefined)
25
+ return;
26
+ gracefulStops.push(ref.id);
27
+ info.status = "stopped";
28
+ delete info.attachAddress;
29
+ },
30
+ async listByLabel(labels) {
31
+ return [...sandboxes.values()]
32
+ .filter((info) => Object.entries(labels).every(([key, value]) => value === undefined || info.labels[key] === value))
33
+ .map((info) => structuredClone(info));
34
+ },
35
+ async inspect(ref) {
36
+ const info = sandboxes.get(ref.id);
37
+ return info === undefined ? undefined : structuredClone(info);
38
+ },
39
+ async release(ref) {
40
+ if (sandboxes.delete(ref.id))
41
+ releases.push(ref.id);
42
+ },
43
+ };
44
+ }
@@ -16,8 +16,6 @@ export declare function parseGateHandoff(value: unknown): GateHandoff | undefine
16
16
  /** Reads the Host-owned fields off a stored or live wait request; unknown shapes yield nothing. */
17
17
  export declare function parseHostGateContext(value: unknown): HostGateContext;
18
18
  export declare function browserSite(browser: StageBrowserConfig): string | undefined;
19
- /** What the Host stamps on every gate of a browser stage; the agent cannot supply it. */
20
- export declare function hostGateContextFor(browser: StageBrowserConfig | undefined): HostGateContext | undefined;
21
19
  /**
22
20
  * Overwrites (or, for stages without a browser, strips) the Host-owned keys of
23
21
  * a wait request. Non-object requests pass through unchanged.
@@ -7,6 +7,8 @@ export function parseGateHandoff(value) {
7
7
  if (record.kind === "local_window")
8
8
  return { kind: "local_window" };
9
9
  if (record.kind === "live_view" && typeof record.url === "string") {
10
+ if (isRootRelativePath(record.url))
11
+ return { kind: "live_view", url: record.url };
10
12
  try {
11
13
  const url = new URL(record.url);
12
14
  if (url.protocol === "https:" || url.protocol === "http:") {
@@ -19,6 +21,9 @@ export function parseGateHandoff(value) {
19
21
  }
20
22
  return undefined;
21
23
  }
24
+ function isRootRelativePath(value) {
25
+ return value.startsWith("/") && !value.startsWith("//") && !value.includes("\\");
26
+ }
22
27
  /** Reads the Host-owned fields off a stored or live wait request; unknown shapes yield nothing. */
23
28
  export function parseHostGateContext(value) {
24
29
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
@@ -57,17 +62,6 @@ export function browserSite(browser) {
57
62
  return hostOf(browser.login_url);
58
63
  return undefined;
59
64
  }
60
- /** What the Host stamps on every gate of a browser stage; the agent cannot supply it. */
61
- export function hostGateContextFor(browser) {
62
- if (browser === undefined)
63
- return undefined;
64
- const site = browserSite(browser);
65
- return {
66
- handoff: { kind: "local_window" },
67
- ...(site !== undefined ? { site } : {}),
68
- ...(browser.profile !== undefined ? { profile: browser.profile } : {}),
69
- };
70
- }
71
65
  /**
72
66
  * Overwrites (or, for stages without a browser, strips) the Host-owned keys of
73
67
  * a wait request. Non-object requests pass through unchanged.
@@ -0,0 +1,23 @@
1
+ export type DisplayCapability = "local_window" | "virtual_display" | "headless_only";
2
+ export type LiveViewKind = "none" | "relay" | "provider_view";
3
+ export type ViewerInput = "interactive" | "view_only" | "unsupported";
4
+ export type AttachStyle = "cdp" | "host_launched" | "unsupported";
5
+ export type ProfilePersistence = "host_volume" | "provider_managed" | "none";
6
+ export type DialogHandling = "relay_handled" | "provider_handled" | "unsupported";
7
+ export type PopupHandling = "relay_retarget" | "provider_handled" | "unsupported";
8
+ export type BrowserHostCapabilities = {
9
+ display: DisplayCapability;
10
+ liveView: LiveViewKind;
11
+ viewerInput: ViewerInput;
12
+ attach: AttachStyle;
13
+ profilePersistence: ProfilePersistence;
14
+ gracefulCloseRequired: boolean;
15
+ hardAllowlist: boolean;
16
+ dialogs: DialogHandling;
17
+ popups: PopupHandling;
18
+ permissionPolicy: boolean;
19
+ };
20
+ /** Every field is optional; an unset field reads as its conservative default. */
21
+ export type BrowserHostCapabilityRecord = Partial<BrowserHostCapabilities>;
22
+ export declare const DEFAULT_BROWSER_HOST_CAPABILITIES: Readonly<BrowserHostCapabilities>;
23
+ export declare function resolveBrowserHostCapabilities(record?: BrowserHostCapabilityRecord): BrowserHostCapabilities;
@@ -0,0 +1,23 @@
1
+ export const DEFAULT_BROWSER_HOST_CAPABILITIES = {
2
+ display: "headless_only",
3
+ liveView: "none",
4
+ viewerInput: "unsupported",
5
+ attach: "unsupported",
6
+ profilePersistence: "none",
7
+ gracefulCloseRequired: false,
8
+ hardAllowlist: false,
9
+ dialogs: "unsupported",
10
+ popups: "unsupported",
11
+ permissionPolicy: false,
12
+ };
13
+ export function resolveBrowserHostCapabilities(record) {
14
+ const resolved = { ...DEFAULT_BROWSER_HOST_CAPABILITIES };
15
+ if (record === undefined)
16
+ return resolved;
17
+ const target = resolved;
18
+ for (const [key, value] of Object.entries(record)) {
19
+ if (value !== undefined && key in DEFAULT_BROWSER_HOST_CAPABILITIES)
20
+ target[key] = value;
21
+ }
22
+ return resolved;
23
+ }
@@ -4,7 +4,7 @@ export type DisplayProbe = () => {
4
4
  hasDisplay: boolean;
5
5
  docker: boolean;
6
6
  };
7
- export declare const NO_SCREEN_MESSAGE = "A visible browser is needed for login, but this Host has no screen. A live view handoff is not available yet.";
7
+ export declare const NO_SCREEN_MESSAGE = "A visible browser is needed for login, but this Host has no screen and no live view.";
8
8
  export declare const DOCKER_HINT = " This Host runs in Docker: run the login stage on a machine with a screen, or log in there first and reuse the profile.";
9
9
  /**
10
10
  * A human login stage is a stage with a `browser` whose after-phase verify
@@ -15,4 +15,4 @@ export declare function isHumanLoginStage(dag: Pick<ResolvedPipelineDag, "nodes"
15
15
  export declare function loginPageUrl(browser: StageBrowserConfig): string | undefined;
16
16
  export declare function defaultDisplayProbe(platform?: NodeJS.Platform, env?: Record<string, string | undefined>): ReturnType<DisplayProbe>;
17
17
  export declare function noScreenError(docker: boolean): Error;
18
- export declare function humanLoginPromptBlock(url: string | undefined): string;
18
+ export declare function humanLoginPromptBlock(url: string | undefined, handoff?: "local_window" | "live_view"): string;
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
- export const NO_SCREEN_MESSAGE = "A visible browser is needed for login, but this Host has no screen. A live view handoff is not available yet.";
2
+ export const NO_SCREEN_MESSAGE = "A visible browser is needed for login, but this Host has no screen and no live view.";
3
3
  export const DOCKER_HINT = " This Host runs in Docker: run the login stage on a machine with a screen, or log in there first and reuse the profile.";
4
4
  /**
5
5
  * A human login stage is a stage with a `browser` whose after-phase verify
@@ -23,7 +23,16 @@ export function defaultDisplayProbe(platform = process.platform, env = process.e
23
23
  export function noScreenError(docker) {
24
24
  return new Error(`${NO_SCREEN_MESSAGE}${docker ? DOCKER_HINT : ""}`);
25
25
  }
26
- export function humanLoginPromptBlock(url) {
26
+ export function humanLoginPromptBlock(url, handoff = "local_window") {
27
+ if (handoff === "live_view") {
28
+ return [
29
+ "## Human login",
30
+ "",
31
+ `The Host has opened ${url !== undefined ? JSON.stringify(url) : "the login page"} in a browser the operator can see through the live view shown in the console in this gate.`,
32
+ "Do not type credentials and do not drive the browser. Call ask_operator with kind confirm: ask the operator to log in through the live view and confirm when done. Leave the browser open.",
33
+ "After the operator accepts, emit the envelope. The Host re-checks the login itself; if it still sees a logged-out session, this stage runs again.",
34
+ ].join("\n");
35
+ }
27
36
  return [
28
37
  "## Human login",
29
38
  "",
@@ -0,0 +1,11 @@
1
+ import type { LiveViewSessionRequest } from "./liveViewRelay.js";
2
+ /**
3
+ * Rebuilds a relay request from what the Host persisted (the stage env and, for a
4
+ * stage with a profile, the run's anchor), so a live view survives a Host restart.
5
+ */
6
+ export declare function readLiveViewSessionRequest(input: {
7
+ runDir: string;
8
+ runId: string;
9
+ stageId: string;
10
+ profile?: string;
11
+ }): Promise<LiveViewSessionRequest | undefined>;
@@ -0,0 +1,22 @@
1
+ import { readPersistedAnchor } from "./anchor.js";
2
+ import { readStagePersistedBrowserEnv } from "./stageBrowserEnv.js";
3
+ /**
4
+ * Rebuilds a relay request from what the Host persisted (the stage env and, for a
5
+ * stage with a profile, the run's anchor), so a live view survives a Host restart.
6
+ */
7
+ export async function readLiveViewSessionRequest(input) {
8
+ const env = await readStagePersistedBrowserEnv(input.runDir, input.stageId);
9
+ if (env === undefined)
10
+ return undefined;
11
+ const anchor = input.profile !== undefined
12
+ ? await readPersistedAnchor(input.runDir, input.profile)
13
+ : undefined;
14
+ return {
15
+ runId: input.runId,
16
+ stageId: input.stageId,
17
+ env,
18
+ ...(anchor !== undefined
19
+ ? { anchorEnv: anchor.anchorEnv, cdpAddress: anchor.cdpAddress }
20
+ : {}),
21
+ };
22
+ }
@@ -0,0 +1,76 @@
1
+ import type { BrowserEnv } from "./browserHost.js";
2
+ export type LiveViewMessage = {
3
+ type: "frame" | "status" | "tabs" | "url" | "retarget" | "closed" | "dialog" | "dialog_closed";
4
+ data: unknown;
5
+ };
6
+ export type LiveViewInputEvent = Record<string, unknown> & {
7
+ type: string;
8
+ };
9
+ export type LiveViewSubscriber = (message: LiveViewMessage) => void;
10
+ export type LiveViewInputRejection = "closed" | "not_array" | "batch_too_large" | "rate_limited" | "invalid_event" | "upstream_unavailable";
11
+ /** A rejected batch forwards nothing. */
12
+ export type LiveViewInputResult = {
13
+ ok: true;
14
+ accepted: number;
15
+ } | {
16
+ ok: false;
17
+ reason: LiveViewInputRejection;
18
+ };
19
+ export type LiveViewDialogKind = "alert" | "confirm" | "prompt" | "beforeunload";
20
+ /** Untrusted page text: plain text only, never HTML, never logged. */
21
+ export type LiveViewDialog = {
22
+ id: string;
23
+ kind: LiveViewDialogKind;
24
+ message: string;
25
+ defaultPrompt: string;
26
+ targetId: string;
27
+ /** False for alert and beforeunload: agent-browser accepts them within milliseconds. */
28
+ answerable: boolean;
29
+ };
30
+ export type LiveViewDialogCloseResult = "accepted" | "dismissed" | "timeout" | "closed_by_page";
31
+ export type LiveViewDialogAnswer = {
32
+ id: string;
33
+ accept: boolean;
34
+ promptText?: string;
35
+ };
36
+ export type LiveViewDialogRejection = "no_dialog" | "not_answerable" | "invalid" | "closed";
37
+ export type LiveViewDialogResult = {
38
+ ok: true;
39
+ } | {
40
+ ok: false;
41
+ reason: LiveViewDialogRejection;
42
+ };
43
+ export type LiveViewReopenRejection = "closed" | "no_tab" | "upstream_unavailable" | "rate_limited" | "failed";
44
+ export type LiveViewReopenResult = {
45
+ ok: true;
46
+ } | {
47
+ ok: false;
48
+ reason: LiveViewReopenRejection;
49
+ };
50
+ export type LiveViewSessionRequest = {
51
+ runId: string;
52
+ stageId: string;
53
+ /** The stage's persisted browser env; the relay never changes it. */
54
+ env: BrowserEnv;
55
+ /** The run's shared browser anchor env, when the stage has a profile. */
56
+ anchorEnv?: BrowserEnv;
57
+ /** The browser's CDP address when the Host already holds it; else the relay reads it from the env or the session. */
58
+ cdpAddress?: string;
59
+ };
60
+ export interface LiveViewSession {
61
+ /** Replays the cached status, tabs, url, any open dialog and last frame to the new subscriber first. Returns an unsubscribe function. */
62
+ subscribe(subscriber: LiveViewSubscriber): () => void;
63
+ /** Forwards one ordered batch of input events, all or nothing. */
64
+ sendInput(events: readonly LiveViewInputEvent[]): Promise<LiveViewInputResult>;
65
+ /** Answers the open confirm or prompt with that id; one answer per dialog. */
66
+ answerDialog(answer: LiveViewDialogAnswer): Promise<LiveViewDialogResult>;
67
+ /** Replaces the stage's current tab with a fresh one at the same URL (same browser context); the old tab closes only after the new one streams. */
68
+ reopenTab(): Promise<LiveViewReopenResult>;
69
+ /** Drops the cached frame so a re-target does not replay a stale page. */
70
+ clearFrame(): void;
71
+ /** Idempotent; subscribers receive a `closed` message. */
72
+ close(): Promise<void>;
73
+ }
74
+ export interface LiveViewRelay {
75
+ open(request: LiveViewSessionRequest): Promise<LiveViewSession>;
76
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import type { LiveViewRelay, LiveViewSession, LiveViewSessionRequest } from "./liveViewRelay.js";
2
+ export declare const LIVE_VIEW_SESSION_GRACE_MS = 5000;
3
+ export type LiveViewLease = {
4
+ session: LiveViewSession;
5
+ release(): void;
6
+ };
7
+ export interface LiveViewSessionManager {
8
+ /** Opens the one relay session of (run, stage) on first use; undefined when the stage has no browser to show. */
9
+ acquire(runId: string, stageId: string): Promise<LiveViewLease | undefined>;
10
+ /** The open session, if any; never opens one. */
11
+ peek(runId: string, stageId: string): LiveViewSession | undefined;
12
+ closeStage(runId: string, stageId: string): Promise<void>;
13
+ closeRun(runId: string): Promise<void>;
14
+ /** Closes every session and cancels every grace timer. */
15
+ dispose(): Promise<void>;
16
+ }
17
+ export type LiveViewSessionManagerOptions = {
18
+ relay: LiveViewRelay;
19
+ /** Rebuilds the relay request from persisted state; undefined when the stage has no browser. */
20
+ request: (runId: string, stageId: string) => Promise<LiveViewSessionRequest | undefined>;
21
+ graceMs?: number;
22
+ setTimer?: (fn: () => void, ms: number) => unknown;
23
+ clearTimer?: (handle: unknown) => void;
24
+ };
25
+ export declare function createLiveViewSessionManager(options: LiveViewSessionManagerOptions): LiveViewSessionManager;
@@ -0,0 +1,139 @@
1
+ export const LIVE_VIEW_SESSION_GRACE_MS = 5_000;
2
+ function keyOf(runId, stageId) {
3
+ return `${runId}\0${stageId}`;
4
+ }
5
+ export function createLiveViewSessionManager(options) {
6
+ const graceMs = options.graceMs ?? LIVE_VIEW_SESSION_GRACE_MS;
7
+ const setTimer = options.setTimer ??
8
+ ((fn, ms) => {
9
+ const handle = setTimeout(fn, ms);
10
+ handle.unref();
11
+ return handle;
12
+ });
13
+ const clearTimer = options.clearTimer ?? ((handle) => clearTimeout(handle));
14
+ const entries = new Map();
15
+ const closing = new Map();
16
+ function track(entry, closer) {
17
+ const key = keyOf(entry.runId, entry.stageId);
18
+ const set = closing.get(key) ?? new Set();
19
+ closing.set(key, set);
20
+ const tracked = closer.finally(() => {
21
+ set.delete(tracked);
22
+ if (set.size === 0 && closing.get(key) === set)
23
+ closing.delete(key);
24
+ });
25
+ set.add(tracked);
26
+ return tracked;
27
+ }
28
+ async function settled(match) {
29
+ const pending = [];
30
+ for (const [key, set] of closing)
31
+ if (match(key))
32
+ pending.push(...set);
33
+ await Promise.all(pending);
34
+ }
35
+ function stopTimer(entry) {
36
+ if (entry.timer === undefined)
37
+ return;
38
+ clearTimer(entry.timer);
39
+ entry.timer = undefined;
40
+ }
41
+ function closeEntry(entry) {
42
+ stopTimer(entry);
43
+ return track(entry, (async () => {
44
+ const session = await entry.opening.catch(() => undefined);
45
+ await session?.close().catch(() => undefined);
46
+ })());
47
+ }
48
+ function open(runId, stageId) {
49
+ const key = keyOf(runId, stageId);
50
+ const entry = { runId, stageId, refs: 0 };
51
+ entry.opening = (async () => {
52
+ const request = await options.request(runId, stageId);
53
+ if (request === undefined)
54
+ return undefined;
55
+ const session = await options.relay.open(request);
56
+ entry.session = session;
57
+ session.subscribe((message) => {
58
+ if (message.type === "closed" && entries.get(key) === entry)
59
+ entries.delete(key);
60
+ });
61
+ return session;
62
+ })();
63
+ entries.set(key, entry);
64
+ entry.opening.then((session) => {
65
+ if (session === undefined && entries.get(key) === entry)
66
+ entries.delete(key);
67
+ }, () => {
68
+ if (entries.get(key) === entry)
69
+ entries.delete(key);
70
+ });
71
+ return entry;
72
+ }
73
+ return {
74
+ async acquire(runId, stageId) {
75
+ const entry = entries.get(keyOf(runId, stageId)) ?? open(runId, stageId);
76
+ stopTimer(entry);
77
+ entry.refs += 1;
78
+ let session;
79
+ try {
80
+ session = await entry.opening;
81
+ }
82
+ catch (err) {
83
+ entry.refs -= 1;
84
+ throw err;
85
+ }
86
+ if (session === undefined) {
87
+ entry.refs -= 1;
88
+ return undefined;
89
+ }
90
+ let released = false;
91
+ return {
92
+ session,
93
+ release() {
94
+ if (released)
95
+ return;
96
+ released = true;
97
+ entry.refs -= 1;
98
+ if (entry.refs > 0 || entries.get(keyOf(runId, stageId)) !== entry)
99
+ return;
100
+ stopTimer(entry);
101
+ entry.timer = setTimer(() => {
102
+ entry.timer = undefined;
103
+ if (entry.refs > 0 || entries.get(keyOf(runId, stageId)) !== entry)
104
+ return;
105
+ entries.delete(keyOf(runId, stageId));
106
+ void track(entry, session.close().catch(() => undefined));
107
+ }, graceMs);
108
+ },
109
+ };
110
+ },
111
+ peek(runId, stageId) {
112
+ return entries.get(keyOf(runId, stageId))?.session;
113
+ },
114
+ async closeStage(runId, stageId) {
115
+ const key = keyOf(runId, stageId);
116
+ const entry = entries.get(key);
117
+ if (entry !== undefined) {
118
+ entries.delete(key);
119
+ void closeEntry(entry);
120
+ }
121
+ await settled((k) => k === key);
122
+ },
123
+ async closeRun(runId) {
124
+ const doomed = [...entries.values()].filter((entry) => entry.runId === runId);
125
+ for (const entry of doomed) {
126
+ entries.delete(keyOf(entry.runId, entry.stageId));
127
+ void closeEntry(entry);
128
+ }
129
+ await settled((k) => k.startsWith(`${runId}\0`));
130
+ },
131
+ async dispose() {
132
+ const doomed = [...entries.values()];
133
+ entries.clear();
134
+ for (const entry of doomed)
135
+ void closeEntry(entry);
136
+ await settled(() => true);
137
+ },
138
+ };
139
+ }
@@ -0,0 +1,25 @@
1
+ import type { BrowserSandboxOrchestrator } from "./sandboxOrchestrator.js";
2
+ export type LiveViewSourceRequest = {
3
+ runId: string;
4
+ stageId: string;
5
+ /** Opaque sandbox reference of the stage's browser. */
6
+ sandboxId: string;
7
+ };
8
+ export type LiveViewAddress = {
9
+ /** Short-lived and per-session. Never store it in a gate, run file, log or audit record. */
10
+ url: string;
11
+ /** Origin the console must allow to embed the viewer. */
12
+ embedOrigin: string;
13
+ expiresAt: number;
14
+ };
15
+ export interface LiveViewSource {
16
+ viewerAddress(request: LiveViewSourceRequest): Promise<LiveViewAddress>;
17
+ }
18
+ export type SandboxIdResolver = (input: {
19
+ scope: string;
20
+ runId: string;
21
+ stageId: string;
22
+ profile?: string;
23
+ }) => Promise<string | undefined>;
24
+ /** Finds the stage's sandbox by the labels every orchestrator stamps on it. */
25
+ export declare function sandboxIdFromLabels(orchestrator: Pick<BrowserSandboxOrchestrator, "listByLabel">): SandboxIdResolver;
@@ -0,0 +1,7 @@
1
+ /** Finds the stage's sandbox by the labels every orchestrator stamps on it. */
2
+ export function sandboxIdFromLabels(orchestrator) {
3
+ return async ({ scope, runId, profile }) => {
4
+ const found = await orchestrator.listByLabel({ scope, runId, ...(profile !== undefined ? { profile } : {}) });
5
+ return found.find((info) => info.status === "running")?.ref.id;
6
+ };
7
+ }