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.
- package/README.md +4 -3
- package/dist/agent/adapterSpill.d.ts +14 -0
- package/dist/agent/adapterSpill.js +83 -0
- package/dist/agent/agentBackend.d.ts +2 -2
- package/dist/agent/agentBackend.js +2 -2
- package/dist/agent/claudeAdapter.d.ts +13 -2
- package/dist/agent/claudeAdapter.js +50 -6
- package/dist/agent/cursorProvider.d.ts +3 -0
- package/dist/agent/cursorProvider.js +23 -6
- package/dist/agent/fakeAgent.d.ts +3 -3
- package/dist/agent/fakeAgent.js +0 -6
- package/dist/agent/piAdapter.d.ts +2 -2
- package/dist/agent/piAdapter.js +116 -15
- package/dist/agent/piIsolatedMcp.d.ts +1 -0
- package/dist/agent/piIsolatedMcp.js +19 -1
- package/dist/agent/piIsolatedMcpProbe.d.ts +11 -0
- package/dist/agent/piIsolatedMcpProbe.js +20 -0
- package/dist/agent/port.d.ts +2 -2
- package/dist/agent/providerAuth.d.ts +6 -0
- package/dist/agent/providerAuth.js +86 -17
- package/dist/agent/{resolveAgentPort.d.ts → resolveStagePort.d.ts} +8 -6
- package/dist/agent/{resolveAgentPort.js → resolveStagePort.js} +13 -9
- package/dist/agent/stageTurnDriver.d.ts +33 -0
- package/dist/agent/stageTurnDriver.js +64 -0
- package/dist/agent/toolOutputBudget.d.ts +46 -0
- package/dist/agent/toolOutputBudget.js +97 -0
- package/dist/agent/toolOutputBudgetExtension.d.ts +22 -0
- package/dist/agent/toolOutputBudgetExtension.js +141 -0
- package/dist/agent/toolOutputSpill.d.ts +34 -0
- package/dist/agent/toolOutputSpill.js +85 -0
- package/dist/browser/agentBrowserLiveViewRelay.d.ts +41 -0
- package/dist/browser/agentBrowserLiveViewRelay.js +801 -0
- package/dist/browser/anchor.d.ts +2 -0
- package/dist/browser/anchor.js +2 -1
- package/dist/browser/auditSink.d.ts +8 -0
- package/dist/browser/browserHost.d.ts +29 -0
- package/dist/browser/browserSweep.d.ts +4 -1
- package/dist/browser/browserSweep.js +7 -0
- package/dist/browser/browserTeardown.d.ts +1 -0
- package/dist/browser/browserTeardown.js +32 -2
- package/dist/browser/containerBrowserEndpoint.d.ts +11 -0
- package/dist/browser/containerBrowserEndpoint.js +57 -0
- package/dist/browser/containerBrowserHost.d.ts +24 -0
- package/dist/browser/containerBrowserHost.js +147 -0
- package/dist/browser/dockerSandboxOrchestrator.d.ts +26 -0
- package/dist/browser/dockerSandboxOrchestrator.js +142 -0
- package/dist/browser/fakeLiveViewRelay.d.ts +15 -0
- package/dist/browser/fakeLiveViewRelay.js +94 -0
- package/dist/browser/fakeLiveViewSource.d.ts +6 -0
- package/dist/browser/fakeLiveViewSource.js +16 -0
- package/dist/browser/fakeRemoteBrowserHost.d.ts +2 -1
- package/dist/browser/fakeRemoteBrowserHost.js +2 -1
- package/dist/browser/fakeSandboxOrchestrator.d.ts +9 -0
- package/dist/browser/fakeSandboxOrchestrator.js +44 -0
- package/dist/browser/gateHandoff.d.ts +0 -2
- package/dist/browser/gateHandoff.js +5 -11
- package/dist/browser/hostCapabilities.d.ts +23 -0
- package/dist/browser/hostCapabilities.js +23 -0
- package/dist/browser/humanLogin.d.ts +2 -2
- package/dist/browser/humanLogin.js +11 -2
- package/dist/browser/liveViewPersisted.d.ts +11 -0
- package/dist/browser/liveViewPersisted.js +22 -0
- package/dist/browser/liveViewRelay.d.ts +76 -0
- package/dist/browser/liveViewRelay.js +1 -0
- package/dist/browser/liveViewSessions.d.ts +25 -0
- package/dist/browser/liveViewSessions.js +139 -0
- package/dist/browser/liveViewSource.d.ts +25 -0
- package/dist/browser/liveViewSource.js +7 -0
- package/dist/browser/liveViewTickets.d.ts +46 -0
- package/dist/browser/liveViewTickets.js +122 -0
- package/dist/browser/localBrowserHost.d.ts +10 -0
- package/dist/browser/localBrowserHost.js +53 -7
- package/dist/browser/ownerScope.d.ts +10 -0
- package/dist/browser/ownerScope.js +6 -0
- package/dist/browser/profileStore.d.ts +5 -2
- package/dist/browser/profileStore.js +3 -5
- package/dist/browser/sandboxOrchestrator.d.ts +60 -0
- package/dist/browser/sandboxOrchestrator.js +8 -0
- package/dist/browser/sessionApiSandboxOrchestrator.d.ts +34 -0
- package/dist/browser/sessionApiSandboxOrchestrator.js +108 -0
- package/dist/browser/stageBrowserEnv.d.ts +7 -0
- package/dist/browser/stageBrowserEnv.js +53 -15
- package/dist/browser/stageHandoff.d.ts +47 -0
- package/dist/browser/stageHandoff.js +101 -0
- package/dist/browser/stageProfileLock.d.ts +1 -0
- package/dist/browser/stageProfileLock.js +1 -3
- package/dist/browser/volumeProfileStore.d.ts +7 -0
- package/dist/browser/volumeProfileStore.js +35 -0
- package/dist/cli/browserCommand.d.ts +3 -1
- package/dist/cli/browserCommand.js +25 -20
- package/dist/cli/resolveCliProjectRoot.d.ts +7 -0
- package/dist/cli/resolveCliProjectRoot.js +27 -0
- package/dist/cli/runCommand.d.ts +1 -1
- package/dist/cli/runCommand.js +59 -17
- package/dist/cli/skillsCommand.d.ts +20 -0
- package/dist/cli/skillsCommand.js +74 -31
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +22 -4
- package/dist/cli.js +4 -4
- package/dist/config/browseCatalog.d.ts +2 -0
- package/dist/config/browseCatalog.js +6 -0
- package/dist/config/catalogRelativePath.d.ts +4 -0
- package/dist/config/catalogRelativePath.js +37 -9
- package/dist/config/createStage.d.ts +1 -0
- package/dist/config/createStage.js +11 -2
- package/dist/config/createTask.d.ts +30 -0
- package/dist/config/createTask.js +161 -0
- package/dist/config/createTrigger.d.ts +15 -0
- package/dist/config/createTrigger.js +32 -25
- package/dist/config/draftPackage.d.ts +41 -0
- package/dist/config/draftPackage.js +216 -23
- package/dist/config/ensureCatalogScanRoot.d.ts +2 -1
- package/dist/config/ensureCatalogScanRoot.js +6 -2
- package/dist/config/findTaskInCatalog.d.ts +33 -0
- package/dist/config/findTaskInCatalog.js +69 -0
- package/dist/config/hostConfig.d.ts +21 -0
- package/dist/config/hostConfig.js +113 -1
- package/dist/config/importTask.d.ts +19 -0
- package/dist/config/importTask.js +133 -0
- package/dist/config/installSkillRequest.d.ts +40 -0
- package/dist/config/installSkillRequest.js +286 -0
- package/dist/config/listConfig.d.ts +0 -16
- package/dist/config/listConfig.js +0 -3
- package/dist/config/loadOutcome.d.ts +11 -6
- package/dist/config/loadPipeline.js +0 -1
- package/dist/config/loadStage.js +2 -0
- package/dist/config/loadStageflowManifest.js +4 -1
- package/dist/config/loadTask.js +2 -0
- package/dist/config/loadTrigger.js +2 -0
- package/dist/config/mergePipelineIncludes.js +3 -0
- package/dist/config/normalizePipelineStageEntry.js +10 -1
- package/dist/config/printTargetYaml.js +0 -1
- package/dist/config/publishDraftPackage.d.ts +5 -1
- package/dist/config/publishDraftPackage.js +4 -1
- package/dist/config/resolveCatalogRoots.d.ts +4 -1
- package/dist/config/resolveCatalogRoots.js +18 -1
- package/dist/config/skillUsageIndex.d.ts +6 -0
- package/dist/config/skillUsageIndex.js +39 -0
- package/dist/config/stageBrowser.js +13 -0
- package/dist/config/triggerAdapterStatus.d.ts +11 -0
- package/dist/config/triggerAdapterStatus.js +90 -0
- package/dist/config/triggerCatalog.d.ts +41 -2
- package/dist/config/triggerCatalog.js +107 -15
- package/dist/config/updateTask.d.ts +30 -0
- package/dist/config/updateTask.js +119 -0
- package/dist/config/updateTrigger.d.ts +38 -0
- package/dist/config/updateTrigger.js +220 -0
- package/dist/config/validateCatalog.d.ts +6 -2
- package/dist/config/validateCatalog.js +48 -7
- package/dist/config/workshopCatalogFilenames.d.ts +37 -0
- package/dist/config/workshopCatalogFilenames.js +125 -0
- package/dist/config/workshopCatalogRepair.d.ts +7 -0
- package/dist/config/workshopCatalogRepair.js +144 -0
- package/dist/config/yamlParsePosition.d.ts +4 -0
- package/dist/config/yamlParsePosition.js +9 -0
- package/dist/hosted/containment.d.ts +3 -0
- package/dist/hosted/containment.js +36 -0
- package/dist/hosted/errors.d.ts +21 -0
- package/dist/hosted/errors.js +30 -0
- package/dist/hosted/packageSkills.d.ts +3 -0
- package/dist/hosted/packageSkills.js +80 -0
- package/dist/hosted/runtime.d.ts +12 -0
- package/dist/hosted/runtime.js +596 -0
- package/dist/hosted/sequencer.d.ts +28 -0
- package/dist/hosted/sequencer.js +129 -0
- package/dist/hosted/translate.d.ts +3 -0
- package/dist/hosted/translate.js +43 -0
- package/dist/hosted/types.d.ts +108 -0
- package/dist/hosted/types.js +10 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +6 -0
- package/dist/mcp/getStartedTool.js +15 -6
- package/dist/mcp/triggerTools.js +2 -8
- package/dist/operatorAgent/agentPort.d.ts +11 -0
- package/dist/operatorAgent/agentPort.js +12 -0
- package/dist/operatorAgent/index.d.ts +1 -0
- package/dist/operatorAgent/index.js +1 -0
- package/dist/operatorAgent/piModel.js +26 -6
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.d.ts +1 -1
- package/dist/operatorAgent/profiles/workshopAuthorPlaybook.js +4 -2
- package/dist/operatorAgent/profiles/workshopAuthorTools.js +103 -16
- package/dist/package-meta.d.ts +1 -1
- package/dist/package-meta.js +1 -1
- package/dist/project/ensureProjectRootsFromEnv.d.ts +3 -0
- package/dist/project/ensureProjectRootsFromEnv.js +22 -0
- package/dist/project/globalHome.d.ts +1 -0
- package/dist/project/globalHome.js +18 -0
- package/dist/project/workspaceCatalog.d.ts +10 -0
- package/dist/project/workspaceCatalog.js +91 -0
- package/dist/projection/projectRun.d.ts +0 -1
- package/dist/projection/projectRun.js +0 -1
- package/dist/runstore/backup.js +14 -4
- package/dist/runstore/port.d.ts +11 -1
- package/dist/runstore/restore.js +5 -2
- package/dist/runstore/sqlite/SqliteRunStore.d.ts +8 -0
- package/dist/runstore/sqlite/SqliteRunStore.js +29 -9
- package/dist/runstore/sqlite/migrations/012-trigger-fires.d.ts +7 -0
- package/dist/runstore/sqlite/migrations/012-trigger-fires.js +18 -0
- package/dist/runstore/sqlite/migrations/index.js +2 -0
- package/dist/runstore/stageSnapshot.d.ts +3 -1
- package/dist/runstore/stageSnapshot.js +19 -0
- package/dist/runstore/workspaceLayout.js +27 -0
- package/dist/runstore/writeContainedArtifact.d.ts +1 -0
- package/dist/runstore/writeContainedArtifact.js +52 -0
- package/dist/runtime/containerLimits.d.ts +0 -3
- package/dist/runtime/containerLimits.js +0 -5
- package/dist/runtime/credentialBinding.d.ts +3 -0
- package/dist/runtime/credentialBinding.js +23 -6
- package/dist/runtime/emailSource.js +20 -28
- package/dist/runtime/githubPollSource.js +20 -28
- package/dist/runtime/pipelineRunner.d.ts +4 -4
- package/dist/runtime/pipelineScheduler.d.ts +2 -2
- package/dist/runtime/pipelineScheduler.js +3 -0
- package/dist/runtime/resumeReconstruct.d.ts +2 -2
- package/dist/runtime/resumeTimedOut.d.ts +2 -2
- package/dist/runtime/runManager.d.ts +18 -2
- package/dist/runtime/runManager.js +95 -30
- package/dist/runtime/runRetentionSweep.d.ts +0 -2
- package/dist/runtime/runRetentionSweep.js +0 -8
- package/dist/runtime/runRetryCoordinator.d.ts +2 -2
- package/dist/runtime/scheduleSource.js +15 -24
- package/dist/runtime/stageAttemptBootstrap.d.ts +2 -2
- package/dist/runtime/stageAttemptBootstrap.js +10 -1
- package/dist/runtime/stageProcessLauncher.js +2 -2
- package/dist/runtime/stageRoots.js +12 -1
- package/dist/runtime/stageRunner.d.ts +2 -2
- package/dist/runtime/stageRunner.js +8 -2
- package/dist/runtime/stageWorker.js +2 -2
- package/dist/runtime/triggerPort.d.ts +5 -3
- package/dist/runtime/triggerRunner.d.ts +1 -0
- package/dist/runtime/triggerRunner.js +27 -20
- package/dist/runtime/webhookAccept.d.ts +25 -0
- package/dist/runtime/webhookAccept.js +56 -0
- package/dist/server/bootstrap.d.ts +7 -2
- package/dist/server/bootstrap.js +15 -1
- package/dist/server/controlToken.d.ts +1 -1
- package/dist/server/controlToken.js +18 -2
- package/dist/server/healthSurfaces.d.ts +12 -0
- package/dist/server/healthSurfaces.js +27 -6
- package/dist/server/http.d.ts +19 -10
- package/dist/server/http.js +605 -85
- package/dist/server/liveViewPath.d.ts +9 -0
- package/dist/server/liveViewPath.js +8 -0
- package/dist/server/liveViewRoutes.d.ts +58 -0
- package/dist/server/liveViewRoutes.js +565 -0
- package/dist/server/mcpHost.d.ts +4 -2
- package/dist/server/mcpHost.js +2 -0
- package/dist/server/readCatalogFile.d.ts +29 -0
- package/dist/server/readCatalogFile.js +215 -0
- package/dist/server/triggerRoutes.d.ts +1 -0
- package/dist/server/triggerRoutes.js +200 -73
- package/dist/tools/readEmail.d.ts +3 -3
- package/dist/tools/writeStageArtifact.d.ts +9 -0
- package/dist/tools/writeStageArtifact.js +2 -47
- package/dist/types/pipeline.d.ts +1 -1
- package/dist/types/stage.d.ts +1 -1
- package/dist/types/stageflowManifest.d.ts +1 -1
- package/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/dist/ui/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/dist/ui/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/dist/ui/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/dist/ui/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/dist/ui/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/dist/ui/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/dist/ui/assets/index-BoFPkKFg.js +256 -0
- package/dist/ui/assets/{index-Bv4p1aPK.css → index-CIPbbBUi.css} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/workshop/autosave.d.ts +2 -2
- package/dist/workshop/autosave.js +4 -6
- package/dist/workshop/buildStore.d.ts +2 -0
- package/dist/workshop/chatAttachments.d.ts +27 -0
- package/dist/workshop/chatAttachments.js +98 -0
- package/dist/workshop/chatTurn.d.ts +5 -2
- package/dist/workshop/chatTurn.js +22 -18
- package/dist/workshop/sessionMutations.d.ts +17 -0
- package/dist/workshop/sessionMutations.js +107 -0
- package/dist/workshop/sessionStore.d.ts +10 -0
- package/dist/workshop/sessionStore.js +52 -0
- package/dist/workshop/workshopPin.d.ts +4 -3
- package/dist/workshop/workshopPin.js +26 -6
- package/dist/workshop/workshopTranscriptTurn.d.ts +8 -0
- package/dist/workshop/workshopTranscriptTurn.js +46 -0
- package/examples/README.md +1 -1
- package/examples/archify-on-pr/README.md +1 -1
- package/examples/archify-on-pr/archify-on-pr.pipeline.yaml +0 -1
- package/examples/archify-on-pr/author-diagrams.yaml +1 -0
- package/examples/conditional-fork/branch-a.yaml +1 -1
- package/examples/conditional-fork/decide.yaml +2 -1
- package/examples/feature-loop/{implement.yaml → feature-implement.yaml} +1 -1
- package/examples/feature-loop/feature-loop.pipeline.yaml +10 -10
- package/examples/feature-loop/{plan.yaml → feature-plan.yaml} +1 -1
- package/examples/feature-loop/{review.yaml → feature-review.yaml} +1 -1
- package/examples/feedback-loop/{implement.yaml → feedback-implement.yaml} +1 -1
- package/examples/feedback-loop/feedback-loop-wait-human.pipeline.yaml +9 -9
- package/examples/feedback-loop/feedback-loop.pipeline.yaml +9 -9
- package/examples/feedback-loop/{plan.yaml → feedback-plan.yaml} +1 -1
- package/examples/feedback-loop/{review.yaml → feedback-review.yaml} +1 -1
- package/examples/generic-fan-in/{research.yaml → fan-in-research.yaml} +1 -1
- package/examples/generic-fan-in/generic-fan-in-accepted.pipeline.yaml +3 -3
- package/examples/generic-fan-in/generic-fan-in.pipeline.yaml +3 -3
- package/examples/hello-world/{research.yaml → hello-research.yaml} +1 -1
- package/examples/hello-world/hello.pipeline.yaml +2 -2
- package/examples/mcp-hitl-tour/{review.yaml → mcp-hitl-review.yaml} +1 -1
- package/examples/mcp-hitl-tour/mcp-hitl-tour.pipeline.yaml +3 -3
- package/examples/oss-issue-contribution/oss-explain-fix.yaml +1 -0
- package/examples/oss-issue-contribution/oss-explain-issue.yaml +1 -0
- package/examples/oss-issue-contribution/oss-issue-contribution.pipeline.yaml +0 -2
- package/examples/stageflow.yaml +6 -0
- package/examples/triggers/sample-github-pr.trigger.yaml +12 -0
- package/examples/triggers/sample-inbox-email.trigger.yaml +14 -0
- package/examples/triggers/sample-manual-hello.trigger.yaml +5 -0
- package/examples/triggers/sample-nightly-hello.trigger.yaml +8 -0
- package/examples/triggers/sample-signed-webhook.trigger.yaml +11 -0
- package/examples/triggers-readiness/README.md +48 -0
- package/examples/triggers-readiness/readiness-emit.yaml +18 -0
- package/examples/triggers-readiness/readiness.pipeline.yaml +4 -0
- package/examples/triggers-readiness/readiness.task.yaml +6 -0
- package/examples/triggers-readiness/stageflow.yaml +13 -0
- package/examples/triggers-readiness/triggers/readiness-dynamic-manual.trigger.yaml +4 -0
- package/examples/triggers-readiness/triggers/readiness-email.trigger.yaml +12 -0
- package/examples/triggers-readiness/triggers/readiness-github-pr.trigger.yaml +12 -0
- package/examples/triggers-readiness/triggers/readiness-manual.trigger.yaml +5 -0
- package/examples/triggers-readiness/triggers/readiness-schedule.trigger.yaml +8 -0
- package/examples/triggers-readiness/triggers/readiness-webhook.trigger.yaml +11 -0
- package/package.json +9 -1
- package/skills/stageflow/references/control-surface.md +1 -1
- package/skills/stageflow-author/references/catalog-write-conventions.md +2 -2
- package/skills/stageflow-run/SKILL.md +1 -1
- package/skills/stageflow-run/references/mcp-call.md +1 -1
- package/skills/stageflow-setup/SKILL.md +1 -1
- package/dist/ui/assets/index-59cjF0OA.js +0 -133
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type LiveViewMode = "view" | "control";
|
|
2
|
+
export type LiveViewTarget = {
|
|
3
|
+
scope: string;
|
|
4
|
+
runId: string;
|
|
5
|
+
stageId: string;
|
|
6
|
+
};
|
|
7
|
+
export type LiveViewGrant = LiveViewTarget & {
|
|
8
|
+
mode: LiveViewMode;
|
|
9
|
+
};
|
|
10
|
+
export type LiveViewTicketRequest = LiveViewGrant & {
|
|
11
|
+
callerId?: string | null;
|
|
12
|
+
};
|
|
13
|
+
export type LiveViewCredential = LiveViewGrant & {
|
|
14
|
+
credential: string;
|
|
15
|
+
callerId: string | null;
|
|
16
|
+
};
|
|
17
|
+
export interface LiveViewTicketService {
|
|
18
|
+
/** One-use, short-lived; bound to scope, run, stage and mode. */
|
|
19
|
+
issue(request: LiveViewTicketRequest): {
|
|
20
|
+
ticket: string;
|
|
21
|
+
expiresAt: number;
|
|
22
|
+
};
|
|
23
|
+
/** Spends the ticket whatever the outcome; refused when unknown, expired, reused or for another target. */
|
|
24
|
+
redeem(ticket: string, target: LiveViewTarget): LiveViewCredential | undefined;
|
|
25
|
+
/** Looks up a live-view credential for the same target; undefined when unknown, expired or revoked. */
|
|
26
|
+
resolve(credential: string, target: LiveViewTarget): LiveViewCredential | undefined;
|
|
27
|
+
/** Invalidates tickets and credentials of one stage; `mode` limits it to that mode. */
|
|
28
|
+
revoke(runId: string, stageId: string, mode?: LiveViewMode): void;
|
|
29
|
+
/** Whether a live credential of the stage exists. */
|
|
30
|
+
hasCredentials(runId: string, stageId: string): boolean;
|
|
31
|
+
/** Invalidates tickets and credentials of every stage of a run. */
|
|
32
|
+
revokeRun(runId: string): void;
|
|
33
|
+
/** Invalidates every ticket and credential. */
|
|
34
|
+
revokeAll(): void;
|
|
35
|
+
/** Listeners receive the credentials that were just invalidated. */
|
|
36
|
+
onRevoked(listener: (credentials: readonly string[]) => void): () => void;
|
|
37
|
+
}
|
|
38
|
+
export declare const LIVE_VIEW_TICKET_TTL_MS = 60000;
|
|
39
|
+
export declare const LIVE_VIEW_CREDENTIAL_TTL_MS: number;
|
|
40
|
+
export type LiveViewTicketServiceOptions = {
|
|
41
|
+
now?: () => number;
|
|
42
|
+
randomToken?: () => string;
|
|
43
|
+
ticketTtlMs?: number;
|
|
44
|
+
credentialTtlMs?: number;
|
|
45
|
+
};
|
|
46
|
+
export declare function createLiveViewTicketService(options?: LiveViewTicketServiceOptions): LiveViewTicketService;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
export const LIVE_VIEW_TICKET_TTL_MS = 60_000;
|
|
3
|
+
export const LIVE_VIEW_CREDENTIAL_TTL_MS = 24 * 60 * 60_000;
|
|
4
|
+
function sameTarget(a, b) {
|
|
5
|
+
return a.scope === b.scope && a.runId === b.runId && a.stageId === b.stageId;
|
|
6
|
+
}
|
|
7
|
+
export function createLiveViewTicketService(options = {}) {
|
|
8
|
+
const now = options.now ?? Date.now;
|
|
9
|
+
const randomToken = options.randomToken ?? (() => randomBytes(32).toString("base64url"));
|
|
10
|
+
const ticketTtlMs = options.ticketTtlMs ?? LIVE_VIEW_TICKET_TTL_MS;
|
|
11
|
+
const credentialTtlMs = options.credentialTtlMs ?? LIVE_VIEW_CREDENTIAL_TTL_MS;
|
|
12
|
+
const tickets = new Map();
|
|
13
|
+
const credentials = new Map();
|
|
14
|
+
const listeners = new Set();
|
|
15
|
+
function sweep() {
|
|
16
|
+
const at = now();
|
|
17
|
+
for (const [key, entry] of tickets)
|
|
18
|
+
if (entry.expiresAt <= at)
|
|
19
|
+
tickets.delete(key);
|
|
20
|
+
for (const [key, entry] of credentials)
|
|
21
|
+
if (entry.expiresAt <= at)
|
|
22
|
+
credentials.delete(key);
|
|
23
|
+
}
|
|
24
|
+
function revokeWhere(matches) {
|
|
25
|
+
for (const [key, entry] of tickets)
|
|
26
|
+
if (matches(entry))
|
|
27
|
+
tickets.delete(key);
|
|
28
|
+
const revoked = [];
|
|
29
|
+
for (const [key, entry] of credentials) {
|
|
30
|
+
if (!matches(entry))
|
|
31
|
+
continue;
|
|
32
|
+
credentials.delete(key);
|
|
33
|
+
revoked.push(key);
|
|
34
|
+
}
|
|
35
|
+
if (revoked.length === 0)
|
|
36
|
+
return;
|
|
37
|
+
for (const listener of [...listeners]) {
|
|
38
|
+
try {
|
|
39
|
+
listener(revoked);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// a failing listener must not block revocation
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
issue(request) {
|
|
48
|
+
sweep();
|
|
49
|
+
const ticket = randomToken();
|
|
50
|
+
const expiresAt = now() + ticketTtlMs;
|
|
51
|
+
tickets.set(ticket, {
|
|
52
|
+
scope: request.scope,
|
|
53
|
+
runId: request.runId,
|
|
54
|
+
stageId: request.stageId,
|
|
55
|
+
mode: request.mode,
|
|
56
|
+
callerId: request.callerId ?? null,
|
|
57
|
+
expiresAt,
|
|
58
|
+
});
|
|
59
|
+
return { ticket, expiresAt };
|
|
60
|
+
},
|
|
61
|
+
redeem(ticket, target) {
|
|
62
|
+
const entry = tickets.get(ticket);
|
|
63
|
+
if (entry === undefined)
|
|
64
|
+
return undefined;
|
|
65
|
+
tickets.delete(ticket);
|
|
66
|
+
if (entry.expiresAt <= now() || !sameTarget(entry, target))
|
|
67
|
+
return undefined;
|
|
68
|
+
const credential = randomToken();
|
|
69
|
+
credentials.set(credential, { ...entry, expiresAt: now() + credentialTtlMs });
|
|
70
|
+
return {
|
|
71
|
+
credential,
|
|
72
|
+
scope: entry.scope,
|
|
73
|
+
runId: entry.runId,
|
|
74
|
+
stageId: entry.stageId,
|
|
75
|
+
mode: entry.mode,
|
|
76
|
+
callerId: entry.callerId,
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
resolve(credential, target) {
|
|
80
|
+
const entry = credentials.get(credential);
|
|
81
|
+
if (entry === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (entry.expiresAt <= now()) {
|
|
84
|
+
credentials.delete(credential);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
if (!sameTarget(entry, target))
|
|
88
|
+
return undefined;
|
|
89
|
+
return {
|
|
90
|
+
credential,
|
|
91
|
+
scope: entry.scope,
|
|
92
|
+
runId: entry.runId,
|
|
93
|
+
stageId: entry.stageId,
|
|
94
|
+
mode: entry.mode,
|
|
95
|
+
callerId: entry.callerId,
|
|
96
|
+
};
|
|
97
|
+
},
|
|
98
|
+
revoke(runId, stageId, mode) {
|
|
99
|
+
revokeWhere((entry) => entry.runId === runId && entry.stageId === stageId && (mode === undefined || entry.mode === mode));
|
|
100
|
+
},
|
|
101
|
+
hasCredentials(runId, stageId) {
|
|
102
|
+
sweep();
|
|
103
|
+
for (const entry of credentials.values()) {
|
|
104
|
+
if (entry.runId === runId && entry.stageId === stageId)
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
},
|
|
109
|
+
revokeRun(runId) {
|
|
110
|
+
revokeWhere((entry) => entry.runId === runId);
|
|
111
|
+
},
|
|
112
|
+
revokeAll() {
|
|
113
|
+
revokeWhere(() => true);
|
|
114
|
+
},
|
|
115
|
+
onRevoked(listener) {
|
|
116
|
+
listeners.add(listener);
|
|
117
|
+
return () => {
|
|
118
|
+
listeners.delete(listener);
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
import { type BrowserHost } from "./browserHost.js";
|
|
2
|
+
export declare const DENY_PERMISSION_PROMPTS_ARG = "--deny-permission-prompts";
|
|
2
3
|
export type LocalBrowserHostOptions = {
|
|
3
4
|
platform?: NodeJS.Platform;
|
|
4
5
|
/** Read only for display detection; never for AGENT_BROWSER_* settings. */
|
|
5
6
|
hostEnv?: Record<string, string | undefined>;
|
|
6
7
|
socketRoot?: string;
|
|
7
8
|
emptyConfigPath?: string;
|
|
9
|
+
/** `relay` only when the process also serves the live view routes; default `none`. */
|
|
10
|
+
liveView?: "relay" | "none";
|
|
11
|
+
/** Whether an Xvfb binary is installed; default searches `PATH` for an executable `Xvfb`. */
|
|
12
|
+
xvfbProbe?: () => boolean;
|
|
13
|
+
/** Host config `browser.launch_args`; applied only to sessions that launch Chrome, after the Host's own switches. */
|
|
14
|
+
launchArgs?: readonly string[];
|
|
15
|
+
/** Host config `browser.executable_path`; applied only to sessions that launch Chrome. */
|
|
16
|
+
executablePath?: string;
|
|
8
17
|
};
|
|
18
|
+
export declare function defaultXvfbProbe(platform?: NodeJS.Platform, hostEnv?: Record<string, string | undefined>): boolean;
|
|
9
19
|
export declare function defaultSocketRoot(platform: NodeJS.Platform): string;
|
|
10
20
|
export declare function createLocalBrowserHost(options?: LocalBrowserHostOptions): BrowserHost;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { accessSync, constants } from "node:fs";
|
|
1
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
3
|
import os from "node:os";
|
|
3
4
|
import path from "node:path";
|
|
@@ -5,7 +6,24 @@ import { globalStageflowHome } from "../project/globalHome.js";
|
|
|
5
6
|
import { shortHash, } from "./browserHost.js";
|
|
6
7
|
const MAX_SOCKET_PATH_BYTES = 103;
|
|
7
8
|
const MAX_SESSION_NAME_LENGTH = 48;
|
|
9
|
+
export const DENY_PERMISSION_PROMPTS_ARG = "--deny-permission-prompts";
|
|
10
|
+
const HOST_LAUNCH_ARGS = [DENY_PERMISSION_PROMPTS_ARG];
|
|
8
11
|
const DISPLAY_VARS = ["DISPLAY", "WAYLAND_DISPLAY", "XAUTHORITY"];
|
|
12
|
+
export function defaultXvfbProbe(platform = process.platform, hostEnv = process.env) {
|
|
13
|
+
const dirs = (hostEnv.PATH ?? "").split(platform === "win32" ? ";" : ":");
|
|
14
|
+
for (const dir of dirs) {
|
|
15
|
+
if (dir === "")
|
|
16
|
+
continue;
|
|
17
|
+
try {
|
|
18
|
+
accessSync(path.join(dir, "Xvfb"), constants.X_OK);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
9
27
|
export function defaultSocketRoot(platform) {
|
|
10
28
|
const uid = typeof process.getuid === "function" ? process.getuid() : "u";
|
|
11
29
|
const base = platform === "win32" ? os.tmpdir() : "/tmp";
|
|
@@ -24,14 +42,22 @@ function sessionNameForProfileStage(profile, runId, stageId) {
|
|
|
24
42
|
return `${plain.slice(0, room)}-${suffix}`;
|
|
25
43
|
}
|
|
26
44
|
const OPEN_ANCHOR_TIMEOUT_MS = 60_000;
|
|
27
|
-
function
|
|
45
|
+
function displayKind(platform, hostEnv, xvfbProbe) {
|
|
28
46
|
if (platform !== "linux")
|
|
29
|
-
return
|
|
30
|
-
|
|
47
|
+
return "local_window";
|
|
48
|
+
if (hostEnv.DISPLAY || hostEnv.WAYLAND_DISPLAY)
|
|
49
|
+
return "local_window";
|
|
50
|
+
return xvfbProbe() ? "virtual_display" : "headless_only";
|
|
31
51
|
}
|
|
32
52
|
export function createLocalBrowserHost(options = {}) {
|
|
33
53
|
const platform = options.platform ?? process.platform;
|
|
34
54
|
const hostEnv = options.hostEnv ?? process.env;
|
|
55
|
+
const display = displayKind(platform, hostEnv, options.xvfbProbe ?? (() => defaultXvfbProbe(platform, hostEnv)));
|
|
56
|
+
const launchArgs = [
|
|
57
|
+
...HOST_LAUNCH_ARGS,
|
|
58
|
+
...(options.launchArgs ?? []).filter((arg) => !HOST_LAUNCH_ARGS.includes(arg)),
|
|
59
|
+
];
|
|
60
|
+
const executablePath = options.executablePath;
|
|
35
61
|
async function build(input) {
|
|
36
62
|
const socketRoot = options.socketRoot ?? defaultSocketRoot(platform);
|
|
37
63
|
const socketDir = path.join(socketRoot, shortHash(input.identity, 8));
|
|
@@ -52,7 +78,12 @@ export function createLocalBrowserHost(options = {}) {
|
|
|
52
78
|
AGENT_BROWSER_CONFIG: emptyConfigPath,
|
|
53
79
|
};
|
|
54
80
|
input.set(env);
|
|
55
|
-
if (input.
|
|
81
|
+
if (input.launches) {
|
|
82
|
+
env.AGENT_BROWSER_ARGS = launchArgs.join(",");
|
|
83
|
+
if (executablePath !== undefined)
|
|
84
|
+
env.AGENT_BROWSER_EXECUTABLE_PATH = executablePath;
|
|
85
|
+
}
|
|
86
|
+
if (input.headed && platform === "linux" && display === "local_window") {
|
|
56
87
|
for (const name of DISPLAY_VARS) {
|
|
57
88
|
const value = hostEnv[name];
|
|
58
89
|
if (value)
|
|
@@ -63,16 +94,21 @@ export function createLocalBrowserHost(options = {}) {
|
|
|
63
94
|
}
|
|
64
95
|
function headedFor(request) {
|
|
65
96
|
return (request.humanLogin === true ||
|
|
66
|
-
((request.browser.headed ?? true) &&
|
|
97
|
+
((request.browser.headed ?? true) && display !== "headless_only"));
|
|
67
98
|
}
|
|
68
99
|
async function profileBrowserEnv(request) {
|
|
69
100
|
const { profile } = request;
|
|
101
|
+
const profileDir = profile.profileDir;
|
|
102
|
+
if (profileDir === undefined) {
|
|
103
|
+
throw new Error(`profile "${profile.key.name}" has no folder; the local browser host needs one`);
|
|
104
|
+
}
|
|
70
105
|
return build({
|
|
71
|
-
identity: `profile:${profile.key.scope}/${profile.key.name}:${
|
|
106
|
+
identity: `profile:${profile.key.scope}/${profile.key.name}:${profileDir}`,
|
|
72
107
|
session: sessionNameForProfile(profile.key.scope, profile.key.name),
|
|
73
108
|
headed: headedFor(request),
|
|
109
|
+
launches: true,
|
|
74
110
|
set: (env) => {
|
|
75
|
-
env.AGENT_BROWSER_PROFILE =
|
|
111
|
+
env.AGENT_BROWSER_PROFILE = profileDir;
|
|
76
112
|
},
|
|
77
113
|
});
|
|
78
114
|
}
|
|
@@ -86,7 +122,15 @@ export function createLocalBrowserHost(options = {}) {
|
|
|
86
122
|
}
|
|
87
123
|
return address;
|
|
88
124
|
}
|
|
125
|
+
const capabilities = {
|
|
126
|
+
display,
|
|
127
|
+
liveView: options.liveView ?? "none",
|
|
128
|
+
attach: "host_launched",
|
|
129
|
+
profilePersistence: "host_volume",
|
|
130
|
+
gracefulCloseRequired: true,
|
|
131
|
+
};
|
|
89
132
|
return {
|
|
133
|
+
capabilities,
|
|
90
134
|
async ensureProfileBrowser(request) {
|
|
91
135
|
const wantHeaded = headedFor(request);
|
|
92
136
|
let previous = request.previous;
|
|
@@ -130,6 +174,7 @@ export function createLocalBrowserHost(options = {}) {
|
|
|
130
174
|
identity: `stage:${request.runId}/${request.stageId}`,
|
|
131
175
|
session: sessionNameForProfileStage(profile, request.runId, request.stageId),
|
|
132
176
|
headed: headedFor(request),
|
|
177
|
+
launches: false,
|
|
133
178
|
set: (env) => {
|
|
134
179
|
env.AGENT_BROWSER_CDP = cdp;
|
|
135
180
|
env.AGENT_BROWSER_PIN_TAB = "1";
|
|
@@ -141,6 +186,7 @@ export function createLocalBrowserHost(options = {}) {
|
|
|
141
186
|
identity,
|
|
142
187
|
session: `sf-t-${shortHash(identity, 12)}`,
|
|
143
188
|
headed: headedFor(request),
|
|
189
|
+
launches: true,
|
|
144
190
|
set: (env) => {
|
|
145
191
|
if (browser.allow_domains && browser.allow_domains.length > 0) {
|
|
146
192
|
env.AGENT_BROWSER_ALLOWED_DOMAINS = browser.allow_domains.join(",");
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type RunOwnerRef = {
|
|
2
|
+
runId: string;
|
|
3
|
+
};
|
|
4
|
+
/** Maps a run to the owner scope its profiles, leases, audit records, sandboxes and live view belong to. */
|
|
5
|
+
export type OwnerScopeResolver = (run?: RunOwnerRef) => string;
|
|
6
|
+
/** The only resolver today: every run belongs to the single local owner. */
|
|
7
|
+
export declare const localOwnerScope: OwnerScopeResolver;
|
|
8
|
+
export declare function runOwnerScope(support: {
|
|
9
|
+
ownerScope?: OwnerScopeResolver;
|
|
10
|
+
}, run?: RunOwnerRef): string;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { LOCAL_BROWSER_SCOPE } from "./profileStore.js";
|
|
2
|
+
/** The only resolver today: every run belongs to the single local owner. */
|
|
3
|
+
export const localOwnerScope = () => LOCAL_BROWSER_SCOPE;
|
|
4
|
+
export function runOwnerScope(support, run) {
|
|
5
|
+
return (support.ownerScope ?? localOwnerScope)(run);
|
|
6
|
+
}
|
|
@@ -5,8 +5,11 @@ export interface ProfileKey {
|
|
|
5
5
|
}
|
|
6
6
|
export interface ProfileHandle {
|
|
7
7
|
key: ProfileKey;
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
/** Folder of a Host-local profile; absent for a volume or provider profile. */
|
|
9
|
+
profileDir?: string;
|
|
10
|
+
stateDir?: string;
|
|
11
|
+
/** Provider-neutral reference of a volume profile; the orchestrator maps it to real storage. */
|
|
12
|
+
volumeRef?: string;
|
|
10
13
|
}
|
|
11
14
|
export interface ProfileStore {
|
|
12
15
|
open(key: ProfileKey): Promise<ProfileHandle>;
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
// TODO(multi-tenant): this fixed scope is the single local owner.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// user, never from YAML) and pass it down. Find every call site with: grep -rn "LOCAL_BROWSER_SCOPE" src
|
|
5
|
-
// See docs/browser-internals.md, "Known limitations" item 3.
|
|
1
|
+
// TODO(multi-tenant): this fixed scope is the single local owner. Only ownerScope.ts (`localOwnerScope`) and
|
|
2
|
+
// tests read it; everything else takes a scope as input. A hosted service supplies an OwnerScopeResolver
|
|
3
|
+
// from the authenticated user, never from YAML. See docs/browser-internals.md, "Multi-tenant to-do list".
|
|
6
4
|
export const LOCAL_BROWSER_SCOPE = "local";
|
|
7
5
|
export class InvalidProfileKeyError extends Error {
|
|
8
6
|
code = "invalid_profile_key";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type SandboxLabels = {
|
|
2
|
+
scope: string;
|
|
3
|
+
runId: string;
|
|
4
|
+
stageId?: string;
|
|
5
|
+
profile?: string;
|
|
6
|
+
};
|
|
7
|
+
export type SandboxRef = {
|
|
8
|
+
/** Stable reference chosen by the adapter; opaque to the core. */
|
|
9
|
+
id: string;
|
|
10
|
+
/** The one place adapter data lives; only the issuing adapter reads it. */
|
|
11
|
+
adapter: {
|
|
12
|
+
id: string;
|
|
13
|
+
version: number;
|
|
14
|
+
data?: unknown;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Reserved seam for restricting what a sandbox may reach. No implementation
|
|
19
|
+
* enforces it yet; adapters that cannot enforce a policy must reject it with
|
|
20
|
+
* `not_supported` rather than ignore it. A hard `allow_domains` guarantee
|
|
21
|
+
* (private network plus filtering proxy) lands here later.
|
|
22
|
+
*/
|
|
23
|
+
export type SandboxEgressPolicy = {
|
|
24
|
+
allowDomains?: string[];
|
|
25
|
+
adapter?: {
|
|
26
|
+
id: string;
|
|
27
|
+
version: number;
|
|
28
|
+
data?: unknown;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type SandboxStartRequest = {
|
|
32
|
+
labels: SandboxLabels;
|
|
33
|
+
profile?: {
|
|
34
|
+
scope: string;
|
|
35
|
+
name: string;
|
|
36
|
+
};
|
|
37
|
+
egress?: SandboxEgressPolicy;
|
|
38
|
+
};
|
|
39
|
+
export type SandboxStatus = "running" | "stopped" | "dead";
|
|
40
|
+
export type SandboxInfo = {
|
|
41
|
+
ref: SandboxRef;
|
|
42
|
+
labels: SandboxLabels;
|
|
43
|
+
status: SandboxStatus;
|
|
44
|
+
/** Present while the sandbox is running. */
|
|
45
|
+
attachAddress?: string;
|
|
46
|
+
};
|
|
47
|
+
export type SandboxErrorClass = "unavailable" | "out_of_capacity" | "not_authorized" | "not_supported" | "failed";
|
|
48
|
+
export declare class SandboxError extends Error {
|
|
49
|
+
readonly errorClass: SandboxErrorClass;
|
|
50
|
+
constructor(errorClass: SandboxErrorClass, message: string);
|
|
51
|
+
}
|
|
52
|
+
export interface BrowserSandboxOrchestrator {
|
|
53
|
+
start(request: SandboxStartRequest): Promise<SandboxInfo>;
|
|
54
|
+
/** Closes the browser cleanly (flushing profile state) without releasing the sandbox. */
|
|
55
|
+
stopGracefully(ref: SandboxRef): Promise<void>;
|
|
56
|
+
listByLabel(labels: Partial<SandboxLabels>): Promise<SandboxInfo[]>;
|
|
57
|
+
inspect(ref: SandboxRef): Promise<SandboxInfo | undefined>;
|
|
58
|
+
/** Idempotent: releasing an unknown or already released sandbox succeeds. */
|
|
59
|
+
release(ref: SandboxRef): Promise<void>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type BrowserSandboxOrchestrator } from "./sandboxOrchestrator.js";
|
|
2
|
+
export type SessionApiState = "active" | "flushed" | "failed";
|
|
3
|
+
export type SessionApiSession = {
|
|
4
|
+
id: string;
|
|
5
|
+
state: SessionApiState;
|
|
6
|
+
connect_url?: string;
|
|
7
|
+
region: string;
|
|
8
|
+
metadata: Record<string, string>;
|
|
9
|
+
};
|
|
10
|
+
export type SessionApiFailure = "unauthorized" | "quota_exceeded" | "unavailable" | "rejected";
|
|
11
|
+
export declare class SessionApiError extends Error {
|
|
12
|
+
readonly failure: SessionApiFailure;
|
|
13
|
+
constructor(failure: SessionApiFailure, message: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The HTTP session API a managed browser provider exposes, reduced to the five calls an
|
|
17
|
+
* orchestrator needs. A real provider adapter implements this against the vendor SDK; the
|
|
18
|
+
* tests record the calls and answer from memory.
|
|
19
|
+
*/
|
|
20
|
+
export interface SessionApiClient {
|
|
21
|
+
createSession(input: {
|
|
22
|
+
metadata: Record<string, string>;
|
|
23
|
+
persistContext?: string;
|
|
24
|
+
}): Promise<SessionApiSession>;
|
|
25
|
+
getSession(id: string): Promise<SessionApiSession | undefined>;
|
|
26
|
+
listSessions(): Promise<SessionApiSession[]>;
|
|
27
|
+
flushSession(id: string): Promise<void>;
|
|
28
|
+
deleteSession(id: string): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
export type SessionApiSandboxOrchestratorOptions = {
|
|
31
|
+
client: SessionApiClient;
|
|
32
|
+
adapterId?: string;
|
|
33
|
+
};
|
|
34
|
+
export declare function createSessionApiSandboxOrchestrator(options: SessionApiSandboxOrchestratorOptions): BrowserSandboxOrchestrator;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { SandboxError, } from "./sandboxOrchestrator.js";
|
|
2
|
+
export class SessionApiError extends Error {
|
|
3
|
+
failure;
|
|
4
|
+
constructor(failure, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.failure = failure;
|
|
7
|
+
this.name = "SessionApiError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const ADAPTER_VERSION = 1;
|
|
11
|
+
const LABEL_PREFIX = "stageflow.";
|
|
12
|
+
const LABEL_KEYS = [
|
|
13
|
+
["scope", "scope"],
|
|
14
|
+
["runId", "run"],
|
|
15
|
+
["stageId", "stage"],
|
|
16
|
+
["profile", "profile"],
|
|
17
|
+
];
|
|
18
|
+
function toMetadata(labels) {
|
|
19
|
+
const metadata = {};
|
|
20
|
+
for (const [key, name] of LABEL_KEYS) {
|
|
21
|
+
const value = labels[key];
|
|
22
|
+
if (value !== undefined)
|
|
23
|
+
metadata[`${LABEL_PREFIX}${name}`] = value;
|
|
24
|
+
}
|
|
25
|
+
return metadata;
|
|
26
|
+
}
|
|
27
|
+
function fromMetadata(metadata) {
|
|
28
|
+
const labels = {};
|
|
29
|
+
for (const [key, name] of LABEL_KEYS) {
|
|
30
|
+
const value = metadata[`${LABEL_PREFIX}${name}`];
|
|
31
|
+
if (value !== undefined)
|
|
32
|
+
labels[key] = value;
|
|
33
|
+
}
|
|
34
|
+
return labels;
|
|
35
|
+
}
|
|
36
|
+
function statusOf(state) {
|
|
37
|
+
if (state === "active")
|
|
38
|
+
return "running";
|
|
39
|
+
return state === "flushed" ? "stopped" : "dead";
|
|
40
|
+
}
|
|
41
|
+
function mapError(err) {
|
|
42
|
+
if (err instanceof SandboxError)
|
|
43
|
+
return err;
|
|
44
|
+
if (err instanceof SessionApiError) {
|
|
45
|
+
const byFailure = {
|
|
46
|
+
unauthorized: "not_authorized",
|
|
47
|
+
quota_exceeded: "out_of_capacity",
|
|
48
|
+
unavailable: "unavailable",
|
|
49
|
+
rejected: "failed",
|
|
50
|
+
};
|
|
51
|
+
return new SandboxError(byFailure[err.failure], err.message);
|
|
52
|
+
}
|
|
53
|
+
return new SandboxError("failed", err instanceof Error ? err.message : String(err));
|
|
54
|
+
}
|
|
55
|
+
export function createSessionApiSandboxOrchestrator(options) {
|
|
56
|
+
const { client } = options;
|
|
57
|
+
const adapterId = options.adapterId ?? "session-api";
|
|
58
|
+
function infoOf(session) {
|
|
59
|
+
const status = statusOf(session.state);
|
|
60
|
+
return {
|
|
61
|
+
ref: { id: session.id, adapter: { id: adapterId, version: ADAPTER_VERSION, data: { region: session.region } } },
|
|
62
|
+
labels: fromMetadata(session.metadata),
|
|
63
|
+
status,
|
|
64
|
+
...(status === "running" && session.connect_url !== undefined ? { attachAddress: session.connect_url } : {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async function guard(call) {
|
|
68
|
+
try {
|
|
69
|
+
return await call();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
throw mapError(err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
async start(request) {
|
|
77
|
+
if (request.egress !== undefined) {
|
|
78
|
+
throw new SandboxError("not_supported", "this adapter cannot enforce an egress policy");
|
|
79
|
+
}
|
|
80
|
+
const session = await guard(() => client.createSession({
|
|
81
|
+
metadata: toMetadata(request.labels),
|
|
82
|
+
...(request.profile !== undefined ? { persistContext: `${request.profile.scope}/${request.profile.name}` } : {}),
|
|
83
|
+
}));
|
|
84
|
+
return infoOf(session);
|
|
85
|
+
},
|
|
86
|
+
async stopGracefully(ref) {
|
|
87
|
+
await guard(async () => {
|
|
88
|
+
if ((await client.getSession(ref.id)) !== undefined)
|
|
89
|
+
await client.flushSession(ref.id);
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
async listByLabel(labels) {
|
|
93
|
+
const wanted = toMetadata(labels);
|
|
94
|
+
const sessions = await guard(() => client.listSessions());
|
|
95
|
+
return sessions
|
|
96
|
+
.filter((s) => Object.entries(wanted).every(([key, value]) => s.metadata[key] === value))
|
|
97
|
+
.filter((s) => Object.keys(s.metadata).some((key) => key.startsWith(LABEL_PREFIX)))
|
|
98
|
+
.map(infoOf);
|
|
99
|
+
},
|
|
100
|
+
async inspect(ref) {
|
|
101
|
+
const session = await guard(() => client.getSession(ref.id));
|
|
102
|
+
return session === undefined ? undefined : infoOf(session);
|
|
103
|
+
},
|
|
104
|
+
async release(ref) {
|
|
105
|
+
await guard(() => client.deleteSession(ref.id));
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -2,7 +2,13 @@ import type { StageBrowserConfig } from "../types/stage.js";
|
|
|
2
2
|
import { BROWSER_ENV_FILENAME, type BrowserEnv, type StageBrowserSupport } from "./browserHost.js";
|
|
3
3
|
export { BROWSER_ENV_FILENAME };
|
|
4
4
|
export declare function readStagePersistedBrowserEnv(runDir: string, stageId: string): Promise<BrowserEnv | undefined>;
|
|
5
|
+
export declare function hostLaunchOptions(): {
|
|
6
|
+
launchArgs: string[];
|
|
7
|
+
executablePath?: string;
|
|
8
|
+
};
|
|
5
9
|
export declare function defaultStageBrowserSupport(): StageBrowserSupport;
|
|
10
|
+
/** Browser support for a process that serves the live view routes (`sf ui`, `sf mcp`). */
|
|
11
|
+
export declare function consoleStageBrowserSupport(): StageBrowserSupport;
|
|
6
12
|
/**
|
|
7
13
|
* Computed once per stage and persisted in the run dir: agent-browser silently
|
|
8
14
|
* relaunches the browser when a command's launch env differs from the daemon's,
|
|
@@ -11,6 +17,7 @@ export declare function defaultStageBrowserSupport(): StageBrowserSupport;
|
|
|
11
17
|
export declare function resolveStageBrowserEnv(support: StageBrowserSupport, input: {
|
|
12
18
|
runId: string;
|
|
13
19
|
stageId: string;
|
|
20
|
+
scope: string;
|
|
14
21
|
runDir: string;
|
|
15
22
|
browser: StageBrowserConfig | undefined;
|
|
16
23
|
/** Attempt the login check result belongs to; defaults to 1. */
|