import { randomUUID } from "node:crypto"; import type { ObjectId } from "mongodb"; import { collections } from "$lib/server/database"; import { config } from "$lib/server/config"; import { issueAgentProxyCredential, type IssuedAgentProxyCredential, } from "$lib/server/agent/llmProxy"; import { SandboxClient, type CancelMatchingSandboxJobsOptions, type CreateSandboxOptions, type SandboxConnection, type SandboxExecResult, type SandboxJobInfo, type SandboxProcess, } from "$lib/server/agent/SandboxClient"; import type { Workspace, WorkspaceLlmProxy, WorkspacePiSession, WorkspaceSandbox, WorkspaceSandboxCleanup, WorkspaceSandboxStatus, } from "$lib/types/Workspace"; export const AGENT_RUNTIME_IMAGE = "hf.co/spaces/Mike0021/chat-ui-agent-runtime"; export const AGENT_BRIDGE_PORT = 4_777; export const AGENT_PROVIDER_ID = "chat-ui-agent"; export const AGENT_WORKSPACE_DIR = "/workspace"; export const AGENT_POLICY_PATH = "/run/chat-ui-agent/policy"; const AGENT_CONFIG_DIR = "/root/.pi/agent"; const AGENT_MODELS_PATH = `${AGENT_CONFIG_DIR}/models.json`; const AGENT_SESSION_DIR = `${AGENT_CONFIG_DIR}/sessions`; const AGENT_BRIDGE_PATH = "/opt/chat-ui-agent/bridge.mjs"; const AGENT_APPROVAL_EXTENSION_PATH = "/opt/chat-ui-agent/approval-gate.mjs"; const DEFAULT_HUB_URL = "https://huggingface.co"; const DEFAULT_BRANCH = "main"; const BRIDGE_START_TIMEOUT_MS = 20_000; const MAX_STORED_ERROR_CHARS = 1_000; const PROVISIONING_LEASE_MS = 15 * 60 * 1_000; const TERMINAL_SANDBOX_STAGES = new Set(["COMPLETED", "ERROR", "DELETED", "CANCELED"]); export const BLANK_WORKSPACE_GITIGNORE = `# Local dependency trees and generated caches node_modules/ .venv/ venv/ __pypackages__/ __pycache__/ *.pyc *.pyo .pytest_cache/ .mypy_cache/ .ruff_cache/ .coverage # Ignore conventional local credential files as defense in depth .env .env.* !.env.example !.env.sample !.env.template `; export type WorkspaceProvisioningPhase = "claiming" | "sandbox" | "repository" | "credentials" | "bridge" | "ready" | "cleanup"; export interface WorkspaceProvisioningProgress { phase: WorkspaceProvisioningPhase; message: string; } export type WorkspaceProvisioningCallback = ( progress: WorkspaceProvisioningProgress ) => void | Promise; export interface ProvisionWorkspaceOptions { workspace: Workspace; /** Models exposed to pi and allowed through this workspace's proxy. */ availableModels?: AgentRuntimeModelDefinition[]; /** * Current request's HF credential may be used only as a server-side fallback for * the Jobs lifecycle. Source checkout auth is resolved later by the Git proxy and * no raw Hugging Face credential is ever copied into the sandbox. */ requestHfToken?: string; /** Optional override; otherwise AGENT_SANDBOX_NAMESPACE or token whoami is used. */ namespace?: string; /** Public chat-ui origin reachable from the sandbox. */ publicOrigin?: string; runtimeImage?: string; hubUrl?: string; onProgress?: WorkspaceProvisioningCallback; } export interface ReconnectWorkspaceOptions { workspace: Workspace; requestHfToken?: string; hubUrl?: string; onProgress?: WorkspaceProvisioningCallback; /** Health-check only: never rewrite lifecycle state or top-level revision. */ observational?: boolean; } export interface KillWorkspaceOptions { workspace: Workspace; requestHfToken?: string; hubUrl?: string; onProgress?: WorkspaceProvisioningCallback; } export interface RestartWorkspaceOptions extends ProvisionWorkspaceOptions {} export interface AgentRuntimeModelDefinition { id: string; name?: string; reasoning?: boolean; multimodal?: boolean; contextWindow?: number; maxTokens?: number; cost?: { input: number; output: number; cacheRead?: number; cacheWrite?: number }; } /** Server-only runtime handle. Its SandboxClient contains the Jobs credential. */ export interface WorkspaceRuntimeConnection { workspace: Workspace; sandbox: SandboxClient; bridgeUrl: string; } interface RuntimePatch { sandbox?: WorkspaceSandbox; piSession?: WorkspacePiSession | null; llmProxy?: WorkspaceLlmProxy | null; updatedAt: Date; } export interface WorkspaceSandboxExpectation { statuses: readonly WorkspaceSandboxStatus[]; id?: string; nonce?: string; generation?: string; /** Undefined ignores cleanup; null requires none; an object requires exact identity. */ cleanup?: WorkspaceSandboxCleanup | null; /** Optional document revision fence used by provisioning claims. */ updatedAt?: Date; /** Undefined ignores retirement; null requires no lease; an id requires exact ownership. */ retirement?: { id: string } | null; } export interface WorkspaceProvisionerStore { claim( workspaceId: ObjectId, expected: WorkspaceSandboxExpectation, llmProxy: WorkspaceLlmProxy, generation: string, cleanup: WorkspaceSandboxCleanup | undefined, now: Date, staleBefore: Date ): Promise; claimStop( workspaceId: ObjectId, expected: WorkspaceSandboxExpectation, generation: string, cleanup: WorkspaceSandboxCleanup | undefined, now: Date ): Promise; get(workspaceId: ObjectId): Promise; update( workspaceId: ObjectId, patch: RuntimePatch, expected: WorkspaceSandboxExpectation ): Promise; } interface SandboxRuntime { readonly connection: SandboxConnection; readonly proxyHeaders: Record; health(): Promise; inspect(): Promise; exec( cmd: string | string[], options?: { cwd?: string; environment?: Record; timeoutSeconds?: number; stdin?: string; } ): Promise; spawn( cmd: string | string[], options?: { cwd?: string; environment?: Record; tag?: string } ): Promise; listProcesses(): Promise; writeFile(path: string, content: string | ArrayBuffer, mode?: string): Promise; proxy(port: number, path: string, init?: RequestInit): Promise; proxyUrlFor(port: number, path?: string, scheme?: "https" | "wss"): string; kill(): Promise; } export interface WorkspaceProvisionerDependencies { store?: WorkspaceProvisionerStore; createSandbox?: (options: CreateSandboxOptions) => Promise; cancelMatchingSandboxJobs?: ( options: CancelMatchingSandboxJobsOptions ) => Promise; connectSandbox?: ( connection: SandboxConnection, hfToken: string, options?: { hubUrl?: string; fetch?: typeof fetch } ) => SandboxRuntime; issueCredential?: typeof issueAgentProxyCredential; fetch?: typeof fetch; now?: () => Date; randomUuid?: () => string; delay?: (milliseconds: number) => Promise; configValue?: (name: string) => string; } export class WorkspaceProvisioningError extends Error { constructor( message: string, readonly code: | "invalid_configuration" | "invalid_repository" | "already_provisioning" | "sandbox_unavailable" | "workspace_missing" = "sandbox_unavailable" ) { super(message); this.name = "WorkspaceProvisioningError"; } } export function isRetryableWorkspaceProvisioningFailure(cause: unknown): boolean { return !(cause instanceof WorkspaceProvisioningError) || cause.code === "sandbox_unavailable"; } /** * Owns the credential boundary and lifecycle of one dedicated HF Sandbox. * The Jobs token is only ever passed to the Hub/SandboxClient. The sandbox * receives a short-lived agent proxy credential whose hash alone is durable. */ export class WorkspaceProvisioner { private readonly store: WorkspaceProvisionerStore; private readonly createSandbox: (options: CreateSandboxOptions) => Promise; private readonly cancelMatchingSandboxJobs: NonNullable< WorkspaceProvisionerDependencies["cancelMatchingSandboxJobs"] >; private readonly connectSandbox: NonNullable; private readonly issueCredential: typeof issueAgentProxyCredential; private readonly requestFetch: typeof fetch; private readonly now: () => Date; private readonly randomUuid: () => string; private readonly wait: (milliseconds: number) => Promise; private readonly configValue: (name: string) => string; constructor(dependencies: WorkspaceProvisionerDependencies = {}) { this.store = dependencies.store ?? mongoWorkspaceStore; this.createSandbox = dependencies.createSandbox ?? ((options) => SandboxClient.create(options)); this.cancelMatchingSandboxJobs = dependencies.cancelMatchingSandboxJobs ?? ((options) => SandboxClient.cancelMatchingJobs(options)); this.connectSandbox = dependencies.connectSandbox ?? ((connection, hfToken, options) => SandboxClient.connect(connection, hfToken, options)); this.issueCredential = dependencies.issueCredential ?? issueAgentProxyCredential; this.requestFetch = dependencies.fetch ?? fetch; this.now = dependencies.now ?? (() => new Date()); this.randomUuid = dependencies.randomUuid ?? randomUUID; this.wait = dependencies.delay ?? delay; this.configValue = dependencies.configValue ?? readConfigValue; } async provision(options: ProvisionWorkspaceOptions): Promise { if (options.workspace.retirement) { throw new WorkspaceProvisioningError( "Agent workspace is being retired", "sandbox_unavailable" ); } if ( options.workspace.sandbox.status === "ready" || options.workspace.sandbox.status === "idle" ) { return this.reconnect(options); } const claimNow = this.now(); if ( options.workspace.sandbox.status === "provisioning" && options.workspace.sandbox.claimedAt && options.workspace.sandbox.claimedAt.getTime() > claimNow.getTime() - PROVISIONING_LEASE_MS ) { throw new WorkspaceProvisioningError( "Another request is already provisioning this workspace", "already_provisioning" ); } const jobsToken = this.resolveJobsToken(options.requestHfToken); const hubUrl = trimTrailingSlash(options.hubUrl || DEFAULT_HUB_URL); const publicOrigin = this.resolvePublicOrigin(options.publicOrigin); const runtimeImage = options.runtimeImage || this.configValue("AGENT_RUNTIME_IMAGE") || AGENT_RUNTIME_IMAGE; validateRepository(options.workspace); const requestedRuntimeModels = normalizeRuntimeModels( options.workspace, options.availableModels ); let namespace = options.namespace || this.configValue("AGENT_SANDBOX_NAMESPACE") || undefined; const requireNamespace = async (): Promise => (namespace ??= await this.resolveNamespace(jobsToken, hubUrl)); const abandonedGeneration = abandonedUnpersistedGeneration( options.workspace, claimNow, PROVISIONING_LEASE_MS ); const cleanup = options.workspace.sandbox.cleanup ?? (abandonedGeneration ? { generation: abandonedGeneration, namespace: options.workspace.sandbox.namespace || (await requireNamespace()), hubUrl, } : undefined); const credential = await this.issueCredential(requestedRuntimeModels.map((model) => model.id)); const persistedCredential = toPersistedCredential(credential); await emit(options.onProgress, "claiming", "Reserving agent workspace"); const generation = this.randomUuid(); let claimed = await this.store.claim( options.workspace._id, provisionClaimExpectation(options.workspace), persistedCredential, generation, cleanup, claimNow, new Date(claimNow.getTime() - PROVISIONING_LEASE_MS) ); if (!claimed) { const current = await this.store.get(options.workspace._id); if (!current) { throw new WorkspaceProvisioningError( "Agent workspace no longer exists", "workspace_missing" ); } if ( (current.sandbox.status === "ready" || current.sandbox.status === "idle") && current.sandbox.id && !current.retirement ) { return this.reconnect({ ...options, workspace: current }); } throw new WorkspaceProvisioningError( "Another request is already provisioning this workspace", "already_provisioning" ); } let expectedSandbox: WorkspaceSandboxExpectation = { ...sandboxExpectation(claimed.sandbox, ["provisioning"]), retirement: null, }; if (claimed.sandbox.cleanup) { const cleanupTarget = claimed.sandbox.cleanup; await emit(options.onProgress, "cleanup", "Stopping abandoned HF Sandbox"); try { await this.cancelMatchingSandboxJobs({ hfToken: jobsToken, namespace: cleanupTarget.namespace, labels: sandboxOwnershipLabels(claimed._id, cleanupTarget.generation), hubUrl: cleanupTarget.hubUrl, fetch: this.requestFetch, }); } catch (error) { const failedAt = this.now(); const safeMessage = redactError(error, [jobsToken, options.requestHfToken ?? ""]); await this.store .update( claimed._id, { sandbox: { ...claimed.sandbox, status: "error", flavor: claimed.flavor, lastSeenAt: failedAt, error: safeMessage, }, llmProxy: null, updatedAt: failedAt, }, expectedSandbox ) .catch(() => undefined); throw new WorkspaceProvisioningError(safeMessage, "sandbox_unavailable"); } const cleanupClearedAt = this.now(); const cleanupClearedSandbox = { ...claimed.sandbox }; delete cleanupClearedSandbox.cleanup; await this.updateRuntime( claimed._id, { sandbox: cleanupClearedSandbox, updatedAt: cleanupClearedAt }, expectedSandbox ); claimed = { ...claimed, sandbox: cleanupClearedSandbox, updatedAt: cleanupClearedAt }; expectedSandbox = { ...sandboxExpectation(cleanupClearedSandbox, ["provisioning"]), retirement: null, }; } if (hasCompleteConnection(claimed.sandbox)) { await emit(options.onProgress, "cleanup", "Stopping previous HF Sandbox"); try { const previousSandbox = this.connectSandbox(connectionFromWorkspace(claimed), jobsToken, { hubUrl, fetch: this.requestFetch, }); await previousSandbox.kill(); } catch (error) { const failedAt = this.now(); const safeMessage = redactError(error, [ jobsToken, options.requestHfToken ?? "", credential.token, ]); await this.updateRuntime( claimed._id, { sandbox: { ...claimed.sandbox, status: "error", lastSeenAt: failedAt, error: safeMessage, }, llmProxy: null, updatedAt: failedAt, }, expectedSandbox ); throw new WorkspaceProvisioningError(safeMessage, "sandbox_unavailable"); } } let sandbox: SandboxRuntime | undefined; let createdConnection: SandboxConnection | undefined; let connectionPersisted = false; try { // Source and model configuration must come from the record protected by // the successful claim, never from the caller's potentially stale copy. const repository = validateRepository(claimed); const runtimeModels = normalizeRuntimeModels(claimed, options.availableModels); const persistCreatedConnection = async (connection: SandboxConnection): Promise => { if (createdConnection && !sameSandboxConnection(createdConnection, connection)) { throw new WorkspaceProvisioningError( "HF Sandbox creation returned inconsistent connection metadata", "sandbox_unavailable" ); } createdConnection = connection; if (connectionPersisted) return; const startedAt = this.now(); const provisioningSandbox = workspaceSandboxFromConnection( connection, "provisioning", startedAt, generation, claimNow ); await this.updateRuntime( claimed._id, { sandbox: provisioningSandbox, updatedAt: startedAt, }, expectedSandbox ); expectedSandbox = { ...sandboxExpectation(provisioningSandbox, ["provisioning"]), retirement: null, }; connectionPersisted = true; }; await emit(options.onProgress, "sandbox", "Starting dedicated HF Sandbox"); sandbox = await this.createSandbox({ hfToken: jobsToken, namespace: await requireNamespace(), image: runtimeImage, flavor: claimed.flavor, labels: sandboxOwnershipLabels(claimed._id, generation), hubUrl, onStatus: async (message) => emit(options.onProgress, "sandbox", message), onCreated: persistCreatedConnection, }); // Keep injected implementations compatible, while the production client // persists from onCreated before it begins readiness polling. await persistCreatedConnection(sandbox.connection); await emit(options.onProgress, "repository", "Preparing repository"); await this.prepareRepository( sandbox, repository, claimed.branch, publicOrigin, credential.token ); const piSession: WorkspacePiSession = { id: claimed.piSession?.id || this.randomUuid(), dir: AGENT_SESSION_DIR, // A new bridge owns a new in-memory replay buffer whose first seq is 1. lastSequence: 0, }; await emit(options.onProgress, "credentials", "Configuring inference proxy"); await this.configurePi(sandbox, claimed, credential, publicOrigin, runtimeModels); await emit(options.onProgress, "bridge", "Starting coding agent"); const process = await sandbox.spawn(["node", AGENT_BRIDGE_PATH], { cwd: AGENT_WORKSPACE_DIR, tag: `chat-ui-agent:${claimed._id.toString()}`, environment: bridgeEnvironment(claimed, piSession), }); if (!Number.isSafeInteger(process.pid) || process.pid <= 0) { throw new WorkspaceProvisioningError( "Sandbox did not return a coding-agent process id", "sandbox_unavailable" ); } piSession.bridgePid = process.pid; await this.waitForBridge(sandbox); const readyAt = this.now(); const readySandbox = workspaceSandboxFromConnection( sandbox.connection, "ready", readyAt, generation ); const readyExpectation: WorkspaceSandboxExpectation = { ...liveSandboxExpectation(readySandbox), retirement: null, }; await this.updateRuntime( claimed._id, { sandbox: readySandbox, piSession, updatedAt: readyAt, }, expectedSandbox ); const readyWorkspace = await this.requireWorkspace(claimed._id); if (!matchesWorkspaceExpectation(readyWorkspace, readyExpectation)) { await this.throwWorkspaceChanged(claimed._id); } await emit(options.onProgress, "ready", "Agent workspace ready"); return runtimeConnection(readyWorkspace, sandbox); } catch (error) { await emit(options.onProgress, "cleanup", "Cleaning up failed sandbox"); let cleanupError: unknown; try { const cleanupSandbox = sandbox ?? (createdConnection ? this.connectSandbox(createdConnection, jobsToken, { hubUrl, fetch: this.requestFetch, }) : undefined); await cleanupSandbox?.kill(); } catch (killError) { cleanupError = killError; } const failedAt = this.now(); const originalMessage = redactError(error, [ jobsToken, options.requestHfToken ?? "", credential.token, ]); const cleanupMessage = cleanupError ? redactError(cleanupError, [jobsToken, options.requestHfToken ?? "", credential.token]) : undefined; const safeMessage = cleanupMessage ? `${originalMessage}. Automatic sandbox cleanup failed: ${cleanupMessage}`.slice( 0, MAX_STORED_ERROR_CHARS ) : originalMessage; await this.store .update( claimed._id, { sandbox: createdConnection && cleanupError ? { ...workspaceSandboxFromConnection( createdConnection, "provisioning", failedAt, generation, claimNow ), status: "error", error: safeMessage, } : { status: "error", generation, claimedAt: claimNow, flavor: claimed.flavor, image: runtimeImage, lastSeenAt: failedAt, error: safeMessage, }, llmProxy: null, updatedAt: failedAt, }, expectedSandbox ) .catch(() => undefined); throw new WorkspaceProvisioningError( safeMessage, error instanceof WorkspaceProvisioningError ? error.code : "sandbox_unavailable" ); } } async reconnect(options: ReconnectWorkspaceOptions): Promise { if ( options.workspace.sandbox.status !== "ready" && options.workspace.sandbox.status !== "idle" ) { throw new WorkspaceProvisioningError( `Workspace sandbox is not reconnectable (${options.workspace.sandbox.status})`, options.workspace.sandbox.status === "provisioning" ? "already_provisioning" : "sandbox_unavailable" ); } const jobsToken = this.resolveJobsToken(options.requestHfToken); const connection = connectionFromWorkspace(options.workspace); await emit(options.onProgress, "sandbox", "Reconnecting to HF Sandbox"); const sandbox = this.connectSandbox(connection, jobsToken, { hubUrl: trimTrailingSlash(options.hubUrl || DEFAULT_HUB_URL), fetch: this.requestFetch, }); const job = await sandbox.inspect().catch(() => undefined); if (job && TERMINAL_SANDBOX_STAGES.has(job.stage.toUpperCase())) { if (!options.observational) { await this.retireSandbox( options.workspace, sandbox, jobsToken, "HF Sandbox has stopped", liveWorkspaceExpectation(options.workspace), options.onProgress ); } throw new WorkspaceProvisioningError("HF Sandbox has stopped", "sandbox_unavailable"); } // HF edge routes can briefly return 404 independently. Only treat a // missing bridge as authoritative when the control route is healthy and // its process list confirms the persisted PID is gone. Otherwise let the // actual operation use its own bounded transport reconnect. if (await sandbox.health()) { const bridgePid = options.workspace.piSession?.bridgePid; if (Number.isSafeInteger(bridgePid) && bridgePid && bridgePid > 0) { const processes = await sandbox.listProcesses().catch(() => undefined); const bridge = processes?.find((process) => process.pid === bridgePid); if (processes && (!bridge || bridge.running === false || bridge.exitCode != null)) { if (!options.observational) { await this.retireSandbox( options.workspace, sandbox, jobsToken, "Coding-agent bridge has stopped", liveWorkspaceExpectation(options.workspace), options.onProgress ); } throw new WorkspaceProvisioningError( "Coding-agent bridge is not running", "sandbox_unavailable" ); } } } if (options.observational) { const current = await this.requireWorkspace(options.workspace._id); if (!matchesWorkspaceExpectation(current, liveWorkspaceExpectation(options.workspace))) { await this.throwWorkspaceChanged(options.workspace._id); } return runtimeConnection(current, sandbox); } const readyAt = this.now(); const healthySandbox = { ...options.workspace.sandbox }; delete healthySandbox.error; const reconnectExpectation = liveWorkspaceExpectation(options.workspace); await this.updateRuntime( options.workspace._id, { sandbox: { ...healthySandbox, status: "ready", lastSeenAt: readyAt, }, updatedAt: readyAt, }, reconnectExpectation ); const workspace = await this.requireWorkspace(options.workspace._id); if (!matchesWorkspaceExpectation(workspace, reconnectExpectation)) { await this.throwWorkspaceChanged(options.workspace._id); } await emit(options.onProgress, "ready", "Agent workspace reconnected"); return runtimeConnection(workspace, sandbox); } async kill(options: KillWorkspaceOptions): Promise { let stopRuntime: { jobsToken: string; sandbox: SandboxRuntime } | undefined; let cleanup = options.workspace.sandbox.cleanup; const sandboxStatus = options.workspace.sandbox.status; const hasAttachedJobMetadata = Boolean( options.workspace.sandbox.id || options.workspace.sandbox.namespace || options.workspace.sandbox.baseUrl || options.workspace.sandbox.nonce ); const hubUrl = trimTrailingSlash(options.hubUrl || DEFAULT_HUB_URL); if ( sandboxStatus === "ready" || sandboxStatus === "idle" || (sandboxStatus !== "dead" && hasAttachedJobMetadata) ) { const connection = connectionFromWorkspace(options.workspace); const jobsToken = this.resolveJobsToken(options.requestHfToken); const sandbox = this.connectSandbox(connection, jobsToken, { hubUrl, fetch: this.requestFetch, }); stopRuntime = { jobsToken, sandbox }; } else if ( sandboxStatus !== "dead" && sandboxStatus !== "unprovisioned" && options.workspace.sandbox.generation ) { // A process can die after Jobs accepts creation but before onCreated // persists the connection. Exact ownership labels are the only durable // handle in that interval, so preflight credentials/namespace before // claiming and cancel the old generation only after the CAS succeeds. const jobsToken = this.resolveJobsToken(options.requestHfToken); const namespace = options.workspace.sandbox.namespace || this.configValue("AGENT_SANDBOX_NAMESPACE") || (await this.resolveNamespace(jobsToken, hubUrl)); cleanup ??= { namespace, generation: options.workspace.sandbox.generation, hubUrl, }; } const cleanupJobsToken = cleanup ? (stopRuntime?.jobsToken ?? this.resolveJobsToken(options.requestHfToken)) : undefined; const claimedAt = this.now(); const generation = this.randomUuid(); let claimed = await this.store.claimStop( options.workspace._id, stopClaimExpectation(options.workspace), generation, cleanup, claimedAt ); if (!claimed) { return this.throwWorkspaceChanged(options.workspace._id); } let stopExpectation = stopClaimExpectation(claimed, ["provisioning"]); if (claimed.sandbox.cleanup) { const cleanupTarget = claimed.sandbox.cleanup; const cleanupToken = cleanupJobsToken ?? this.resolveJobsToken(options.requestHfToken); await emit(options.onProgress, "cleanup", "Stopping unattached HF Sandbox"); const stoppedAt = this.now(); try { await this.cancelMatchingSandboxJobs({ hfToken: cleanupToken, namespace: cleanupTarget.namespace, labels: sandboxOwnershipLabels(claimed._id, cleanupTarget.generation), hubUrl: cleanupTarget.hubUrl, fetch: this.requestFetch, }); } catch (error) { const safeMessage = redactError(error, [cleanupToken, options.requestHfToken ?? ""]); await this.updateRuntime( claimed._id, { sandbox: { ...claimed.sandbox, status: "error", flavor: claimed.flavor, lastSeenAt: stoppedAt, error: `HF Sandbox stop failed: ${safeMessage}`.slice(0, MAX_STORED_ERROR_CHARS), }, piSession: claimed.piSession ? { ...claimed.piSession, bridgePid: undefined } : null, llmProxy: null, updatedAt: stoppedAt, }, stopExpectation ); throw new WorkspaceProvisioningError(safeMessage, "sandbox_unavailable"); } const cleanupClearedSandbox = { ...claimed.sandbox }; delete cleanupClearedSandbox.cleanup; await this.updateRuntime( claimed._id, { sandbox: cleanupClearedSandbox, updatedAt: stoppedAt }, stopExpectation ); claimed = { ...claimed, sandbox: cleanupClearedSandbox, updatedAt: stoppedAt }; stopExpectation = stopClaimExpectation(claimed, ["provisioning"]); } if (stopRuntime) { return this.retireSandbox( claimed, stopRuntime.sandbox, stopRuntime.jobsToken, "HF Sandbox stopped", stopExpectation, options.onProgress ); } const now = this.now(); const stoppedSandbox = stoppedSandboxMetadata(claimed.sandbox, now); await emit(options.onProgress, "cleanup", "Stopping HF Sandbox"); await this.updateRuntime( claimed._id, { sandbox: stoppedSandbox, piSession: claimed.piSession ? { ...claimed.piSession, bridgePid: undefined } : null, llmProxy: null, updatedAt: now, }, stopExpectation ); return this.requireExpectedWorkspace( claimed._id, stopClaimExpectation({ ...claimed, sandbox: stoppedSandbox }) ); } async restart(options: RestartWorkspaceOptions): Promise { const dead = await this.kill(options); return this.provision({ ...options, workspace: dead }); } private resolveJobsToken(requestHfToken?: string): string { const token = this.configValue("AGENT_HF_TOKEN") || this.configValue("HF_TOKEN") || requestHfToken; if (!token) { throw new WorkspaceProvisioningError( "Agent Sandboxes require AGENT_HF_TOKEN, HF_TOKEN, or an authenticated HF session", "invalid_configuration" ); } return token; } private resolvePublicOrigin(override?: string): string { const configured = override || this.configValue("PUBLIC_ORIGIN"); if (!configured) { throw new WorkspaceProvisioningError( "PUBLIC_ORIGIN is required so the sandbox can reach the inference proxy", "invalid_configuration" ); } let origin: URL; try { origin = new URL(configured); } catch { throw new WorkspaceProvisioningError( "PUBLIC_ORIGIN must be an absolute HTTP(S) URL", "invalid_configuration" ); } if (!["http:", "https:"].includes(origin.protocol) || origin.username || origin.password) { throw new WorkspaceProvisioningError( "PUBLIC_ORIGIN must be a credential-free HTTP(S) URL", "invalid_configuration" ); } if (origin.search || origin.hash) { throw new WorkspaceProvisioningError( "PUBLIC_ORIGIN must not contain a query or fragment", "invalid_configuration" ); } return trimTrailingSlash(origin.toString()); } private async resolveNamespace(token: string, hubUrl: string): Promise { const response = await this.requestFetch(`${hubUrl}/api/whoami-v2`, { headers: { Authorization: `Bearer ${token}` }, redirect: "manual", signal: AbortSignal.timeout(10_000), }); if (!response.ok) { throw new WorkspaceProvisioningError( "Unable to resolve the HF Jobs namespace", "invalid_configuration" ); } const whoami = (await response.json()) as { name?: unknown }; if (typeof whoami.name !== "string" || !whoami.name.trim()) { throw new WorkspaceProvisioningError( "HF authentication returned no Jobs namespace", "invalid_configuration" ); } return whoami.name; } private async prepareRepository( sandbox: SandboxRuntime, repository: ValidatedRepository, branch: string | undefined, publicOrigin: string, agentSessionToken: string ): Promise { const selectedBranch = validateBranch(branch || DEFAULT_BRANCH); if (repository.kind === "git" || repository.kind === "hf") { const cloneUrl = repository.kind === "hf" ? `${publicOrigin}/api/agent/git/repository.git` : repository.url; const args = ["git", "clone", "--origin", "origin"]; if (branch || repository.kind === "hf") { args.push("--branch", selectedBranch, "--single-branch"); } args.push("--", cloneUrl, AGENT_WORKSPACE_DIR); await assertExec( sandbox, args, repository.kind === "hf" ? "clone Hugging Face repository" : "clone public Git repository", 300, gitCloneEnvironment( repository.kind === "hf" ? cloneUrl : undefined, repository.kind === "hf" ? agentSessionToken : undefined ), repository.kind === "hf" ? mapHuggingFaceCloneFailure : undefined ); } else { await assertExec(sandbox, ["mkdir", "-p", AGENT_WORKSPACE_DIR], "create workspace directory"); await assertExec( sandbox, ["git", "init", "-b", selectedBranch, "--", AGENT_WORKSPACE_DIR], "initialize Git repository" ); } await assertExec( sandbox, ["git", "config", "--global", "--add", "safe.directory", AGENT_WORKSPACE_DIR], "trust dedicated Git workspace" ); await assertExec( sandbox, ["git", "-C", AGENT_WORKSPACE_DIR, "config", "user.name", "Chat UI Agent"], "configure Git author" ); await assertExec( sandbox, ["git", "-C", AGENT_WORKSPACE_DIR, "config", "user.email", "agent@chat-ui.local"], "configure Git email" ); if (repository.kind === "blank") { await sandbox.writeFile( `${AGENT_WORKSPACE_DIR}/.gitignore`, BLANK_WORKSPACE_GITIGNORE, "0644" ); await assertExec( sandbox, ["git", "-C", AGENT_WORKSPACE_DIR, "add", "-A", "--"], "stage workspace baseline", 300 ); await assertExec( sandbox, [ "git", "-C", AGENT_WORKSPACE_DIR, "commit", "--allow-empty", "--no-gpg-sign", "-m", "Initialize agent workspace", ], "commit workspace baseline", 300 ); } } private async configurePi( sandbox: SandboxRuntime, workspace: Workspace, credential: IssuedAgentProxyCredential, publicOrigin: string, runtimeModels: AgentRuntimeModelDefinition[] ): Promise { await assertExec( sandbox, ["mkdir", "-p", AGENT_CONFIG_DIR, AGENT_SESSION_DIR, "/run/chat-ui-agent"], "create pi configuration directory" ); await sandbox.writeFile(AGENT_POLICY_PATH, `${workspace.policy}\n`, "0600"); // Shape verified against @earendil-works/pi-coding-agent 0.84.2 docs/models.md. const models = { providers: { [AGENT_PROVIDER_ID]: { baseUrl: `${publicOrigin}/api/agent/llm`, api: "openai-completions", // A non-secret placeholder makes the custom model available to pi. apiKey: "agent-proxy", authHeader: false, headers: { "X-Agent-Session": credential.token }, models: runtimeModels.map((model) => ({ id: model.id, name: model.name || model.id, reasoning: model.id === workspace.model ? workspace.thinkingLevel !== "off" : Boolean(model.reasoning), input: model.multimodal ? ["text", "image"] : ["text"], contextWindow: model.contextWindow ?? 128_000, maxTokens: model.maxTokens ?? 16_384, cost: { input: model.cost?.input ?? 0, output: model.cost?.output ?? 0, cacheRead: model.cost?.cacheRead ?? 0, cacheWrite: model.cost?.cacheWrite ?? 0, }, })), }, }, }; await sandbox.writeFile(AGENT_MODELS_PATH, `${JSON.stringify(models, null, 2)}\n`, "0600"); } private async waitForBridge(sandbox: SandboxRuntime): Promise { const deadline = Date.now() + BRIDGE_START_TIMEOUT_MS; let lastStatus = 0; while (Date.now() < deadline) { try { const response = await sandbox.proxy(AGENT_BRIDGE_PORT, "/health", { signal: AbortSignal.timeout(2_000), }); lastStatus = response.status; const available = response.ok; await response.body?.cancel().catch(() => undefined); if (available) return; } catch { // The bridge port is expected to refuse connections during its short startup. } await this.wait(100); } throw new WorkspaceProvisioningError( `Coding-agent bridge did not become ready${lastStatus ? ` (HTTP ${lastStatus})` : ""}`, "sandbox_unavailable" ); } private async requireWorkspace(workspaceId: ObjectId): Promise { const workspace = await this.store.get(workspaceId); if (!workspace) { throw new WorkspaceProvisioningError("Agent workspace no longer exists", "workspace_missing"); } return workspace; } private async requireExpectedWorkspace( workspaceId: ObjectId, expected: WorkspaceSandboxExpectation ): Promise { const workspace = await this.requireWorkspace(workspaceId); if (!matchesWorkspaceExpectation(workspace, expected)) { await this.throwWorkspaceChanged(workspaceId); } return workspace; } private async updateRuntime( workspaceId: ObjectId, patch: RuntimePatch, expected: WorkspaceSandboxExpectation ): Promise { if (await this.store.update(workspaceId, patch, expected)) return; await this.throwWorkspaceChanged(workspaceId); } private async throwWorkspaceChanged(workspaceId: ObjectId): Promise { if (!(await this.store.get(workspaceId))) { throw new WorkspaceProvisioningError("Agent workspace no longer exists", "workspace_missing"); } throw new WorkspaceProvisioningError( "Agent workspace changed during the sandbox operation", "sandbox_unavailable" ); } private async retireSandbox( workspace: Workspace, sandbox: SandboxRuntime, jobsToken: string, reason: string, expected: WorkspaceSandboxExpectation, onProgress?: WorkspaceProvisioningCallback ): Promise { await emit(onProgress, "cleanup", "Stopping HF Sandbox"); const stoppedAt = this.now(); try { await sandbox.kill(); } catch (error) { const safeMessage = redactError(error, [jobsToken]); await this.updateRuntime( workspace._id, { sandbox: { ...workspace.sandbox, status: "error", lastSeenAt: stoppedAt, error: `${reason}. Automatic sandbox cleanup failed: ${safeMessage}`.slice( 0, MAX_STORED_ERROR_CHARS ), }, piSession: workspace.piSession ? { ...workspace.piSession, bridgePid: undefined } : null, llmProxy: null, updatedAt: stoppedAt, }, expected ); throw new WorkspaceProvisioningError(safeMessage, "sandbox_unavailable"); } const stoppedSandbox = stoppedSandboxMetadata(workspace.sandbox, stoppedAt); await this.updateRuntime( workspace._id, { sandbox: stoppedSandbox, piSession: workspace.piSession ? { ...workspace.piSession, bridgePid: undefined } : null, llmProxy: null, updatedAt: stoppedAt, }, expected ); return this.requireExpectedWorkspace( workspace._id, stopClaimExpectation({ ...workspace, sandbox: stoppedSandbox }) ); } } type ValidatedRepository = { kind: "blank" } | { kind: "git"; url: string } | { kind: "hf" }; function normalizeRuntimeModels( workspace: Workspace, available: AgentRuntimeModelDefinition[] | undefined ): AgentRuntimeModelDefinition[] { const candidates = available?.length ? available : [ { id: workspace.model, name: workspace.model, reasoning: workspace.thinkingLevel !== "off", }, ]; const byId = new Map(); for (const candidate of candidates.slice(0, 500)) { const id = candidate.id.trim(); if (!id || id.length > 512 || hasAsciiControl(id, true)) continue; byId.set(id, { id, ...(candidate.name?.trim() ? { name: candidate.name.trim().slice(0, 512) } : {}), reasoning: Boolean(candidate.reasoning), multimodal: Boolean(candidate.multimodal), ...(candidate.contextWindow && candidate.contextWindow > 0 ? { contextWindow: Math.min(Math.floor(candidate.contextWindow), 10_000_000) } : {}), ...(candidate.maxTokens && candidate.maxTokens > 0 ? { maxTokens: Math.min(Math.floor(candidate.maxTokens), 1_000_000) } : {}), ...(candidate.cost && Number.isFinite(candidate.cost.input) && candidate.cost.input >= 0 && Number.isFinite(candidate.cost.output) && candidate.cost.output >= 0 ? { cost: { input: candidate.cost.input, output: candidate.cost.output, cacheRead: Math.max(0, candidate.cost.cacheRead ?? 0), cacheWrite: Math.max(0, candidate.cost.cacheWrite ?? 0), }, } : {}), }); } if (!byId.has(workspace.model)) { byId.set(workspace.model, { id: workspace.model, name: workspace.model, reasoning: workspace.thinkingLevel !== "off", }); } return [...byId.values()]; } function validateRepository(workspace: Workspace): ValidatedRepository { if ( !workspace.model.trim() || workspace.model.length > 512 || hasAsciiControl(workspace.model, false) ) { throw new WorkspaceProvisioningError("Invalid agent model id", "invalid_configuration"); } if (workspace.branch) validateBranch(workspace.branch); if (workspace.repo.kind === "blank") return { kind: "blank" }; if (workspace.repo.kind === "git") { let url: URL; try { url = new URL(workspace.repo.ref); } catch { throw new WorkspaceProvisioningError( "Git repository must be a public HTTPS URL", "invalid_repository" ); } if ( url.protocol !== "https:" || !url.hostname || url.username || url.password || url.search || url.hash ) { throw new WorkspaceProvisioningError( "Git repository must be a credential-free public HTTPS URL", "invalid_repository" ); } return { kind: "git", url: url.toString() }; } if ( workspace.repo.ref.length > 256 || !/^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(workspace.repo.ref) || !(["model", "dataset", "space"] as const).includes(workspace.repo.repoType) ) { throw new WorkspaceProvisioningError( "HF repository must use namespace/name", "invalid_repository" ); } return { kind: "hf" }; } /** * Disable every ambient credential helper. For an HF source, scope the existing * short-lived agent-session header to the exact server-side proxy URL. The raw * owner/Jobs Hub credentials never enter this environment or the resulting Git * configuration. LFS smudging is skipped because authenticated LFS action URLs * are intentionally outside this read-only smart-HTTP proxy's narrow surface. */ function gitCloneEnvironment(repoUrl?: string, agentSessionToken?: string): Record { const environment: Record = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "/bin/false", SSH_ASKPASS: "/bin/false", GIT_CONFIG_COUNT: agentSessionToken ? "2" : "1", GIT_CONFIG_KEY_0: "credential.helper", GIT_CONFIG_VALUE_0: "", }; if (repoUrl && agentSessionToken) { environment.GIT_LFS_SKIP_SMUDGE = "1"; environment.GIT_CONFIG_KEY_1 = `http.${repoUrl}.extraHeader`; environment.GIT_CONFIG_VALUE_1 = `X-Agent-Session: ${agentSessionToken}`; } return environment; } function validateBranch(branch: string): string { const components = branch.split("/"); if ( branch.length === 0 || branch.length > 200 || branch === "HEAD" || branch.startsWith("-") || branch.endsWith(".") || branch.endsWith("/") || branch.includes("..") || branch.includes("//") || branch.includes("@{") || components.some( (component) => !component || component.startsWith(".") || component.endsWith(".lock") ) || hasInvalidBranchCharacter(branch) ) { throw new WorkspaceProvisioningError("Invalid Git branch name", "invalid_repository"); } return branch; } function bridgeEnvironment( workspace: Workspace, session: WorkspacePiSession ): Record { if (!session.id || !session.dir) { throw new WorkspaceProvisioningError( "Agent session metadata is incomplete", "invalid_configuration" ); } const prefixArgs = [ "--extension", AGENT_APPROVAL_EXTENSION_PATH, "--thinking", workspace.thinkingLevel, "--session-id", session.id, ]; return { BRIDGE_HOST: "127.0.0.1", BRIDGE_PORT: String(AGENT_BRIDGE_PORT), WORKSPACE_DIR: AGENT_WORKSPACE_DIR, PI_PROVIDER: AGENT_PROVIDER_ID, PI_MODEL: workspace.model, AGENT_POLICY: workspace.policy, AGENT_POLICY_PATH, PI_SESSION_DIR: session.dir, PI_SESSION_NAME: `chat-ui-${workspace.conversationId.toString()}`, PI_BIN_PREFIX_ARGS_JSON: JSON.stringify(prefixArgs), PI_TELEMETRY: "0", }; } function hasAsciiControl(value: string, includeDelete: boolean): boolean { for (const character of value) { const code = character.charCodeAt(0); if (code <= 31 || (includeDelete && code === 127)) return true; } return false; } function hasInvalidBranchCharacter(value: string): boolean { for (const character of value) { const code = character.charCodeAt(0); if (code <= 32 || code === 127 || "~^:?*\\[".includes(character)) return true; } return false; } async function assertExec( sandbox: SandboxRuntime, command: string[], action: string, timeoutSeconds = 60, environment?: Record, mapFailure?: (result: SandboxExecResult) => WorkspaceProvisioningError | undefined ): Promise { const result = await sandbox.exec(command, { timeoutSeconds, ...(environment ? { environment } : {}), }); if (result.exitCode === 0 && !result.timedOut) return; const mapped = mapFailure?.(result); if (mapped) throw mapped; const detail = (result.stderr || result.stdout).trim().slice(-500); throw new WorkspaceProvisioningError( `Unable to ${action}${detail ? `: ${detail}` : ""}`, "sandbox_unavailable" ); } function mapHuggingFaceCloneFailure( result: SandboxExecResult ): WorkspaceProvisioningError | undefined { if (result.timedOut) return undefined; const detail = `${result.stderr}\n${result.stdout}`; if ( /askpass|could not read Username|authentication|HTTP[^\n]*\b(?:401|403)\b|error:\s*403/i.test( detail ) ) { return new WorkspaceProvisioningError( "Sign in with Hugging Face to access this repository, then retry", "invalid_repository" ); } if ( /remote branch .+ not found|repository not found|not found in upstream origin/i.test(detail) ) { return new WorkspaceProvisioningError( "The selected Hugging Face repository or branch is unavailable", "invalid_repository" ); } return undefined; } function toPersistedCredential(credential: IssuedAgentProxyCredential): WorkspaceLlmProxy { return { tokenHash: credential.tokenHash, createdAt: credential.createdAt, expiresAt: credential.expiresAt, allowedModels: credential.allowedModels, }; } function workspaceSandboxFromConnection( connection: SandboxConnection, status: "provisioning" | "ready", seenAt: Date, generation: string, claimedAt?: Date ): WorkspaceSandbox { return { status, generation, ...(claimedAt ? { claimedAt } : {}), id: connection.id, namespace: connection.namespace, baseUrl: connection.baseUrl, nonce: connection.nonce, image: connection.image, flavor: connection.flavor, createdAt: connection.createdAt, lastSeenAt: seenAt, }; } function sandboxOwnershipLabels(workspaceId: ObjectId, generation: string): Record { return { "chat-ui-agent": "1", "chat-ui-workspace": workspaceId.toString(), "chat-ui-generation": generation, }; } function abandonedUnpersistedGeneration( workspace: Workspace, now: Date, leaseMs: number ): string | undefined { const { sandbox } = workspace; if ( !sandbox.generation || hasCompleteConnection(sandbox) || (sandbox.status !== "error" && (sandbox.status !== "provisioning" || (sandbox.claimedAt && sandbox.claimedAt.getTime() > now.getTime() - leaseMs))) ) { return undefined; } return sandbox.generation; } function sameSandboxConnection(left: SandboxConnection, right: SandboxConnection): boolean { return ( left.id === right.id && left.namespace === right.namespace && left.baseUrl === right.baseUrl && left.nonce === right.nonce && left.image === right.image && left.flavor === right.flavor && left.createdAt.getTime() === right.createdAt.getTime() ); } function sandboxExpectation( sandbox: WorkspaceSandbox, statuses: readonly WorkspaceSandboxStatus[] = [sandbox.status] ): WorkspaceSandboxExpectation { return { statuses, ...(sandbox.id ? { id: sandbox.id } : {}), ...(sandbox.nonce ? { nonce: sandbox.nonce } : {}), ...(sandbox.generation ? { generation: sandbox.generation } : {}), cleanup: sandbox.cleanup ? { ...sandbox.cleanup } : null, }; } function provisionClaimExpectation(workspace: Workspace): WorkspaceSandboxExpectation { return { ...sandboxExpectation(workspace.sandbox), updatedAt: workspace.updatedAt, retirement: null, }; } function stopClaimExpectation( workspace: Workspace, statuses: readonly WorkspaceSandboxStatus[] = [workspace.sandbox.status] ): WorkspaceSandboxExpectation { return { ...sandboxExpectation(workspace.sandbox, statuses), retirement: workspace.retirement ? { id: workspace.retirement.id } : null, }; } function liveSandboxExpectation(sandbox: WorkspaceSandbox): WorkspaceSandboxExpectation { if (!sandbox.id || !sandbox.nonce) { throw new WorkspaceProvisioningError( "Workspace has no reconnectable HF Sandbox metadata", "sandbox_unavailable" ); } return sandboxExpectation(sandbox, ["ready", "idle"]); } function liveWorkspaceExpectation(workspace: Workspace): WorkspaceSandboxExpectation { return { ...liveSandboxExpectation(workspace.sandbox), retirement: workspace.retirement ? { id: workspace.retirement.id } : null, }; } function matchesSandboxExpectation( sandbox: WorkspaceSandbox, expected: WorkspaceSandboxExpectation ): boolean { return ( expected.statuses.includes(sandbox.status) && (!expected.id || sandbox.id === expected.id) && (!expected.nonce || sandbox.nonce === expected.nonce) && (!expected.generation || sandbox.generation === expected.generation) && (expected.cleanup === undefined || (expected.cleanup === null ? sandbox.cleanup === undefined : sameSandboxCleanup(sandbox.cleanup, expected.cleanup))) ); } function sameSandboxCleanup( actual: WorkspaceSandboxCleanup | undefined, expected: WorkspaceSandboxCleanup ): boolean { return ( actual?.generation === expected.generation && actual.namespace === expected.namespace && actual.hubUrl === expected.hubUrl ); } function matchesWorkspaceExpectation( workspace: Workspace, expected: WorkspaceSandboxExpectation ): boolean { return ( matchesSandboxExpectation(workspace.sandbox, expected) && (!expected.updatedAt || workspace.updatedAt.getTime() === expected.updatedAt.getTime()) && (expected.retirement === undefined || (expected.retirement === null ? workspace.retirement === undefined : workspace.retirement?.id === expected.retirement.id)) ); } function stoppedSandboxMetadata(sandbox: WorkspaceSandbox, stoppedAt: Date): WorkspaceSandbox { return { status: "dead", flavor: sandbox.flavor, ...(sandbox.generation ? { generation: sandbox.generation } : {}), ...(sandbox.id ? { id: sandbox.id } : {}), ...(sandbox.namespace ? { namespace: sandbox.namespace } : {}), ...(sandbox.image ? { image: sandbox.image } : {}), ...(sandbox.createdAt ? { createdAt: sandbox.createdAt } : {}), lastSeenAt: stoppedAt, }; } function connectionFromWorkspace(workspace: Workspace): SandboxConnection { const sandbox = workspace.sandbox; if (!hasCompleteConnection(sandbox)) { throw new WorkspaceProvisioningError( "Workspace has no reconnectable HF Sandbox metadata", "sandbox_unavailable" ); } return { id: sandbox.id, namespace: sandbox.namespace, baseUrl: sandbox.baseUrl, nonce: sandbox.nonce, image: sandbox.image, flavor: sandbox.flavor, createdAt: sandbox.createdAt || workspace.createdAt, }; } function hasCompleteConnection( sandbox: WorkspaceSandbox ): sandbox is WorkspaceSandbox & Required> { return Boolean( sandbox.id && sandbox.namespace && sandbox.baseUrl && sandbox.nonce && sandbox.image ); } function runtimeConnection( workspace: Workspace, sandbox: SandboxRuntime ): WorkspaceRuntimeConnection { return { workspace, sandbox: sandbox as SandboxClient, bridgeUrl: sandbox.proxyUrlFor(AGENT_BRIDGE_PORT, "/ws", "wss"), }; } function redactError(error: unknown, secrets: string[]): string { let message = error instanceof Error ? error.message : "Sandbox provisioning failed"; for (const secret of secrets) { if (secret) message = message.split(secret).join("[redacted]"); } return message.slice(0, MAX_STORED_ERROR_CHARS); } async function emit( callback: WorkspaceProvisioningCallback | undefined, phase: WorkspaceProvisioningPhase, message: string ): Promise { try { await callback?.({ phase, message }); } catch { // Progress delivery is best-effort and must never strand a billable Job. } } function readConfigValue(name: string): string { const value = (config as unknown as Record)[name]; return typeof value === "string" ? value.trim() : ""; } function trimTrailingSlash(value: string): string { return value.replace(/\/+$/, ""); } function delay(milliseconds: number): Promise { return new Promise((resolve) => setTimeout(resolve, milliseconds)); } export const mongoWorkspaceStore: WorkspaceProvisionerStore = { async claim(workspaceId, expected, llmProxy, generation, cleanup, now, staleBefore) { return collections.workspaces.findOneAndUpdate( { ...workspaceExpectationFilter(workspaceId, expected), $or: [ { "sandbox.status": { $in: ["unprovisioned", "dead", "error"] } }, { "sandbox.status": "provisioning", "sandbox.claimedAt": { $lt: staleBefore }, }, { "sandbox.status": "provisioning", "sandbox.claimedAt": { $exists: false } }, ], }, { $set: { "sandbox.status": "provisioning", "sandbox.generation": generation, "sandbox.claimedAt": now, ...(cleanup ? { "sandbox.cleanup": cleanup } : {}), llmProxy, updatedAt: now, }, $unset: { "sandbox.error": "", "piSession.bridgePid": "", ...(!cleanup ? { "sandbox.cleanup": "" } : {}), }, }, { returnDocument: "after", includeResultMetadata: false } ); }, async claimStop(workspaceId, expected, generation, cleanup, now) { return collections.workspaces.findOneAndUpdate( workspaceExpectationFilter(workspaceId, expected), { $set: { "sandbox.status": "provisioning", "sandbox.generation": generation, "sandbox.claimedAt": now, ...(cleanup ? { "sandbox.cleanup": cleanup } : {}), updatedAt: now, }, $unset: { llmProxy: "", ...(!cleanup ? { "sandbox.cleanup": "" } : {}), }, }, { returnDocument: "after", includeResultMetadata: false } ); }, get(workspaceId) { return collections.workspaces.findOne({ _id: workspaceId }); }, async update(workspaceId, patch, expected) { const set: Record = { updatedAt: patch.updatedAt }; const unset: Record = {}; if (patch.sandbox !== undefined) set.sandbox = patch.sandbox; if (patch.piSession === null) unset.piSession = ""; else if (patch.piSession !== undefined) set.piSession = patch.piSession; if (patch.llmProxy === null) unset.llmProxy = ""; else if (patch.llmProxy !== undefined) set.llmProxy = patch.llmProxy; const result = await collections.workspaces.updateOne( workspaceExpectationFilter(workspaceId, expected), { $set: set, ...(Object.keys(unset).length ? { $unset: unset } : {}) } ); return result.matchedCount === 1; }, }; function workspaceExpectationFilter( workspaceId: ObjectId, expected: WorkspaceSandboxExpectation ): Record { return { _id: workspaceId, "sandbox.status": { $in: [...expected.statuses] }, ...(expected.id ? { "sandbox.id": expected.id } : {}), ...(expected.nonce ? { "sandbox.nonce": expected.nonce } : {}), ...(expected.generation ? { "sandbox.generation": expected.generation } : {}), ...(expected.cleanup === null ? { "sandbox.cleanup": { $exists: false } } : expected.cleanup ? { "sandbox.cleanup.generation": expected.cleanup.generation, "sandbox.cleanup.namespace": expected.cleanup.namespace, "sandbox.cleanup.hubUrl": expected.cleanup.hubUrl, } : {}), ...(expected.updatedAt ? { updatedAt: expected.updatedAt } : {}), ...(expected.retirement === null ? { retirement: { $exists: false } } : expected.retirement ? { "retirement.id": expected.retirement.id } : {}), }; } export const workspaceProvisioner = new WorkspaceProvisioner();