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,30 +1,70 @@
1
1
  import { mkdir, rename, rm, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { loadHostConfig } from "../config/hostConfig.js";
3
+ import { browserHostForOwner, loadHostConfig } from "../config/hostConfig.js";
4
4
  import { stageDir } from "../runstore/paths.js";
5
5
  import { safeAudit } from "./auditSink.js";
6
6
  import { assertBrowserSitesAllowed } from "./sitePolicy.js";
7
7
  import { BROWSER_ENV_FILENAME, BROWSER_POLICY_FILENAME, } from "./browserHost.js";
8
8
  import { ensureRunProfileBrowser } from "./anchor.js";
9
9
  import { BROWSER_CLOSED_FILENAME, closeBrowserSession, defaultBrowserRunner, writeSessionOwner, } from "./browserTeardown.js";
10
- import { defaultDisplayProbe, loginPageUrl, noScreenError } from "./humanLogin.js";
10
+ import { loginPageUrl } from "./humanLogin.js";
11
+ import { persistStageHandoff, resolveStageHandoffCapabilities } from "./stageHandoff.js";
11
12
  import { ensureStageLoginCheck, OPEN_COMMAND_TIMEOUT_MS } from "./loginCheck.js";
12
13
  import { readPersistedBrowserEnv } from "./persistedEnv.js";
14
+ import { createContainerBrowserHost } from "./containerBrowserHost.js";
15
+ import { createDockerSandboxOrchestrator } from "./dockerSandboxOrchestrator.js";
16
+ import { createVolumeProfileStore } from "./volumeProfileStore.js";
13
17
  import { createLocalBrowserHost } from "./localBrowserHost.js";
14
18
  import { createLocalProfileStore } from "./localProfileStore.js";
15
- import { LOCAL_BROWSER_SCOPE } from "./profileStore.js";
19
+ import { localOwnerScope } from "./ownerScope.js";
16
20
  export { BROWSER_ENV_FILENAME };
17
21
  export async function readStagePersistedBrowserEnv(runDir, stageId) {
18
22
  return readPersistedBrowserEnv(path.join(stageDir(runDir, stageId), BROWSER_ENV_FILENAME));
19
23
  }
20
24
  let defaultSupport;
21
- export function defaultStageBrowserSupport() {
22
- defaultSupport ??= {
23
- host: createLocalBrowserHost(),
25
+ export function hostLaunchOptions() {
26
+ const config = loadHostConfig();
27
+ return {
28
+ launchArgs: config.browserLaunchArgs,
29
+ ...(config.browserExecutablePath !== undefined
30
+ ? { executablePath: config.browserExecutablePath }
31
+ : {}),
32
+ };
33
+ }
34
+ function configuredBrowserSupport(liveView) {
35
+ const config = loadHostConfig();
36
+ if (browserHostForOwner(config, localOwnerScope()) === "container") {
37
+ const container = config.browserContainer;
38
+ return {
39
+ host: createContainerBrowserHost({
40
+ orchestrator: createDockerSandboxOrchestrator({
41
+ image: container.image,
42
+ shmSize: container.shmSize,
43
+ memory: container.memory,
44
+ pidsLimit: container.pidsLimit,
45
+ user: "1000:1000",
46
+ }),
47
+ liveView,
48
+ local: hostLaunchOptions(),
49
+ }),
50
+ profiles: createVolumeProfileStore(),
51
+ };
52
+ }
53
+ return {
54
+ host: createLocalBrowserHost({ liveView, ...hostLaunchOptions() }),
24
55
  profiles: createLocalProfileStore(),
25
56
  };
57
+ }
58
+ export function defaultStageBrowserSupport() {
59
+ defaultSupport ??= configuredBrowserSupport("none");
26
60
  return defaultSupport;
27
61
  }
62
+ let consoleSupport;
63
+ /** Browser support for a process that serves the live view routes (`sf ui`, `sf mcp`). */
64
+ export function consoleStageBrowserSupport() {
65
+ consoleSupport ??= configuredBrowserSupport("relay");
66
+ return consoleSupport;
67
+ }
28
68
  /**
29
69
  * Computed once per stage and persisted in the run dir: agent-browser silently
30
70
  * relaunches the browser when a command's launch env differs from the daemon's,
@@ -34,11 +74,9 @@ export async function resolveStageBrowserEnv(support, input) {
34
74
  const { browser } = input;
35
75
  if (browser === undefined)
36
76
  return undefined;
37
- if (input.humanLogin === true) {
38
- const screen = (support.display ?? defaultDisplayProbe)();
39
- if (!screen.hasDisplay)
40
- throw noScreenError(screen.docker);
41
- }
77
+ const capabilities = resolveStageHandoffCapabilities(support, {
78
+ ...(input.humanLogin === true ? { humanLogin: true } : {}),
79
+ });
42
80
  if ((browser.allow_domains?.length ?? 0) > 0 ||
43
81
  browser.check !== undefined ||
44
82
  browser.login_url !== undefined) {
@@ -46,10 +84,11 @@ export async function resolveStageBrowserEnv(support, input) {
46
84
  }
47
85
  const dir = stageDir(input.runDir, input.stageId);
48
86
  const file = path.join(dir, BROWSER_ENV_FILENAME);
49
- // TODO(multi-tenant): open the profile in the run owner's scope, not the fixed local scope.
87
+ await mkdir(dir, { recursive: true });
88
+ await persistStageHandoff({ runDir: input.runDir, stageId: input.stageId }, capabilities);
50
89
  const profile = browser.profile !== undefined
51
90
  ? await support.profiles.open({
52
- scope: LOCAL_BROWSER_SCOPE,
91
+ scope: input.scope,
53
92
  name: browser.profile,
54
93
  })
55
94
  : undefined;
@@ -97,10 +136,9 @@ export async function resolveStageBrowserEnv(support, input) {
97
136
  if (persisted === undefined) {
98
137
  await writeEnv(fresh);
99
138
  if (browser.profile !== undefined) {
100
- // TODO(multi-tenant): audit the run owner's scope.
101
139
  await safeAudit(support.audit, {
102
140
  event: "profile_used",
103
- scope: LOCAL_BROWSER_SCOPE,
141
+ scope: input.scope,
104
142
  profile: browser.profile,
105
143
  runId: input.runId,
106
144
  stageId: input.stageId,
@@ -0,0 +1,47 @@
1
+ import type { StageBrowserConfig } from "../types/stage.js";
2
+ import type { StageBrowserSupport } from "./browserHost.js";
3
+ import { type GateHandoff, type HostGateContext } from "./gateHandoff.js";
4
+ import { type BrowserHostCapabilities } from "./hostCapabilities.js";
5
+ /** The slice of the host capability record a stage's handoff depends on; persisted per stage. */
6
+ export type StageHandoffCapabilities = Pick<BrowserHostCapabilities, "display" | "liveView">;
7
+ type HandoffInput = {
8
+ runDir: string;
9
+ runId: string;
10
+ stageId: string;
11
+ };
12
+ /**
13
+ * Probes the display, applies the host's capabilities, and rejects a human-login
14
+ * stage that has neither a screen nor a live view. Pure of disk writes.
15
+ */
16
+ export declare function resolveStageHandoffCapabilities(support: StageBrowserSupport, options?: {
17
+ humanLogin?: boolean;
18
+ }): StageHandoffCapabilities;
19
+ export declare function persistStageHandoff(input: {
20
+ runDir: string;
21
+ stageId: string;
22
+ }, capabilities: StageHandoffCapabilities): Promise<void>;
23
+ /** Persisted capabilities of a stage; undefined when none were recorded (older runs, direct stage runs). */
24
+ export declare function readStageHandoff(input: {
25
+ runDir: string;
26
+ stageId: string;
27
+ }): Promise<StageHandoffCapabilities | undefined>;
28
+ export declare function gateHandoffFor(context: {
29
+ runId: string;
30
+ stageId: string;
31
+ capabilities: StageHandoffCapabilities;
32
+ }): GateHandoff | undefined;
33
+ /** What the Host stamps on every gate of a browser stage; the agent cannot supply it. */
34
+ export declare function hostGateContextFor(browser: StageBrowserConfig | undefined, context: {
35
+ runId: string;
36
+ stageId: string;
37
+ capabilities: StageHandoffCapabilities;
38
+ }): HostGateContext | undefined;
39
+ /** The handoff for a stage's gates and login prompt. `dirStageId` is the manifest id when it differs from the gate's stage id. */
40
+ export declare function stageGateHandoff(input: HandoffInput & {
41
+ dirStageId?: string;
42
+ }): Promise<GateHandoff | undefined>;
43
+ /** Host-owned gate fields for a stage; undefined for a stage without a browser. */
44
+ export declare function stageGateContext(input: HandoffInput & {
45
+ browser: StageBrowserConfig | undefined;
46
+ }): Promise<HostGateContext | undefined>;
47
+ export {};
@@ -0,0 +1,101 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { stageDir } from "../runstore/paths.js";
4
+ import { browserSite, } from "./gateHandoff.js";
5
+ import { resolveBrowserHostCapabilities, } from "./hostCapabilities.js";
6
+ import { defaultDisplayProbe, noScreenError } from "./humanLogin.js";
7
+ const BROWSER_CAPABILITIES_FILENAME = "browser-capabilities.json";
8
+ /**
9
+ * Probes the display, applies the host's capabilities, and rejects a human-login
10
+ * stage that has neither a screen nor a live view. Pure of disk writes.
11
+ */
12
+ export function resolveStageHandoffCapabilities(support, options = {}) {
13
+ const capabilities = resolveBrowserHostCapabilities(support.host.capabilities);
14
+ const probe = support.display ?? defaultDisplayProbe;
15
+ if (support.host.capabilities === undefined) {
16
+ capabilities.display = probe().hasDisplay ? "local_window" : "headless_only";
17
+ }
18
+ else if (capabilities.display === "local_window" && support.display !== undefined && !support.display().hasDisplay) {
19
+ capabilities.display = "headless_only";
20
+ }
21
+ if (options.humanLogin === true &&
22
+ capabilities.display === "headless_only" &&
23
+ capabilities.liveView === "none") {
24
+ throw noScreenError(probe().docker);
25
+ }
26
+ if (options.humanLogin === true &&
27
+ capabilities.display !== "local_window" &&
28
+ capabilities.viewerInput === "view_only") {
29
+ throw new Error("A human login stage needs an interactive live view, but this browser host offers a view-only one (missing capability viewer_input: interactive).");
30
+ }
31
+ return { display: capabilities.display, liveView: capabilities.liveView };
32
+ }
33
+ export async function persistStageHandoff(input, capabilities) {
34
+ const dir = stageDir(input.runDir, input.stageId);
35
+ await mkdir(dir, { recursive: true });
36
+ await writeFile(path.join(dir, BROWSER_CAPABILITIES_FILENAME), `${JSON.stringify({ display: capabilities.display, liveView: capabilities.liveView })}\n`, { mode: 0o600 });
37
+ }
38
+ /** Persisted capabilities of a stage; undefined when none were recorded (older runs, direct stage runs). */
39
+ export async function readStageHandoff(input) {
40
+ const file = path.join(stageDir(input.runDir, input.stageId), BROWSER_CAPABILITIES_FILENAME);
41
+ try {
42
+ const parsed = JSON.parse(await readFile(file, "utf8"));
43
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
44
+ const { display, liveView } = resolveBrowserHostCapabilities(parsed);
45
+ return { display, liveView };
46
+ }
47
+ }
48
+ catch (err) {
49
+ if (err.code !== "ENOENT")
50
+ throw err;
51
+ }
52
+ return undefined;
53
+ }
54
+ export function gateHandoffFor(context) {
55
+ const { capabilities } = context;
56
+ if (capabilities.display === "local_window")
57
+ return { kind: "local_window" };
58
+ if (capabilities.liveView !== "none") {
59
+ return {
60
+ kind: "live_view",
61
+ url: `/api/runs/${encodeURIComponent(context.runId)}/stages/${encodeURIComponent(context.stageId)}/live-view`,
62
+ };
63
+ }
64
+ return undefined;
65
+ }
66
+ /** What the Host stamps on every gate of a browser stage; the agent cannot supply it. */
67
+ export function hostGateContextFor(browser, context) {
68
+ if (browser === undefined)
69
+ return undefined;
70
+ const site = browserSite(browser);
71
+ const handoff = gateHandoffFor(context);
72
+ return {
73
+ ...(handoff !== undefined ? { handoff } : {}),
74
+ ...(site !== undefined ? { site } : {}),
75
+ ...(browser.profile !== undefined ? { profile: browser.profile } : {}),
76
+ };
77
+ }
78
+ // No record (older runs, direct stage runs) keeps its historical meaning: a local window.
79
+ const LEGACY_CAPABILITIES = { display: "local_window", liveView: "none" };
80
+ async function capabilitiesOf(input) {
81
+ return ((await readStageHandoff({ runDir: input.runDir, stageId: input.dirStageId ?? input.stageId })) ??
82
+ LEGACY_CAPABILITIES);
83
+ }
84
+ /** The handoff for a stage's gates and login prompt. `dirStageId` is the manifest id when it differs from the gate's stage id. */
85
+ export async function stageGateHandoff(input) {
86
+ return gateHandoffFor({
87
+ runId: input.runId,
88
+ stageId: input.stageId,
89
+ capabilities: await capabilitiesOf(input),
90
+ });
91
+ }
92
+ /** Host-owned gate fields for a stage; undefined for a stage without a browser. */
93
+ export async function stageGateContext(input) {
94
+ if (input.browser === undefined)
95
+ return undefined;
96
+ return hostGateContextFor(input.browser, {
97
+ runId: input.runId,
98
+ stageId: input.stageId,
99
+ capabilities: await capabilitiesOf(input),
100
+ });
101
+ }
@@ -2,6 +2,7 @@ import type { StageBrowserSupport } from "./browserHost.js";
2
2
  import type { ProfileLock, ProfileLockOwner, RunLiveness } from "./profileLock.js";
3
3
  export declare function stageProfileLock(support: StageBrowserSupport): ProfileLock;
4
4
  export type ProfileWaitInput = {
5
+ scope: string;
5
6
  profile: string;
6
7
  owner: ProfileLockOwner;
7
8
  isRunLive?: RunLiveness;
@@ -1,5 +1,4 @@
1
1
  import { createLocalProfileLock } from "./localProfileLock.js";
2
- import { LOCAL_BROWSER_SCOPE } from "./profileStore.js";
3
2
  const DEFAULT_LOCK_POLL_MS = 250;
4
3
  let defaultLock;
5
4
  export function stageProfileLock(support) {
@@ -14,8 +13,7 @@ export function stageProfileLock(support) {
14
13
  */
15
14
  export async function acquireStageProfile(support, input) {
16
15
  const locks = stageProfileLock(support);
17
- // TODO(multi-tenant): take the scope from the run owner instead of the fixed local scope.
18
- const key = { scope: LOCAL_BROWSER_SCOPE, name: input.profile };
16
+ const key = { scope: input.scope, name: input.profile };
19
17
  const pollMs = support.lockPollMs ?? DEFAULT_LOCK_POLL_MS;
20
18
  let announced;
21
19
  for (;;) {
@@ -0,0 +1,7 @@
1
+ import { type ProfileStore } from "./profileStore.js";
2
+ /**
3
+ * Profiles that live in a volume the sandbox orchestrator provisions. The handle
4
+ * carries a reference, never a folder; creating, sizing and removing the volume
5
+ * belong to the orchestrator, so `delete` only forgets the reference.
6
+ */
7
+ export declare function createVolumeProfileStore(): ProfileStore;
@@ -0,0 +1,35 @@
1
+ import { validateProfileKey, validateProfileScope, } from "./profileStore.js";
2
+ /**
3
+ * Profiles that live in a volume the sandbox orchestrator provisions. The handle
4
+ * carries a reference, never a folder; creating, sizing and removing the volume
5
+ * belong to the orchestrator, so `delete` only forgets the reference.
6
+ */
7
+ export function createVolumeProfileStore() {
8
+ const scopes = new Map();
9
+ return {
10
+ async open(rawKey) {
11
+ const key = validateProfileKey(rawKey);
12
+ let profiles = scopes.get(key.scope);
13
+ if (!profiles) {
14
+ profiles = new Map();
15
+ scopes.set(key.scope, profiles);
16
+ }
17
+ let handle = profiles.get(key.name);
18
+ if (!handle) {
19
+ handle = { key, volumeRef: `${key.scope}/${key.name}` };
20
+ profiles.set(key.name, handle);
21
+ }
22
+ return handle;
23
+ },
24
+ async list(scope) {
25
+ return [...(scopes.get(validateProfileScope(scope))?.keys() ?? [])].sort();
26
+ },
27
+ async delete(rawKey) {
28
+ const key = validateProfileKey(rawKey);
29
+ scopes.get(key.scope)?.delete(key.name);
30
+ },
31
+ async deleteScope(scope) {
32
+ scopes.delete(validateProfileScope(scope));
33
+ },
34
+ };
35
+ }
@@ -16,6 +16,8 @@ export declare const BROWSER_EXIT: {
16
16
  export type BrowserCommandDeps = {
17
17
  log: (line: string) => void;
18
18
  error: (line: string) => void;
19
+ /** Owner scope of the command; the CLI runs as the local owner when unset. */
20
+ scope?: string;
19
21
  store: ProfileStore;
20
22
  host: BrowserHost;
21
23
  locks: ProfileLock;
@@ -29,7 +31,7 @@ export type BrowserCommandDeps = {
29
31
  /** True when a daemon session for this env is open. */
30
32
  sessionOpen: (env: BrowserEnv) => boolean;
31
33
  /** profile name -> ISO time of last recorded use (from the audit log). */
32
- auditLastUsed: () => Promise<Record<string, string>>;
34
+ auditLastUsed: (scope: string) => Promise<Record<string, string>>;
33
35
  confirm: (question: string) => Promise<boolean>;
34
36
  interactive: boolean;
35
37
  signal?: AbortSignal;
@@ -6,13 +6,13 @@ import { createInterface } from "node:readline";
6
6
  import { localAuditLogPath } from "../browser/auditSink.js";
7
7
  import { closeBrowserSession, defaultBrowserRunner } from "../browser/browserTeardown.js";
8
8
  import { defaultDisplayProbe, noScreenError } from "../browser/humanLogin.js";
9
+ import { hostLaunchOptions } from "../browser/stageBrowserEnv.js";
9
10
  import { createLocalBrowserHost } from "../browser/localBrowserHost.js";
10
11
  import { createLocalProfileLock } from "../browser/localProfileLock.js";
11
12
  import { createLocalProfileStore } from "../browser/localProfileStore.js";
12
13
  import { matchLoginState, runLoginCheck } from "../browser/loginCheck.js";
13
- // TODO(multi-tenant): every `sf browser` command below uses the fixed local scope.
14
- // In a hosted service the scope must come from the signed-in user. Search for LOCAL_BROWSER_SCOPE in this file.
15
- import { InvalidProfileKeyError, LOCAL_BROWSER_SCOPE, validateProfileName, } from "../browser/profileStore.js";
14
+ import { localOwnerScope } from "../browser/ownerScope.js";
15
+ import { InvalidProfileKeyError, validateProfileName, } from "../browser/profileStore.js";
16
16
  import { cliRunLive, createRunLiveness } from "../browser/runLiveness.js";
17
17
  import { loadHostConfig } from "../config/hostConfig.js";
18
18
  import { BlockedSiteError, hostOf, isBlockedHost } from "../browser/sitePolicy.js";
@@ -41,6 +41,9 @@ const DEFAULT_LOGIN_TIMEOUT_SEC = 300;
41
41
  const LOGIN_POLL_MS = 1000;
42
42
  const LOGIN_CLOSED_AFTER_FAILED_READS = 3;
43
43
  const STAGE_ID = "browser-cli";
44
+ function scopeOf(deps) {
45
+ return deps.scope ?? localOwnerScope();
46
+ }
44
47
  class CliError extends Error {
45
48
  code;
46
49
  exit;
@@ -88,7 +91,7 @@ function defaultSessionOpen(env) {
88
91
  return false;
89
92
  }
90
93
  }
91
- async function defaultAuditLastUsed() {
94
+ async function defaultAuditLastUsed(scope) {
92
95
  const out = {};
93
96
  let text;
94
97
  try {
@@ -103,7 +106,7 @@ async function defaultAuditLastUsed() {
103
106
  try {
104
107
  const rec = JSON.parse(line);
105
108
  if (rec.event === "profile_used" &&
106
- rec.scope === LOCAL_BROWSER_SCOPE &&
109
+ rec.scope === scope &&
107
110
  typeof rec.profile === "string" &&
108
111
  typeof rec.at === "string" &&
109
112
  (out[rec.profile] === undefined || rec.at > out[rec.profile])) {
@@ -132,7 +135,7 @@ function defaultDeps() {
132
135
  log: (line) => console.log(line),
133
136
  error: (line) => console.error(line),
134
137
  store: createLocalProfileStore(),
135
- host: createLocalBrowserHost(),
138
+ host: createLocalBrowserHost(hostLaunchOptions()),
136
139
  locks: createLocalProfileLock({ isRunLive }),
137
140
  runner: defaultBrowserRunner,
138
141
  display: () => defaultDisplayProbe(),
@@ -201,17 +204,17 @@ function newOwner() {
201
204
  return { runId: `cli-${process.pid}-${randomBytes(4).toString("hex")}`, stageId: STAGE_ID };
202
205
  }
203
206
  async function withProfile(deps, name, fn, options) {
204
- const names = await deps.store.list(LOCAL_BROWSER_SCOPE);
207
+ const names = await deps.store.list(scopeOf(deps));
205
208
  if (!options.create && !names.includes(name)) {
206
209
  throw new CliError(`Profile "${name}" does not exist. Create it with sf browser login.`, "not_found", BROWSER_EXIT.error);
207
210
  }
208
211
  const owner = newOwner();
209
- const got = await deps.locks.acquire({ scope: LOCAL_BROWSER_SCOPE, name }, owner);
212
+ const got = await deps.locks.acquire({ scope: scopeOf(deps), name }, owner);
210
213
  if (got.status === "queued") {
211
214
  throw new CliError(`Profile "${name}" is in use by run ${got.holder.runId}${got.holder.stageId !== undefined ? ` stage ${got.holder.stageId}` : ""}. Try again when it finishes.`, "profile_busy", BROWSER_EXIT.busy);
212
215
  }
213
216
  try {
214
- const handle = await deps.store.open({ scope: LOCAL_BROWSER_SCOPE, name });
217
+ const handle = await deps.store.open({ scope: scopeOf(deps), name });
215
218
  return await fn(handle);
216
219
  }
217
220
  finally {
@@ -240,16 +243,18 @@ function closeOpts(deps) {
240
243
  };
241
244
  }
242
245
  async function profilesCmd(deps, p) {
243
- const names = await deps.store.list(LOCAL_BROWSER_SCOPE);
244
- const audited = await deps.auditLastUsed().catch(() => ({}));
246
+ const names = await deps.store.list(scopeOf(deps));
247
+ const audited = await deps.auditLastUsed(scopeOf(deps)).catch(() => ({}));
245
248
  const profiles = [];
246
249
  for (const name of names) {
247
250
  let last = audited[name];
248
251
  try {
249
- const handle = await deps.store.open({ scope: LOCAL_BROWSER_SCOPE, name });
250
- const iso = (await stat(handle.profileDir)).mtime.toISOString();
251
- if (last === undefined || iso > last)
252
- last = iso;
252
+ const handle = await deps.store.open({ scope: scopeOf(deps), name });
253
+ if (handle.profileDir !== undefined) {
254
+ const iso = (await stat(handle.profileDir)).mtime.toISOString();
255
+ if (last === undefined || iso > last)
256
+ last = iso;
257
+ }
253
258
  }
254
259
  catch {
255
260
  // store without a real folder
@@ -263,9 +268,9 @@ async function profilesCmd(deps, p) {
263
268
  }
264
269
  async function statusCmd(deps, p) {
265
270
  const name = requireName(p);
266
- const exists = (await deps.store.list(LOCAL_BROWSER_SCOPE)).includes(name);
271
+ const exists = (await deps.store.list(scopeOf(deps))).includes(name);
267
272
  let lock = null;
268
- const holder = await deps.locks.holder({ scope: LOCAL_BROWSER_SCOPE, name });
273
+ const holder = await deps.locks.holder({ scope: scopeOf(deps), name });
269
274
  if (holder !== undefined) {
270
275
  lock = {
271
276
  run_id: holder.runId,
@@ -275,7 +280,7 @@ async function statusCmd(deps, p) {
275
280
  }
276
281
  let sessionOpen = false;
277
282
  if (exists) {
278
- const handle = await deps.store.open({ scope: LOCAL_BROWSER_SCOPE, name });
283
+ const handle = await deps.store.open({ scope: scopeOf(deps), name });
279
284
  sessionOpen = deps.sessionOpen(await envFor(deps, handle, { profile: name }));
280
285
  }
281
286
  const doc = { name, exists, locked: lock !== null, lock, session_open: sessionOpen };
@@ -403,7 +408,7 @@ async function loginCmd(deps, p) {
403
408
  }
404
409
  async function clearCmd(deps, p) {
405
410
  const name = requireName(p);
406
- if (!(await deps.store.list(LOCAL_BROWSER_SCOPE)).includes(name)) {
411
+ if (!(await deps.store.list(scopeOf(deps))).includes(name)) {
407
412
  throw new CliError(`Profile "${name}" does not exist.`, "not_found", BROWSER_EXIT.error);
408
413
  }
409
414
  if (!p.yes) {
@@ -418,7 +423,7 @@ async function clearCmd(deps, p) {
418
423
  return withProfile(deps, name, async (handle) => {
419
424
  const env = await envFor(deps, handle, { profile: name });
420
425
  await closeBrowserSession(env, closeOpts(deps)).catch(() => undefined);
421
- await deps.store.delete({ scope: LOCAL_BROWSER_SCOPE, name });
426
+ await deps.store.delete({ scope: scopeOf(deps), name });
422
427
  emit(deps, p.json, { cleared: true, name }, [`Cleared profile "${name}".`]);
423
428
  return BROWSER_EXIT.ok;
424
429
  }, { create: false });
@@ -0,0 +1,7 @@
1
+ import type { RunStore } from "../runstore/port.js";
2
+ export type ResolvedCliProjectRoot = {
3
+ rootPath: string;
4
+ wireId: string;
5
+ };
6
+ export declare function resolveCliProjectRootLocal(flag: string): ResolvedCliProjectRoot | null;
7
+ export declare function resolveCliProjectRoot(flag: string, store: RunStore): Promise<ResolvedCliProjectRoot>;
@@ -0,0 +1,27 @@
1
+ import path from "node:path";
2
+ import { findCatalogRoot, resolveCatalogRoots } from "../config/resolveCatalogRoots.js";
3
+ import { WORKSPACE_CATALOG_ID, workspaceCatalogPath, } from "../project/workspaceCatalog.js";
4
+ export function resolveCliProjectRootLocal(flag) {
5
+ const trimmed = flag.trim();
6
+ if (trimmed.length === 0)
7
+ return null;
8
+ if (trimmed === WORKSPACE_CATALOG_ID) {
9
+ return { rootPath: workspaceCatalogPath(), wireId: WORKSPACE_CATALOG_ID };
10
+ }
11
+ if (path.isAbsolute(trimmed)) {
12
+ const resolved = path.resolve(trimmed);
13
+ return { rootPath: resolved, wireId: resolved };
14
+ }
15
+ return null;
16
+ }
17
+ export async function resolveCliProjectRoot(flag, store) {
18
+ const local = resolveCliProjectRootLocal(flag);
19
+ if (local)
20
+ return local;
21
+ const roots = await resolveCatalogRoots({ store });
22
+ const found = findCatalogRoot(roots, flag.trim());
23
+ if (found === undefined) {
24
+ throw new Error(`Unknown project_root: ${flag.trim()}`);
25
+ }
26
+ return { rootPath: found.path, wireId: found.project_root };
27
+ }
@@ -3,7 +3,7 @@ import type { StartRunResult } from "../runtime/runManager.js";
3
3
  import type { TaskFile } from "../types/task.js";
4
4
  import { type EnsureGlobalServiceResult } from "../server/ensureGlobalService.js";
5
5
  import { type CliRunReportIo } from "./runOutput.js";
6
- export declare const RUN_USAGE = "Usage:\n sf run --task <path> --pipeline <path> [--checkout <path>] [--repository <owner/repo>] [--ref <ref>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]";
6
+ export declare const RUN_USAGE = "Usage:\n sf run --task <path> --pipeline <path> [--project-root <id-or-path>] [--checkout <path>] [--repository <owner/repo>] [--ref <ref>] [--json] [--include stages] [--skip-gates] [--git-sha <sha>] [--ci-pr-url <url>] [--ci-job-url <url>] [--operator-cwd <path>] [--operator-agent-dir <path>]";
7
7
  export type RunCommandIo = CliRunReportIo;
8
8
  export type StartRunFn = (input: {
9
9
  task: string | TaskFile;