@truefoundry/trueforge-core 0.2.0-rc.2 → 0.2.0-rc.4
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/dist/agent-session/TurnResourceResolver.d.ts +13 -10
- package/dist/agent-session/TurnResourceResolver.d.ts.map +1 -1
- package/dist/agent-session/TurnResourceResolver.js +15 -1
- package/dist/agent-session/TurnResourceResolver.js.map +1 -1
- package/dist/agent-session/TurnResourceResolver.mjs +15 -1
- package/dist/agent-session/TurnResourceResolver.mjs.map +1 -1
- package/dist/agent-session/index.d.ts +1 -1
- package/dist/agent-session/index.d.ts.map +1 -1
- package/dist/agent-session/index.js.map +1 -1
- package/dist/agent-session/index.mjs.map +1 -1
- package/dist/agent-session/schemas/agentSpec.d.ts +4 -2
- package/dist/agent-session/schemas/agentSpec.d.ts.map +1 -1
- package/dist/agent-session/schemas/agentSpec.js +5 -3
- package/dist/agent-session/schemas/agentSpec.js.map +1 -1
- package/dist/agent-session/schemas/agentSpec.mjs +5 -3
- package/dist/agent-session/schemas/agentSpec.mjs.map +1 -1
- package/dist/agent-session/schemas/session.d.ts +3 -0
- package/dist/agent-session/schemas/session.d.ts.map +1 -1
- package/dist/agent-session/store/ISessionStore.d.ts +10 -0
- package/dist/agent-session/store/ISessionStore.d.ts.map +1 -1
- package/dist/agent-session/store/ISessionStore.js.map +1 -1
- package/dist/agent-session/store/InMemorySessionStore.d.ts +2 -1
- package/dist/agent-session/store/InMemorySessionStore.d.ts.map +1 -1
- package/dist/agent-session/store/InMemorySessionStore.js +13 -0
- package/dist/agent-session/store/InMemorySessionStore.js.map +1 -1
- package/dist/agent-session/store/InMemorySessionStore.mjs +13 -0
- package/dist/agent-session/store/InMemorySessionStore.mjs.map +1 -1
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +3 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/llm/VercelAILLM.d.ts.map +1 -1
- package/dist/core/llm/VercelAILLM.js +4 -0
- package/dist/core/llm/VercelAILLM.js.map +1 -1
- package/dist/core/llm/VercelAILLM.mjs +4 -0
- package/dist/core/llm/VercelAILLM.mjs.map +1 -1
- package/dist/core/redisKeys.d.ts +7 -0
- package/dist/core/redisKeys.d.ts.map +1 -0
- package/dist/core/redisKeys.js +36 -0
- package/dist/core/redisKeys.js.map +1 -0
- package/dist/core/redisKeys.mjs +10 -0
- package/dist/core/redisKeys.mjs.map +1 -0
- package/dist/core/sandbox/Sandbox.d.ts.map +1 -1
- package/dist/core/sandbox/Sandbox.js +8 -1
- package/dist/core/sandbox/Sandbox.js.map +1 -1
- package/dist/core/sandbox/Sandbox.mjs +8 -1
- package/dist/core/sandbox/Sandbox.mjs.map +1 -1
- package/dist/core/sandbox/provider/DaytonaProvider.d.ts +1 -1
- package/dist/core/sandbox/provider/DaytonaProvider.d.ts.map +1 -1
- package/dist/core/sandbox/provider/DaytonaProvider.js +3 -3
- package/dist/core/sandbox/provider/DaytonaProvider.js.map +1 -1
- package/dist/core/sandbox/provider/DaytonaProvider.mjs +3 -3
- package/dist/core/sandbox/provider/DaytonaProvider.mjs.map +1 -1
- package/dist/core/sandbox/provider/Provider.d.ts +20 -4
- package/dist/core/sandbox/provider/Provider.d.ts.map +1 -1
- package/dist/core/sandbox/provider/Provider.js.map +1 -1
- package/dist/core/sandbox/provider/Provider.mjs.map +1 -1
- package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts +2 -2
- package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts.map +1 -1
- package/dist/core/sandbox/provider/TFYSandboxProvider.js +7 -6
- package/dist/core/sandbox/provider/TFYSandboxProvider.js.map +1 -1
- package/dist/core/sandbox/provider/TFYSandboxProvider.mjs +7 -6
- package/dist/core/sandbox/provider/TFYSandboxProvider.mjs.map +1 -1
- package/dist/core/sandbox/sandboxScripts.gen.d.ts +1 -1
- package/dist/core/sandbox/sandboxScripts.gen.d.ts.map +1 -1
- package/dist/core/sandbox/sandboxScripts.gen.js +1 -1
- package/dist/core/sandbox/sandboxScripts.gen.js.map +1 -1
- package/dist/core/sandbox/sandboxScripts.gen.mjs +1 -1
- package/dist/core/sandbox/sandboxScripts.gen.mjs.map +1 -1
- package/dist/core/sandbox/skills/ISkillMounter.d.ts +1 -1
- package/dist/core/sandbox/skills/ISkillMounter.d.ts.map +1 -1
- package/dist/core/sandbox/skills/ISkillMounter.js.map +1 -1
- package/dist/core/sandbox/skills/SkillMounter.d.ts +20 -2
- package/dist/core/sandbox/skills/SkillMounter.d.ts.map +1 -1
- package/dist/core/sandbox/skills/SkillMounter.js +59 -23
- package/dist/core/sandbox/skills/SkillMounter.js.map +1 -1
- package/dist/core/sandbox/skills/SkillMounter.mjs +58 -23
- package/dist/core/sandbox/skills/SkillMounter.mjs.map +1 -1
- package/dist/core/sandbox/skills/index.d.ts +1 -1
- package/dist/core/sandbox/skills/index.d.ts.map +1 -1
- package/dist/core/sandbox/skills/index.js.map +1 -1
- package/dist/core/sandbox/skills/index.mjs.map +1 -1
- package/dist/request-reply/executor.d.ts +1 -1
- package/dist/request-reply/executor.d.ts.map +1 -1
- package/dist/request-reply/executor.js +1 -1
- package/dist/request-reply/executor.js.map +1 -1
- package/dist/request-reply/executor.mjs +1 -1
- package/dist/request-reply/executor.mjs.map +1 -1
- package/dist/request-reply/utils.d.ts +0 -1
- package/dist/request-reply/utils.d.ts.map +1 -1
- package/dist/request-reply/utils.js +4 -6
- package/dist/request-reply/utils.js.map +1 -1
- package/dist/request-reply/utils.mjs +4 -5
- package/dist/request-reply/utils.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/sandbox/provider/DaytonaProvider.ts"],"sourcesContent":["import type { Sandbox, Snapshot } from '@daytona/sdk';\nimport { Daytona, DaytonaError } from '@daytona/sdk';\nimport { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxNotAvailableError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_PREVIEW_URL_EXPIRY_SECONDS, DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport type { ExecResult, SandboxBuild, SandboxExecParams, SandboxFileInfo, SandboxProvider } from './Provider';\n\nconst SANDBOX_NOT_FOUND_STATUS = 404;\n/** Another replica already registered this build name; its create is the one that counts. */\nconst SNAPSHOT_CONFLICT_STATUS = 409;\nconst SANDBOX_STATE_STARTED = 'started';\n\nconst BUILD_STATE_ACTIVE = 'active';\nconst BUILD_STATE_INACTIVE = 'inactive';\nconst BUILD_STATE_ERROR = 'error';\nconst BUILD_STATE_BUILD_FAILED = 'build_failed';\n\nconst IMAGE_BUILD_NAME_PREFIX = 'trueforge-build-';\n/** Same default the Daytona SDK applies when `DaytonaConfig.apiUrl` is omitted. */\nconst DEFAULT_DAYTONA_API_URL = 'https://app.daytona.io/api';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Digest portion of a container image reference (the tag/digest after the final `:`)\n * The release image is always published with an explicit digest tag\n */\nfunction imageDigest(image: string): string {\n const lastSegment = image.slice(image.lastIndexOf('/') + 1);\n const colon = lastSegment.lastIndexOf(':');\n if (colon === -1) {\n throw new Error(`Sandbox image reference has no tag/digest: ${image}`);\n }\n return lastSegment.slice(colon + 1);\n}\n\n/** Deterministic build name per image digest so every server replica converges on one build. */\nfunction deriveImageBuildName(digest: string): string {\n return `${IMAGE_BUILD_NAME_PREFIX}${digest}`;\n}\n\n/** Terminal-failure build states: a build stuck here never becomes ready on its own. */\nfunction isFailedBuildState(state: Snapshot['state']): boolean {\n return state === BUILD_STATE_ERROR || state === BUILD_STATE_BUILD_FAILED;\n}\n\n/** Convert Daytona https preview URLs to wss for the NATS client. */\nfunction httpUrlToWsUrl(url: string): string {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') {\n parsed.protocol = 'wss:';\n } else if (parsed.protocol === 'http:') {\n parsed.protocol = 'ws:';\n }\n return parsed.toString();\n}\n\nexport interface DaytonaSandboxProviderOptions {\n /** Caller-owned Daytona SDK client (credentials / lifetime). */\n client: Daytona;\n /**\n * Same API key the client was built with; used for the register-only snapshot POST because the\n * SDK's `snapshot.create` polls to a terminal state instead of returning once registered.\n */\n apiKey: string;\n /** Daytona API base URL (including `/api`). Defaults to the SDK's public cloud endpoint. */\n apiUrl?: string | undefined;\n tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n sandboxImage: string;\n /**\n * Daytona snapshot name to clone sandboxes from. When omitted it is derived from the image\n * digest. Callers that create sandboxes pass the persisted build_ref so cloning targets the\n * snapshot that was actually built, not a speculative name derived from the current image.\n */\n buildRef?: string | undefined;\n timeoutMs: number;\n autoStopIntervalInMinutes: number;\n autoArchiveIntervalInMinutes: number;\n autoDeleteIntervalInMinutes: number;\n fileMaxBytesForDownload: number;\n /** Defaults to the built-in sandbox NATS WebSocket port (4444). */\n natsBridgePort?: number;\n /** Defaults to 1 hour (same as the gateway's max agent execution time). */\n previewUrlExpirySeconds?: number;\n logger: Logger;\n}\n\nexport class DaytonaSandboxProvider implements SandboxProvider {\n readonly type = 'daytona';\n private readonly tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n private readonly imageUri: string;\n /** Daytona snapshot name sandboxes are cloned from; the persisted build_ref, or derived from the image digest. */\n private readonly buildRef: string;\n private readonly timeoutMs: number;\n private readonly autoStopIntervalInMinutes: number;\n private readonly autoArchiveIntervalInMinutes: number;\n private readonly autoDeleteIntervalInMinutes: number;\n private readonly fileMaxBytesForDownload: number;\n private readonly natsBridgePort: number;\n private readonly previewUrlExpirySeconds: number;\n private readonly apiKey: string;\n private readonly apiUrl: string;\n private readonly logger: Logger;\n private readonly daytona: Daytona;\n private static readonly cachedSandboxes = new Map<string, { sandbox: Sandbox; defaultTimeoutMs: number }>();\n // De-dupes concurrent recovery attempts on the same sandbox to a single refreshData+start round-trip.\n private static readonly inFlightRecoveries = new Map<string, Promise<boolean>>();\n\n constructor(options: DaytonaSandboxProviderOptions) {\n this.daytona = options.client;\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? DEFAULT_DAYTONA_API_URL;\n this.tenantName = options.tenantName;\n this.imageUri = options.sandboxImage;\n this.buildRef = options.buildRef ?? deriveImageBuildName(imageDigest(options.sandboxImage));\n this.timeoutMs = options.timeoutMs;\n this.autoStopIntervalInMinutes = options.autoStopIntervalInMinutes;\n this.autoArchiveIntervalInMinutes = options.autoArchiveIntervalInMinutes;\n this.autoDeleteIntervalInMinutes = options.autoDeleteIntervalInMinutes;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.natsBridgePort = options.natsBridgePort ?? DEFAULT_SANDBOX_NATS_WS_PORT;\n this.previewUrlExpirySeconds = options.previewUrlExpirySeconds ?? DEFAULT_PREVIEW_URL_EXPIRY_SECONDS;\n this.logger = options.logger.child({ module: 'DaytonaProvider' });\n }\n\n private async getOrCreateSandbox(sandboxId?: string): Promise<{ sandbox: Sandbox; defaultTimeoutMs: number }> {\n if (sandboxId) {\n validateSandboxOwnedByTenant({ sandboxId, tenantName: this.tenantName });\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n if (cached) {\n return cached;\n }\n }\n\n const sandbox = sandboxId\n ? await this.restoreExistingSandbox(sandboxId)\n : await this.daytona.create({\n name: `${this.tenantName}.${randomUUID()}`,\n snapshot: this.buildRef,\n autoStopInterval: this.autoStopIntervalInMinutes,\n autoArchiveInterval: this.autoArchiveIntervalInMinutes,\n autoDeleteInterval: this.autoDeleteIntervalInMinutes,\n });\n\n const entry = { sandbox, defaultTimeoutMs: this.timeoutMs };\n DaytonaSandboxProvider.cachedSandboxes.set(sandbox.name, entry);\n return entry;\n }\n\n // Returns true iff the caller should retry: either we restarted a stopped sandbox, or the cache entry is missing and the retry will rebuild it via the cold path.\n private static recoverSandboxIfStopped(sandboxId: string): Promise<boolean> {\n const existing = DaytonaSandboxProvider.inFlightRecoveries.get(sandboxId);\n if (existing) {\n return existing;\n }\n\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n // Cache may have been evicted by a concurrent error path; signal retry so getOrCreateSandbox rebuilds via restoreExistingSandbox.\n if (!cached) {\n return Promise.resolve(true);\n }\n\n const recovery = (async () => {\n await cached.sandbox.refreshData();\n if (cached.sandbox.state === SANDBOX_STATE_STARTED) {\n return false;\n }\n // start() covers both stopped and archived per Daytona; throws on unrecoverable states (error/destroyed).\n await cached.sandbox.start();\n return true;\n })().finally(() => {\n DaytonaSandboxProvider.inFlightRecoveries.delete(sandboxId);\n });\n\n DaytonaSandboxProvider.inFlightRecoveries.set(sandboxId, recovery);\n return recovery;\n }\n\n private async executeWithSandboxRecovery<T>(sandboxId: string, operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (originalError) {\n // TODO: Narrow to a specific Daytona error code once @daytona/sdk exposes one for \"sandbox not running\".\n if (!(originalError instanceof DaytonaError)) {\n throw originalError;\n }\n\n let recovered: boolean;\n try {\n recovered = await DaytonaSandboxProvider.recoverSandboxIfStopped(sandboxId);\n } catch (recoveryError) {\n this.logger.error('Sandbox recovery failed', {\n ...extractErrorLogFields(recoveryError),\n originalError: extractErrorLogFields(originalError),\n });\n throw new Error('Sandbox is unavailable; recovery attempt failed.', { cause: recoveryError });\n }\n\n if (!recovered) {\n throw originalError;\n }\n\n try {\n return await operation();\n } catch (retryError) {\n this.logger.error('Sandbox operation failed after successful recovery', {\n ...extractErrorLogFields(retryError),\n originalError: extractErrorLogFields(originalError),\n });\n // Rethrow as-is so callers can match domain error types (SandboxPathIsDirectoryError, DaytonaError statusCode mappings, etc.).\n throw retryError;\n }\n }\n }\n\n private async restoreExistingSandbox(sandboxId: string): Promise<Sandbox> {\n try {\n const sandbox = await this.daytona.get(sandboxId);\n if (sandbox.state !== SANDBOX_STATE_STARTED) {\n await sandbox.start();\n }\n return sandbox;\n } catch (ex) {\n if (ex instanceof DaytonaError && ex.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxNotAvailableError(sandboxId);\n }\n throw ex;\n }\n }\n\n async createSandbox(): Promise<{ sandboxId: string }> {\n return context.with(suppressTracing(context.active()), async () => {\n const { sandbox } = await this.getOrCreateSandbox();\n this.logger.debug(`Sandbox created: name=${sandbox.name}`);\n return { sandboxId: sandbox.name };\n });\n }\n\n /** Resolves undefined when no snapshot carries that name; auth/other failures throw. */\n private async getSnapshot(name: string): Promise<Snapshot | undefined> {\n try {\n return await this.daytona.snapshot.get(name);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * Drops a build stuck in a terminal-failure state so a fresh create can reuse its\n * deterministic name. A concurrent replica may have deleted it already (404) — that\n * is fine and treated as success.\n */\n private async deleteFailedBuild(snapshot: Snapshot): Promise<void> {\n try {\n await this.daytona.snapshot.delete(snapshot);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return;\n }\n throw error;\n }\n }\n\n private toBuild(state: string, errorReason: string | null): SandboxBuild {\n const metadata = { build_ref: this.buildRef, image_uri: this.imageUri };\n switch (state) {\n case BUILD_STATE_ACTIVE:\n return { status: 'ready', reason: null, metadata };\n case BUILD_STATE_ERROR:\n case BUILD_STATE_BUILD_FAILED:\n return { status: 'failed', reason: errorReason ?? `Sandbox image build failed (${state}).`, metadata };\n default:\n // pending / building / pulling / removing / future states: not ready yet.\n return { status: 'pending', reason: `Sandbox image build in progress (${state}).`, metadata };\n }\n }\n\n /**\n * Registers the snapshot and returns its initial state without waiting for the build.\n *\n * The SDK's `snapshot.create` issues this same POST and then polls until the snapshot is active\n * or failed (minutes on a cold image pull). Configure only needs the registration result, so\n * credential errors surface on the request while build progress stays observable via `getSnapshot`.\n */\n private async registerSnapshot(): Promise<{ state: string; errorReason: string | null }> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}/snapshots`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: this.buildRef, imageName: this.imageUri }),\n });\n } catch (error) {\n throw new Error('Daytona snapshot registration request failed.', { cause: error });\n }\n\n const body: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n isRecord(body) && typeof body['message'] === 'string'\n ? body['message']\n : `Daytona snapshot registration failed (${String(response.status)})`;\n throw new DaytonaError(message, response.status);\n }\n if (!isRecord(body) || typeof body['state'] !== 'string') {\n throw new DaytonaError(\"Failed to register snapshot. Daytona didn't return a snapshot state.\");\n }\n return {\n state: body['state'],\n errorReason: typeof body['errorReason'] === 'string' ? body['errorReason'] : null,\n };\n }\n\n async buildImage(): Promise<SandboxBuild> {\n const existing = await this.getSnapshot(this.buildRef);\n if (existing) {\n // Daytona parks unused snapshots as 'inactive' after ~2 idle weeks; reactivate rather than rebuild.\n if (existing.state === BUILD_STATE_INACTIVE) {\n const activated = await this.daytona.snapshot.activate(existing);\n return this.toBuild(activated.state, activated.errorReason);\n }\n if (!isFailedBuildState(existing.state)) {\n return this.toBuild(existing.state, existing.errorReason);\n }\n // A failed build keeps the deterministic name occupied and never self-heals, so\n // re-saving settings (which calls buildImage) could never retry. Drop it, then recreate.\n await this.deleteFailedBuild(existing);\n }\n\n try {\n const registered = await this.registerSnapshot();\n // Registration returns pending almost always; map whatever Daytona sent so a fast\n // active/error still surfaces correctly without a follow-up GET.\n return this.toBuild(registered.state, registered.errorReason);\n } catch (error) {\n // A losing concurrent create is not a build failure: the winner owns the deterministic name.\n if (error instanceof DaytonaError && error.statusCode === SNAPSHOT_CONFLICT_STATUS) {\n this.logger.info(`Daytona snapshot already created concurrently: name=${this.buildRef}`);\n return {\n status: 'pending',\n reason: 'Sandbox image build started by another server replica.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n throw error;\n }\n }\n\n async getImageBuildStatus(): Promise<SandboxBuild> {\n const snapshot = await this.getSnapshot(this.buildRef);\n // Read-only: a missing build reports pending; PUT /settings/sandbox-providers starts the build.\n if (!snapshot) {\n return {\n status: 'pending',\n reason: 'Sandbox image build not started.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n return this.toBuild(snapshot.state, snapshot.errorReason);\n }\n\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox, defaultTimeoutMs } = await this.getOrCreateSandbox(params.sandboxId);\n const response = await sandbox.process.executeCommand(\n params.command,\n params.cwd,\n params.env ?? {},\n params.timeoutSeconds ?? defaultTimeoutMs / 1000,\n );\n return {\n success: true,\n response: { exitCode: response.exitCode, result: response.result },\n };\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n if (e instanceof SandboxNotAvailableError) {\n throw e;\n }\n this.logger.error('Sandbox execution error', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n }\n });\n }\n\n private async getFileInfo(sandbox: Sandbox, path: string): Promise<SandboxFileInfo> {\n const details = await sandbox.fs.getFileDetails(path);\n return { size: details.size, isDir: details.isDir };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n\n const info = await this.getFileInfo(sandbox, params.path);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n return await sandbox.fs.downloadFile(params.path);\n });\n } catch (e: unknown) {\n if (e instanceof SandboxPathIsDirectoryError || e instanceof SandboxFileTooLargeError) {\n throw e;\n }\n if (e instanceof DaytonaError && e.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxFileNotFoundError(params.path);\n }\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n await sandbox.fs.uploadFile(params.content, params.remotePath);\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n // Mints a signed, time-limited https preview URL exposing the given sandbox port.\n private async getPreviewUrl(params: { sandboxId: string; port: number; expiresInSeconds: number }): Promise<string> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n const signed = await sandbox.getSignedPreviewUrl(params.port, params.expiresInSeconds);\n return signed.url;\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n this.logger.error('Failed to create signed preview URL', extractErrorLogFields(e));\n throw e;\n }\n });\n }\n\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: async (sandboxId: string) => {\n const previewUrl = await this.getPreviewUrl({\n sandboxId,\n port: this.natsBridgePort,\n expiresInSeconds: this.previewUrlExpirySeconds,\n });\n return httpUrlToWsUrl(previewUrl);\n },\n sandboxClientNatsUrl: `ws://localhost:${String(this.natsBridgePort)}`,\n logger: this.logger,\n mcpClientInstall: {\n remotePath: join('/opt', 'tf', 'mcp-client', 'mcp_client.py'),\n pathBinSymlink: join('/usr', 'local', 'bin', 'mcp-client'),\n },\n });\n }\n\n getAdditionalInstructions(): string | undefined {\n return undefined;\n }\n\n // Isolated container: image-absolute layout. GIT_CONFIG store --file needs an absolute path.\n // /opt/tf/{uploads,skills,tool-results,git_downloader.py,.git-credentials}\n // /opt/tfy/mcp-client/mcp_client.py + /usr/local/bin/mcp-client (image PATH; no layout bin)\n getToolResultDumpDir(): string {\n return join('/opt', 'tf', 'tool-results');\n }\n\n getGitCredentialsPath(): string {\n return join('/opt', 'tf', '.git-credentials');\n }\n\n getFileUploadsDir(): string {\n return join('/opt', 'tf', 'uploads');\n }\n\n getSkillsDir(): string {\n return join('/opt', 'tf', 'skills');\n }\n\n getGitDownloaderPath(): string {\n return join('/opt', 'tf', 'git_downloader.py');\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/sandbox/provider/DaytonaProvider.ts"],"sourcesContent":["import type { Sandbox, Snapshot } from '@daytona/sdk';\nimport { Daytona, DaytonaError } from '@daytona/sdk';\nimport { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxNotAvailableError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_PREVIEW_URL_EXPIRY_SECONDS, DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport type { ExecResult, SandboxBuild, SandboxExecParams, SandboxFileInfo, SandboxProvider } from './Provider';\n\nconst SANDBOX_NOT_FOUND_STATUS = 404;\n/** Another replica already registered this build name; its create is the one that counts. */\nconst SNAPSHOT_CONFLICT_STATUS = 409;\nconst SANDBOX_STATE_STARTED = 'started';\n\nconst BUILD_STATE_ACTIVE = 'active';\nconst BUILD_STATE_INACTIVE = 'inactive';\nconst BUILD_STATE_ERROR = 'error';\nconst BUILD_STATE_BUILD_FAILED = 'build_failed';\n\nconst IMAGE_BUILD_NAME_PREFIX = 'trueforge-build-';\n/** Same default the Daytona SDK applies when `DaytonaConfig.apiUrl` is omitted. */\nconst DEFAULT_DAYTONA_API_URL = 'https://app.daytona.io/api';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Digest portion of a container image reference (the tag/digest after the final `:`)\n * The release image is always published with an explicit digest tag\n */\nfunction imageDigest(image: string): string {\n const lastSegment = image.slice(image.lastIndexOf('/') + 1);\n const colon = lastSegment.lastIndexOf(':');\n if (colon === -1) {\n throw new Error(`Sandbox image reference has no tag/digest: ${image}`);\n }\n return lastSegment.slice(colon + 1);\n}\n\n/** Deterministic build name per image digest so every server replica converges on one build. */\nfunction deriveImageBuildName(digest: string): string {\n return `${IMAGE_BUILD_NAME_PREFIX}${digest}`;\n}\n\n/** Terminal-failure build states: a build stuck here never becomes ready on its own. */\nfunction isFailedBuildState(state: Snapshot['state']): boolean {\n return state === BUILD_STATE_ERROR || state === BUILD_STATE_BUILD_FAILED;\n}\n\n/** Convert Daytona https preview URLs to wss for the NATS client. */\nfunction httpUrlToWsUrl(url: string): string {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') {\n parsed.protocol = 'wss:';\n } else if (parsed.protocol === 'http:') {\n parsed.protocol = 'ws:';\n }\n return parsed.toString();\n}\n\nexport interface DaytonaSandboxProviderOptions {\n /** Caller-owned Daytona SDK client (credentials / lifetime). */\n client: Daytona;\n /**\n * Same API key the client was built with; used for the register-only snapshot POST because the\n * SDK's `snapshot.create` polls to a terminal state instead of returning once registered.\n */\n apiKey: string;\n /** Daytona API base URL (including `/api`). Defaults to the SDK's public cloud endpoint. */\n apiUrl?: string | undefined;\n tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n sandboxImage: string;\n /**\n * Daytona snapshot name to clone sandboxes from. When omitted it is derived from the image\n * digest. Callers that create sandboxes pass the persisted build_ref so cloning targets the\n * snapshot that was actually built, not a speculative name derived from the current image.\n */\n buildRef?: string | undefined;\n timeoutMs: number;\n autoStopIntervalInMinutes: number;\n autoArchiveIntervalInMinutes: number;\n autoDeleteIntervalInMinutes: number;\n fileMaxBytesForDownload: number;\n /** Defaults to the built-in sandbox NATS WebSocket port (4444). */\n natsBridgePort?: number;\n /** Defaults to 1 hour (same as the gateway's max agent execution time). */\n previewUrlExpirySeconds?: number;\n logger: Logger;\n}\n\nexport class DaytonaSandboxProvider implements SandboxProvider {\n readonly type = 'daytona';\n private readonly tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n private readonly imageUri: string;\n /** Daytona snapshot name sandboxes are cloned from; the persisted build_ref, or derived from the image digest. */\n private readonly buildRef: string;\n private readonly timeoutMs: number;\n private readonly autoStopIntervalInMinutes: number;\n private readonly autoArchiveIntervalInMinutes: number;\n private readonly autoDeleteIntervalInMinutes: number;\n private readonly fileMaxBytesForDownload: number;\n private readonly natsBridgePort: number;\n private readonly previewUrlExpirySeconds: number;\n private readonly apiKey: string;\n private readonly apiUrl: string;\n private readonly logger: Logger;\n private readonly daytona: Daytona;\n private static readonly cachedSandboxes = new Map<string, { sandbox: Sandbox; defaultTimeoutMs: number }>();\n // De-dupes concurrent recovery attempts on the same sandbox to a single refreshData+start round-trip.\n private static readonly inFlightRecoveries = new Map<string, Promise<boolean>>();\n\n constructor(options: DaytonaSandboxProviderOptions) {\n this.daytona = options.client;\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? DEFAULT_DAYTONA_API_URL;\n this.tenantName = options.tenantName;\n this.imageUri = options.sandboxImage;\n this.buildRef = options.buildRef ?? deriveImageBuildName(imageDigest(options.sandboxImage));\n this.timeoutMs = options.timeoutMs;\n this.autoStopIntervalInMinutes = options.autoStopIntervalInMinutes;\n this.autoArchiveIntervalInMinutes = options.autoArchiveIntervalInMinutes;\n this.autoDeleteIntervalInMinutes = options.autoDeleteIntervalInMinutes;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.natsBridgePort = options.natsBridgePort ?? DEFAULT_SANDBOX_NATS_WS_PORT;\n this.previewUrlExpirySeconds = options.previewUrlExpirySeconds ?? DEFAULT_PREVIEW_URL_EXPIRY_SECONDS;\n this.logger = options.logger.child({ module: 'DaytonaProvider' });\n }\n\n private async getOrCreateSandbox(sandboxId?: string): Promise<{ sandbox: Sandbox; defaultTimeoutMs: number }> {\n if (sandboxId) {\n validateSandboxOwnedByTenant({ sandboxId, tenantName: this.tenantName });\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n if (cached) {\n return cached;\n }\n }\n\n const sandbox = sandboxId\n ? await this.restoreExistingSandbox(sandboxId)\n : await this.daytona.create({\n name: `${this.tenantName}.${randomUUID()}`,\n snapshot: this.buildRef,\n autoStopInterval: this.autoStopIntervalInMinutes,\n autoArchiveInterval: this.autoArchiveIntervalInMinutes,\n autoDeleteInterval: this.autoDeleteIntervalInMinutes,\n });\n\n const entry = { sandbox, defaultTimeoutMs: this.timeoutMs };\n DaytonaSandboxProvider.cachedSandboxes.set(sandbox.name, entry);\n return entry;\n }\n\n // Returns true iff the caller should retry: either we restarted a stopped sandbox, or the cache entry is missing and the retry will rebuild it via the cold path.\n private static recoverSandboxIfStopped(sandboxId: string): Promise<boolean> {\n const existing = DaytonaSandboxProvider.inFlightRecoveries.get(sandboxId);\n if (existing) {\n return existing;\n }\n\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n // Cache may have been evicted by a concurrent error path; signal retry so getOrCreateSandbox rebuilds via restoreExistingSandbox.\n if (!cached) {\n return Promise.resolve(true);\n }\n\n const recovery = (async () => {\n await cached.sandbox.refreshData();\n if (cached.sandbox.state === SANDBOX_STATE_STARTED) {\n return false;\n }\n // start() covers both stopped and archived per Daytona; throws on unrecoverable states (error/destroyed).\n await cached.sandbox.start();\n return true;\n })().finally(() => {\n DaytonaSandboxProvider.inFlightRecoveries.delete(sandboxId);\n });\n\n DaytonaSandboxProvider.inFlightRecoveries.set(sandboxId, recovery);\n return recovery;\n }\n\n private async executeWithSandboxRecovery<T>(sandboxId: string, operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (originalError) {\n // TODO: Narrow to a specific Daytona error code once @daytona/sdk exposes one for \"sandbox not running\".\n if (!(originalError instanceof DaytonaError)) {\n throw originalError;\n }\n\n let recovered: boolean;\n try {\n recovered = await DaytonaSandboxProvider.recoverSandboxIfStopped(sandboxId);\n } catch (recoveryError) {\n this.logger.error('Sandbox recovery failed', {\n ...extractErrorLogFields(recoveryError),\n originalError: extractErrorLogFields(originalError),\n });\n throw new Error('Sandbox is unavailable; recovery attempt failed.', { cause: recoveryError });\n }\n\n if (!recovered) {\n throw originalError;\n }\n\n try {\n return await operation();\n } catch (retryError) {\n this.logger.error('Sandbox operation failed after successful recovery', {\n ...extractErrorLogFields(retryError),\n originalError: extractErrorLogFields(originalError),\n });\n // Rethrow as-is so callers can match domain error types (SandboxPathIsDirectoryError, DaytonaError statusCode mappings, etc.).\n throw retryError;\n }\n }\n }\n\n private async restoreExistingSandbox(sandboxId: string): Promise<Sandbox> {\n try {\n const sandbox = await this.daytona.get(sandboxId);\n if (sandbox.state !== SANDBOX_STATE_STARTED) {\n await sandbox.start();\n }\n return sandbox;\n } catch (ex) {\n if (ex instanceof DaytonaError && ex.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxNotAvailableError(sandboxId);\n }\n throw ex;\n }\n }\n\n async createSandbox(): Promise<{ sandboxId: string }> {\n return context.with(suppressTracing(context.active()), async () => {\n const { sandbox } = await this.getOrCreateSandbox();\n this.logger.debug(`Sandbox created: name=${sandbox.name}`);\n return { sandboxId: sandbox.name };\n });\n }\n\n /** Resolves undefined when no snapshot carries that name; auth/other failures throw. */\n private async getSnapshot(name: string): Promise<Snapshot | undefined> {\n try {\n return await this.daytona.snapshot.get(name);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * Drops a build stuck in a terminal-failure state so a fresh create can reuse its\n * deterministic name. A concurrent replica may have deleted it already (404) — that\n * is fine and treated as success.\n */\n private async deleteFailedBuild(snapshot: Snapshot): Promise<void> {\n try {\n await this.daytona.snapshot.delete(snapshot);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return;\n }\n throw error;\n }\n }\n\n private toBuild(state: string, errorReason: string | null): SandboxBuild {\n const metadata = { build_ref: this.buildRef, image_uri: this.imageUri };\n switch (state) {\n case BUILD_STATE_ACTIVE:\n return { status: 'ready', reason: null, metadata };\n case BUILD_STATE_ERROR:\n case BUILD_STATE_BUILD_FAILED:\n return { status: 'failed', reason: errorReason ?? `Sandbox image build failed (${state}).`, metadata };\n default:\n // pending / building / pulling / removing / future states: not ready yet.\n return { status: 'pending', reason: `Sandbox image build in progress (${state}).`, metadata };\n }\n }\n\n /**\n * Registers the snapshot and returns its initial state without waiting for the build.\n *\n * The SDK's `snapshot.create` issues this same POST and then polls until the snapshot is active\n * or failed (minutes on a cold image pull). Configure only needs the registration result, so\n * credential errors surface on the request while build progress stays observable via `getSnapshot`.\n */\n private async registerSnapshot(): Promise<{ state: string; errorReason: string | null }> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}/snapshots`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: this.buildRef, imageName: this.imageUri }),\n });\n } catch (error) {\n throw new Error('Daytona snapshot registration request failed.', { cause: error });\n }\n\n const body: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n isRecord(body) && typeof body['message'] === 'string'\n ? body['message']\n : `Daytona snapshot registration failed (${String(response.status)})`;\n throw new DaytonaError(message, response.status);\n }\n if (!isRecord(body) || typeof body['state'] !== 'string') {\n throw new DaytonaError(\"Failed to register snapshot. Daytona didn't return a snapshot state.\");\n }\n return {\n state: body['state'],\n errorReason: typeof body['errorReason'] === 'string' ? body['errorReason'] : null,\n };\n }\n\n async buildImage(): Promise<SandboxBuild> {\n const existing = await this.getSnapshot(this.buildRef);\n if (existing) {\n // Daytona parks unused snapshots as 'inactive' after ~2 idle weeks; reactivate rather than rebuild.\n if (existing.state === BUILD_STATE_INACTIVE) {\n const activated = await this.daytona.snapshot.activate(existing);\n return this.toBuild(activated.state, activated.errorReason);\n }\n if (!isFailedBuildState(existing.state)) {\n return this.toBuild(existing.state, existing.errorReason);\n }\n // A failed build keeps the deterministic name occupied and never self-heals, so\n // re-saving settings (which calls buildImage) could never retry. Drop it, then recreate.\n await this.deleteFailedBuild(existing);\n }\n\n try {\n const registered = await this.registerSnapshot();\n // Registration returns pending almost always; map whatever Daytona sent so a fast\n // active/error still surfaces correctly without a follow-up GET.\n return this.toBuild(registered.state, registered.errorReason);\n } catch (error) {\n // A losing concurrent create is not a build failure: the winner owns the deterministic name.\n if (error instanceof DaytonaError && error.statusCode === SNAPSHOT_CONFLICT_STATUS) {\n this.logger.info(`Daytona snapshot already created concurrently: name=${this.buildRef}`);\n return {\n status: 'pending',\n reason: 'Sandbox image build started by another server replica.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n throw error;\n }\n }\n\n async getImageBuildStatus(): Promise<SandboxBuild> {\n const snapshot = await this.getSnapshot(this.buildRef);\n // Read-only: a missing build reports pending; PUT /settings/sandbox-providers starts the build.\n if (!snapshot) {\n return {\n status: 'pending',\n reason: 'Sandbox image build not started.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n return this.toBuild(snapshot.state, snapshot.errorReason);\n }\n\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox, defaultTimeoutMs } = await this.getOrCreateSandbox(params.sandboxId);\n const response = await sandbox.process.executeCommand(\n params.command,\n params.cwd,\n params.env ?? {},\n params.timeoutSeconds ?? defaultTimeoutMs / 1000,\n );\n return {\n success: true,\n response: { exitCode: response.exitCode, result: response.result },\n };\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n if (e instanceof SandboxNotAvailableError) {\n throw e;\n }\n this.logger.error('Sandbox execution error', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n }\n });\n }\n\n private async getFileInfo(sandbox: Sandbox, path: string): Promise<SandboxFileInfo> {\n const details = await sandbox.fs.getFileDetails(path);\n return { size: details.size, isDir: details.isDir };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n\n const info = await this.getFileInfo(sandbox, params.path);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n return await sandbox.fs.downloadFile(params.path);\n });\n } catch (e: unknown) {\n if (e instanceof SandboxPathIsDirectoryError || e instanceof SandboxFileTooLargeError) {\n throw e;\n }\n if (e instanceof DaytonaError && e.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxFileNotFoundError(params.path);\n }\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n await sandbox.fs.uploadFile(params.content, params.remotePath);\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n // Mints a signed, time-limited https preview URL exposing the given sandbox port.\n private async getPreviewUrl(params: { sandboxId: string; port: number; expiresInSeconds: number }): Promise<string> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n const signed = await sandbox.getSignedPreviewUrl(params.port, params.expiresInSeconds);\n return signed.url;\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n this.logger.error('Failed to create signed preview URL', extractErrorLogFields(e));\n throw e;\n }\n });\n }\n\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: async (sandboxId: string) => {\n const previewUrl = await this.getPreviewUrl({\n sandboxId,\n port: this.natsBridgePort,\n expiresInSeconds: this.previewUrlExpirySeconds,\n });\n return httpUrlToWsUrl(previewUrl);\n },\n sandboxClientNatsUrl: `ws://localhost:${String(this.natsBridgePort)}`,\n logger: this.logger,\n mcpClientInstall: {\n remotePath: join('/opt', 'tf', 'mcp-client', 'mcp_client.py'),\n pathBinSymlink: join('/usr', 'local', 'bin', 'mcp-client'),\n },\n });\n }\n\n getAdditionalInstructions(): string | undefined {\n return undefined;\n }\n\n // Isolated container: image-absolute layout. GIT_CONFIG store --file needs an absolute path.\n // /opt/tf/{uploads,skills,tool-results,skill_downloader.py,.git-credentials}\n // /opt/tfy/mcp-client/mcp_client.py + /usr/local/bin/mcp-client (image PATH; no layout bin)\n getToolResultDumpDir(): string {\n return join('/opt', 'tf', 'tool-results');\n }\n\n getGitCredentialsPath(): string {\n return join('/opt', 'tf', '.git-credentials');\n }\n\n getFileUploadsDir(): string {\n return join('/opt', 'tf', 'uploads');\n }\n\n getSkillsDir(): string {\n return join('/opt', 'tf', 'skills');\n }\n\n getSkillDownloaderPath(): string {\n return join('/opt', 'tf', 'skill_downloader.py');\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -398,7 +398,7 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
398
398
|
return void 0;
|
|
399
399
|
}
|
|
400
400
|
// Isolated container: image-absolute layout. GIT_CONFIG store --file needs an absolute path.
|
|
401
|
-
// /opt/tf/{uploads,skills,tool-results,
|
|
401
|
+
// /opt/tf/{uploads,skills,tool-results,skill_downloader.py,.git-credentials}
|
|
402
402
|
// /opt/tfy/mcp-client/mcp_client.py + /usr/local/bin/mcp-client (image PATH; no layout bin)
|
|
403
403
|
getToolResultDumpDir() {
|
|
404
404
|
return join("/opt", "tf", "tool-results");
|
|
@@ -412,8 +412,8 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
412
412
|
getSkillsDir() {
|
|
413
413
|
return join("/opt", "tf", "skills");
|
|
414
414
|
}
|
|
415
|
-
|
|
416
|
-
return join("/opt", "tf", "
|
|
415
|
+
getSkillDownloaderPath() {
|
|
416
|
+
return join("/opt", "tf", "skill_downloader.py");
|
|
417
417
|
}
|
|
418
418
|
};
|
|
419
419
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/sandbox/provider/DaytonaProvider.ts"],"sourcesContent":["import type { Sandbox, Snapshot } from '@daytona/sdk';\nimport { Daytona, DaytonaError } from '@daytona/sdk';\nimport { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxNotAvailableError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_PREVIEW_URL_EXPIRY_SECONDS, DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport type { ExecResult, SandboxBuild, SandboxExecParams, SandboxFileInfo, SandboxProvider } from './Provider';\n\nconst SANDBOX_NOT_FOUND_STATUS = 404;\n/** Another replica already registered this build name; its create is the one that counts. */\nconst SNAPSHOT_CONFLICT_STATUS = 409;\nconst SANDBOX_STATE_STARTED = 'started';\n\nconst BUILD_STATE_ACTIVE = 'active';\nconst BUILD_STATE_INACTIVE = 'inactive';\nconst BUILD_STATE_ERROR = 'error';\nconst BUILD_STATE_BUILD_FAILED = 'build_failed';\n\nconst IMAGE_BUILD_NAME_PREFIX = 'trueforge-build-';\n/** Same default the Daytona SDK applies when `DaytonaConfig.apiUrl` is omitted. */\nconst DEFAULT_DAYTONA_API_URL = 'https://app.daytona.io/api';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Digest portion of a container image reference (the tag/digest after the final `:`)\n * The release image is always published with an explicit digest tag\n */\nfunction imageDigest(image: string): string {\n const lastSegment = image.slice(image.lastIndexOf('/') + 1);\n const colon = lastSegment.lastIndexOf(':');\n if (colon === -1) {\n throw new Error(`Sandbox image reference has no tag/digest: ${image}`);\n }\n return lastSegment.slice(colon + 1);\n}\n\n/** Deterministic build name per image digest so every server replica converges on one build. */\nfunction deriveImageBuildName(digest: string): string {\n return `${IMAGE_BUILD_NAME_PREFIX}${digest}`;\n}\n\n/** Terminal-failure build states: a build stuck here never becomes ready on its own. */\nfunction isFailedBuildState(state: Snapshot['state']): boolean {\n return state === BUILD_STATE_ERROR || state === BUILD_STATE_BUILD_FAILED;\n}\n\n/** Convert Daytona https preview URLs to wss for the NATS client. */\nfunction httpUrlToWsUrl(url: string): string {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') {\n parsed.protocol = 'wss:';\n } else if (parsed.protocol === 'http:') {\n parsed.protocol = 'ws:';\n }\n return parsed.toString();\n}\n\nexport interface DaytonaSandboxProviderOptions {\n /** Caller-owned Daytona SDK client (credentials / lifetime). */\n client: Daytona;\n /**\n * Same API key the client was built with; used for the register-only snapshot POST because the\n * SDK's `snapshot.create` polls to a terminal state instead of returning once registered.\n */\n apiKey: string;\n /** Daytona API base URL (including `/api`). Defaults to the SDK's public cloud endpoint. */\n apiUrl?: string | undefined;\n tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n sandboxImage: string;\n /**\n * Daytona snapshot name to clone sandboxes from. When omitted it is derived from the image\n * digest. Callers that create sandboxes pass the persisted build_ref so cloning targets the\n * snapshot that was actually built, not a speculative name derived from the current image.\n */\n buildRef?: string | undefined;\n timeoutMs: number;\n autoStopIntervalInMinutes: number;\n autoArchiveIntervalInMinutes: number;\n autoDeleteIntervalInMinutes: number;\n fileMaxBytesForDownload: number;\n /** Defaults to the built-in sandbox NATS WebSocket port (4444). */\n natsBridgePort?: number;\n /** Defaults to 1 hour (same as the gateway's max agent execution time). */\n previewUrlExpirySeconds?: number;\n logger: Logger;\n}\n\nexport class DaytonaSandboxProvider implements SandboxProvider {\n readonly type = 'daytona';\n private readonly tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n private readonly imageUri: string;\n /** Daytona snapshot name sandboxes are cloned from; the persisted build_ref, or derived from the image digest. */\n private readonly buildRef: string;\n private readonly timeoutMs: number;\n private readonly autoStopIntervalInMinutes: number;\n private readonly autoArchiveIntervalInMinutes: number;\n private readonly autoDeleteIntervalInMinutes: number;\n private readonly fileMaxBytesForDownload: number;\n private readonly natsBridgePort: number;\n private readonly previewUrlExpirySeconds: number;\n private readonly apiKey: string;\n private readonly apiUrl: string;\n private readonly logger: Logger;\n private readonly daytona: Daytona;\n private static readonly cachedSandboxes = new Map<string, { sandbox: Sandbox; defaultTimeoutMs: number }>();\n // De-dupes concurrent recovery attempts on the same sandbox to a single refreshData+start round-trip.\n private static readonly inFlightRecoveries = new Map<string, Promise<boolean>>();\n\n constructor(options: DaytonaSandboxProviderOptions) {\n this.daytona = options.client;\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? DEFAULT_DAYTONA_API_URL;\n this.tenantName = options.tenantName;\n this.imageUri = options.sandboxImage;\n this.buildRef = options.buildRef ?? deriveImageBuildName(imageDigest(options.sandboxImage));\n this.timeoutMs = options.timeoutMs;\n this.autoStopIntervalInMinutes = options.autoStopIntervalInMinutes;\n this.autoArchiveIntervalInMinutes = options.autoArchiveIntervalInMinutes;\n this.autoDeleteIntervalInMinutes = options.autoDeleteIntervalInMinutes;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.natsBridgePort = options.natsBridgePort ?? DEFAULT_SANDBOX_NATS_WS_PORT;\n this.previewUrlExpirySeconds = options.previewUrlExpirySeconds ?? DEFAULT_PREVIEW_URL_EXPIRY_SECONDS;\n this.logger = options.logger.child({ module: 'DaytonaProvider' });\n }\n\n private async getOrCreateSandbox(sandboxId?: string): Promise<{ sandbox: Sandbox; defaultTimeoutMs: number }> {\n if (sandboxId) {\n validateSandboxOwnedByTenant({ sandboxId, tenantName: this.tenantName });\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n if (cached) {\n return cached;\n }\n }\n\n const sandbox = sandboxId\n ? await this.restoreExistingSandbox(sandboxId)\n : await this.daytona.create({\n name: `${this.tenantName}.${randomUUID()}`,\n snapshot: this.buildRef,\n autoStopInterval: this.autoStopIntervalInMinutes,\n autoArchiveInterval: this.autoArchiveIntervalInMinutes,\n autoDeleteInterval: this.autoDeleteIntervalInMinutes,\n });\n\n const entry = { sandbox, defaultTimeoutMs: this.timeoutMs };\n DaytonaSandboxProvider.cachedSandboxes.set(sandbox.name, entry);\n return entry;\n }\n\n // Returns true iff the caller should retry: either we restarted a stopped sandbox, or the cache entry is missing and the retry will rebuild it via the cold path.\n private static recoverSandboxIfStopped(sandboxId: string): Promise<boolean> {\n const existing = DaytonaSandboxProvider.inFlightRecoveries.get(sandboxId);\n if (existing) {\n return existing;\n }\n\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n // Cache may have been evicted by a concurrent error path; signal retry so getOrCreateSandbox rebuilds via restoreExistingSandbox.\n if (!cached) {\n return Promise.resolve(true);\n }\n\n const recovery = (async () => {\n await cached.sandbox.refreshData();\n if (cached.sandbox.state === SANDBOX_STATE_STARTED) {\n return false;\n }\n // start() covers both stopped and archived per Daytona; throws on unrecoverable states (error/destroyed).\n await cached.sandbox.start();\n return true;\n })().finally(() => {\n DaytonaSandboxProvider.inFlightRecoveries.delete(sandboxId);\n });\n\n DaytonaSandboxProvider.inFlightRecoveries.set(sandboxId, recovery);\n return recovery;\n }\n\n private async executeWithSandboxRecovery<T>(sandboxId: string, operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (originalError) {\n // TODO: Narrow to a specific Daytona error code once @daytona/sdk exposes one for \"sandbox not running\".\n if (!(originalError instanceof DaytonaError)) {\n throw originalError;\n }\n\n let recovered: boolean;\n try {\n recovered = await DaytonaSandboxProvider.recoverSandboxIfStopped(sandboxId);\n } catch (recoveryError) {\n this.logger.error('Sandbox recovery failed', {\n ...extractErrorLogFields(recoveryError),\n originalError: extractErrorLogFields(originalError),\n });\n throw new Error('Sandbox is unavailable; recovery attempt failed.', { cause: recoveryError });\n }\n\n if (!recovered) {\n throw originalError;\n }\n\n try {\n return await operation();\n } catch (retryError) {\n this.logger.error('Sandbox operation failed after successful recovery', {\n ...extractErrorLogFields(retryError),\n originalError: extractErrorLogFields(originalError),\n });\n // Rethrow as-is so callers can match domain error types (SandboxPathIsDirectoryError, DaytonaError statusCode mappings, etc.).\n throw retryError;\n }\n }\n }\n\n private async restoreExistingSandbox(sandboxId: string): Promise<Sandbox> {\n try {\n const sandbox = await this.daytona.get(sandboxId);\n if (sandbox.state !== SANDBOX_STATE_STARTED) {\n await sandbox.start();\n }\n return sandbox;\n } catch (ex) {\n if (ex instanceof DaytonaError && ex.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxNotAvailableError(sandboxId);\n }\n throw ex;\n }\n }\n\n async createSandbox(): Promise<{ sandboxId: string }> {\n return context.with(suppressTracing(context.active()), async () => {\n const { sandbox } = await this.getOrCreateSandbox();\n this.logger.debug(`Sandbox created: name=${sandbox.name}`);\n return { sandboxId: sandbox.name };\n });\n }\n\n /** Resolves undefined when no snapshot carries that name; auth/other failures throw. */\n private async getSnapshot(name: string): Promise<Snapshot | undefined> {\n try {\n return await this.daytona.snapshot.get(name);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * Drops a build stuck in a terminal-failure state so a fresh create can reuse its\n * deterministic name. A concurrent replica may have deleted it already (404) — that\n * is fine and treated as success.\n */\n private async deleteFailedBuild(snapshot: Snapshot): Promise<void> {\n try {\n await this.daytona.snapshot.delete(snapshot);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return;\n }\n throw error;\n }\n }\n\n private toBuild(state: string, errorReason: string | null): SandboxBuild {\n const metadata = { build_ref: this.buildRef, image_uri: this.imageUri };\n switch (state) {\n case BUILD_STATE_ACTIVE:\n return { status: 'ready', reason: null, metadata };\n case BUILD_STATE_ERROR:\n case BUILD_STATE_BUILD_FAILED:\n return { status: 'failed', reason: errorReason ?? `Sandbox image build failed (${state}).`, metadata };\n default:\n // pending / building / pulling / removing / future states: not ready yet.\n return { status: 'pending', reason: `Sandbox image build in progress (${state}).`, metadata };\n }\n }\n\n /**\n * Registers the snapshot and returns its initial state without waiting for the build.\n *\n * The SDK's `snapshot.create` issues this same POST and then polls until the snapshot is active\n * or failed (minutes on a cold image pull). Configure only needs the registration result, so\n * credential errors surface on the request while build progress stays observable via `getSnapshot`.\n */\n private async registerSnapshot(): Promise<{ state: string; errorReason: string | null }> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}/snapshots`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: this.buildRef, imageName: this.imageUri }),\n });\n } catch (error) {\n throw new Error('Daytona snapshot registration request failed.', { cause: error });\n }\n\n const body: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n isRecord(body) && typeof body['message'] === 'string'\n ? body['message']\n : `Daytona snapshot registration failed (${String(response.status)})`;\n throw new DaytonaError(message, response.status);\n }\n if (!isRecord(body) || typeof body['state'] !== 'string') {\n throw new DaytonaError(\"Failed to register snapshot. Daytona didn't return a snapshot state.\");\n }\n return {\n state: body['state'],\n errorReason: typeof body['errorReason'] === 'string' ? body['errorReason'] : null,\n };\n }\n\n async buildImage(): Promise<SandboxBuild> {\n const existing = await this.getSnapshot(this.buildRef);\n if (existing) {\n // Daytona parks unused snapshots as 'inactive' after ~2 idle weeks; reactivate rather than rebuild.\n if (existing.state === BUILD_STATE_INACTIVE) {\n const activated = await this.daytona.snapshot.activate(existing);\n return this.toBuild(activated.state, activated.errorReason);\n }\n if (!isFailedBuildState(existing.state)) {\n return this.toBuild(existing.state, existing.errorReason);\n }\n // A failed build keeps the deterministic name occupied and never self-heals, so\n // re-saving settings (which calls buildImage) could never retry. Drop it, then recreate.\n await this.deleteFailedBuild(existing);\n }\n\n try {\n const registered = await this.registerSnapshot();\n // Registration returns pending almost always; map whatever Daytona sent so a fast\n // active/error still surfaces correctly without a follow-up GET.\n return this.toBuild(registered.state, registered.errorReason);\n } catch (error) {\n // A losing concurrent create is not a build failure: the winner owns the deterministic name.\n if (error instanceof DaytonaError && error.statusCode === SNAPSHOT_CONFLICT_STATUS) {\n this.logger.info(`Daytona snapshot already created concurrently: name=${this.buildRef}`);\n return {\n status: 'pending',\n reason: 'Sandbox image build started by another server replica.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n throw error;\n }\n }\n\n async getImageBuildStatus(): Promise<SandboxBuild> {\n const snapshot = await this.getSnapshot(this.buildRef);\n // Read-only: a missing build reports pending; PUT /settings/sandbox-providers starts the build.\n if (!snapshot) {\n return {\n status: 'pending',\n reason: 'Sandbox image build not started.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n return this.toBuild(snapshot.state, snapshot.errorReason);\n }\n\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox, defaultTimeoutMs } = await this.getOrCreateSandbox(params.sandboxId);\n const response = await sandbox.process.executeCommand(\n params.command,\n params.cwd,\n params.env ?? {},\n params.timeoutSeconds ?? defaultTimeoutMs / 1000,\n );\n return {\n success: true,\n response: { exitCode: response.exitCode, result: response.result },\n };\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n if (e instanceof SandboxNotAvailableError) {\n throw e;\n }\n this.logger.error('Sandbox execution error', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n }\n });\n }\n\n private async getFileInfo(sandbox: Sandbox, path: string): Promise<SandboxFileInfo> {\n const details = await sandbox.fs.getFileDetails(path);\n return { size: details.size, isDir: details.isDir };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n\n const info = await this.getFileInfo(sandbox, params.path);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n return await sandbox.fs.downloadFile(params.path);\n });\n } catch (e: unknown) {\n if (e instanceof SandboxPathIsDirectoryError || e instanceof SandboxFileTooLargeError) {\n throw e;\n }\n if (e instanceof DaytonaError && e.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxFileNotFoundError(params.path);\n }\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n await sandbox.fs.uploadFile(params.content, params.remotePath);\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n // Mints a signed, time-limited https preview URL exposing the given sandbox port.\n private async getPreviewUrl(params: { sandboxId: string; port: number; expiresInSeconds: number }): Promise<string> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n const signed = await sandbox.getSignedPreviewUrl(params.port, params.expiresInSeconds);\n return signed.url;\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n this.logger.error('Failed to create signed preview URL', extractErrorLogFields(e));\n throw e;\n }\n });\n }\n\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: async (sandboxId: string) => {\n const previewUrl = await this.getPreviewUrl({\n sandboxId,\n port: this.natsBridgePort,\n expiresInSeconds: this.previewUrlExpirySeconds,\n });\n return httpUrlToWsUrl(previewUrl);\n },\n sandboxClientNatsUrl: `ws://localhost:${String(this.natsBridgePort)}`,\n logger: this.logger,\n mcpClientInstall: {\n remotePath: join('/opt', 'tf', 'mcp-client', 'mcp_client.py'),\n pathBinSymlink: join('/usr', 'local', 'bin', 'mcp-client'),\n },\n });\n }\n\n getAdditionalInstructions(): string | undefined {\n return undefined;\n }\n\n // Isolated container: image-absolute layout. GIT_CONFIG store --file needs an absolute path.\n // /opt/tf/{uploads,skills,tool-results,git_downloader.py,.git-credentials}\n // /opt/tfy/mcp-client/mcp_client.py + /usr/local/bin/mcp-client (image PATH; no layout bin)\n getToolResultDumpDir(): string {\n return join('/opt', 'tf', 'tool-results');\n }\n\n getGitCredentialsPath(): string {\n return join('/opt', 'tf', '.git-credentials');\n }\n\n getFileUploadsDir(): string {\n return join('/opt', 'tf', 'uploads');\n }\n\n getSkillsDir(): string {\n return join('/opt', 'tf', 'skills');\n }\n\n getGitDownloaderPath(): string {\n return join('/opt', 'tf', 'git_downloader.py');\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/sandbox/provider/DaytonaProvider.ts"],"sourcesContent":["import type { Sandbox, Snapshot } from '@daytona/sdk';\nimport { Daytona, DaytonaError } from '@daytona/sdk';\nimport { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxNotAvailableError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_PREVIEW_URL_EXPIRY_SECONDS, DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport type { ExecResult, SandboxBuild, SandboxExecParams, SandboxFileInfo, SandboxProvider } from './Provider';\n\nconst SANDBOX_NOT_FOUND_STATUS = 404;\n/** Another replica already registered this build name; its create is the one that counts. */\nconst SNAPSHOT_CONFLICT_STATUS = 409;\nconst SANDBOX_STATE_STARTED = 'started';\n\nconst BUILD_STATE_ACTIVE = 'active';\nconst BUILD_STATE_INACTIVE = 'inactive';\nconst BUILD_STATE_ERROR = 'error';\nconst BUILD_STATE_BUILD_FAILED = 'build_failed';\n\nconst IMAGE_BUILD_NAME_PREFIX = 'trueforge-build-';\n/** Same default the Daytona SDK applies when `DaytonaConfig.apiUrl` is omitted. */\nconst DEFAULT_DAYTONA_API_URL = 'https://app.daytona.io/api';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/**\n * Digest portion of a container image reference (the tag/digest after the final `:`)\n * The release image is always published with an explicit digest tag\n */\nfunction imageDigest(image: string): string {\n const lastSegment = image.slice(image.lastIndexOf('/') + 1);\n const colon = lastSegment.lastIndexOf(':');\n if (colon === -1) {\n throw new Error(`Sandbox image reference has no tag/digest: ${image}`);\n }\n return lastSegment.slice(colon + 1);\n}\n\n/** Deterministic build name per image digest so every server replica converges on one build. */\nfunction deriveImageBuildName(digest: string): string {\n return `${IMAGE_BUILD_NAME_PREFIX}${digest}`;\n}\n\n/** Terminal-failure build states: a build stuck here never becomes ready on its own. */\nfunction isFailedBuildState(state: Snapshot['state']): boolean {\n return state === BUILD_STATE_ERROR || state === BUILD_STATE_BUILD_FAILED;\n}\n\n/** Convert Daytona https preview URLs to wss for the NATS client. */\nfunction httpUrlToWsUrl(url: string): string {\n const parsed = new URL(url);\n if (parsed.protocol === 'https:') {\n parsed.protocol = 'wss:';\n } else if (parsed.protocol === 'http:') {\n parsed.protocol = 'ws:';\n }\n return parsed.toString();\n}\n\nexport interface DaytonaSandboxProviderOptions {\n /** Caller-owned Daytona SDK client (credentials / lifetime). */\n client: Daytona;\n /**\n * Same API key the client was built with; used for the register-only snapshot POST because the\n * SDK's `snapshot.create` polls to a terminal state instead of returning once registered.\n */\n apiKey: string;\n /** Daytona API base URL (including `/api`). Defaults to the SDK's public cloud endpoint. */\n apiUrl?: string | undefined;\n tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n sandboxImage: string;\n /**\n * Daytona snapshot name to clone sandboxes from. When omitted it is derived from the image\n * digest. Callers that create sandboxes pass the persisted build_ref so cloning targets the\n * snapshot that was actually built, not a speculative name derived from the current image.\n */\n buildRef?: string | undefined;\n timeoutMs: number;\n autoStopIntervalInMinutes: number;\n autoArchiveIntervalInMinutes: number;\n autoDeleteIntervalInMinutes: number;\n fileMaxBytesForDownload: number;\n /** Defaults to the built-in sandbox NATS WebSocket port (4444). */\n natsBridgePort?: number;\n /** Defaults to 1 hour (same as the gateway's max agent execution time). */\n previewUrlExpirySeconds?: number;\n logger: Logger;\n}\n\nexport class DaytonaSandboxProvider implements SandboxProvider {\n readonly type = 'daytona';\n private readonly tenantName: string;\n /** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */\n private readonly imageUri: string;\n /** Daytona snapshot name sandboxes are cloned from; the persisted build_ref, or derived from the image digest. */\n private readonly buildRef: string;\n private readonly timeoutMs: number;\n private readonly autoStopIntervalInMinutes: number;\n private readonly autoArchiveIntervalInMinutes: number;\n private readonly autoDeleteIntervalInMinutes: number;\n private readonly fileMaxBytesForDownload: number;\n private readonly natsBridgePort: number;\n private readonly previewUrlExpirySeconds: number;\n private readonly apiKey: string;\n private readonly apiUrl: string;\n private readonly logger: Logger;\n private readonly daytona: Daytona;\n private static readonly cachedSandboxes = new Map<string, { sandbox: Sandbox; defaultTimeoutMs: number }>();\n // De-dupes concurrent recovery attempts on the same sandbox to a single refreshData+start round-trip.\n private static readonly inFlightRecoveries = new Map<string, Promise<boolean>>();\n\n constructor(options: DaytonaSandboxProviderOptions) {\n this.daytona = options.client;\n this.apiKey = options.apiKey;\n this.apiUrl = options.apiUrl ?? DEFAULT_DAYTONA_API_URL;\n this.tenantName = options.tenantName;\n this.imageUri = options.sandboxImage;\n this.buildRef = options.buildRef ?? deriveImageBuildName(imageDigest(options.sandboxImage));\n this.timeoutMs = options.timeoutMs;\n this.autoStopIntervalInMinutes = options.autoStopIntervalInMinutes;\n this.autoArchiveIntervalInMinutes = options.autoArchiveIntervalInMinutes;\n this.autoDeleteIntervalInMinutes = options.autoDeleteIntervalInMinutes;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.natsBridgePort = options.natsBridgePort ?? DEFAULT_SANDBOX_NATS_WS_PORT;\n this.previewUrlExpirySeconds = options.previewUrlExpirySeconds ?? DEFAULT_PREVIEW_URL_EXPIRY_SECONDS;\n this.logger = options.logger.child({ module: 'DaytonaProvider' });\n }\n\n private async getOrCreateSandbox(sandboxId?: string): Promise<{ sandbox: Sandbox; defaultTimeoutMs: number }> {\n if (sandboxId) {\n validateSandboxOwnedByTenant({ sandboxId, tenantName: this.tenantName });\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n if (cached) {\n return cached;\n }\n }\n\n const sandbox = sandboxId\n ? await this.restoreExistingSandbox(sandboxId)\n : await this.daytona.create({\n name: `${this.tenantName}.${randomUUID()}`,\n snapshot: this.buildRef,\n autoStopInterval: this.autoStopIntervalInMinutes,\n autoArchiveInterval: this.autoArchiveIntervalInMinutes,\n autoDeleteInterval: this.autoDeleteIntervalInMinutes,\n });\n\n const entry = { sandbox, defaultTimeoutMs: this.timeoutMs };\n DaytonaSandboxProvider.cachedSandboxes.set(sandbox.name, entry);\n return entry;\n }\n\n // Returns true iff the caller should retry: either we restarted a stopped sandbox, or the cache entry is missing and the retry will rebuild it via the cold path.\n private static recoverSandboxIfStopped(sandboxId: string): Promise<boolean> {\n const existing = DaytonaSandboxProvider.inFlightRecoveries.get(sandboxId);\n if (existing) {\n return existing;\n }\n\n const cached = DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);\n // Cache may have been evicted by a concurrent error path; signal retry so getOrCreateSandbox rebuilds via restoreExistingSandbox.\n if (!cached) {\n return Promise.resolve(true);\n }\n\n const recovery = (async () => {\n await cached.sandbox.refreshData();\n if (cached.sandbox.state === SANDBOX_STATE_STARTED) {\n return false;\n }\n // start() covers both stopped and archived per Daytona; throws on unrecoverable states (error/destroyed).\n await cached.sandbox.start();\n return true;\n })().finally(() => {\n DaytonaSandboxProvider.inFlightRecoveries.delete(sandboxId);\n });\n\n DaytonaSandboxProvider.inFlightRecoveries.set(sandboxId, recovery);\n return recovery;\n }\n\n private async executeWithSandboxRecovery<T>(sandboxId: string, operation: () => Promise<T>): Promise<T> {\n try {\n return await operation();\n } catch (originalError) {\n // TODO: Narrow to a specific Daytona error code once @daytona/sdk exposes one for \"sandbox not running\".\n if (!(originalError instanceof DaytonaError)) {\n throw originalError;\n }\n\n let recovered: boolean;\n try {\n recovered = await DaytonaSandboxProvider.recoverSandboxIfStopped(sandboxId);\n } catch (recoveryError) {\n this.logger.error('Sandbox recovery failed', {\n ...extractErrorLogFields(recoveryError),\n originalError: extractErrorLogFields(originalError),\n });\n throw new Error('Sandbox is unavailable; recovery attempt failed.', { cause: recoveryError });\n }\n\n if (!recovered) {\n throw originalError;\n }\n\n try {\n return await operation();\n } catch (retryError) {\n this.logger.error('Sandbox operation failed after successful recovery', {\n ...extractErrorLogFields(retryError),\n originalError: extractErrorLogFields(originalError),\n });\n // Rethrow as-is so callers can match domain error types (SandboxPathIsDirectoryError, DaytonaError statusCode mappings, etc.).\n throw retryError;\n }\n }\n }\n\n private async restoreExistingSandbox(sandboxId: string): Promise<Sandbox> {\n try {\n const sandbox = await this.daytona.get(sandboxId);\n if (sandbox.state !== SANDBOX_STATE_STARTED) {\n await sandbox.start();\n }\n return sandbox;\n } catch (ex) {\n if (ex instanceof DaytonaError && ex.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxNotAvailableError(sandboxId);\n }\n throw ex;\n }\n }\n\n async createSandbox(): Promise<{ sandboxId: string }> {\n return context.with(suppressTracing(context.active()), async () => {\n const { sandbox } = await this.getOrCreateSandbox();\n this.logger.debug(`Sandbox created: name=${sandbox.name}`);\n return { sandboxId: sandbox.name };\n });\n }\n\n /** Resolves undefined when no snapshot carries that name; auth/other failures throw. */\n private async getSnapshot(name: string): Promise<Snapshot | undefined> {\n try {\n return await this.daytona.snapshot.get(name);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return undefined;\n }\n throw error;\n }\n }\n\n /**\n * Drops a build stuck in a terminal-failure state so a fresh create can reuse its\n * deterministic name. A concurrent replica may have deleted it already (404) — that\n * is fine and treated as success.\n */\n private async deleteFailedBuild(snapshot: Snapshot): Promise<void> {\n try {\n await this.daytona.snapshot.delete(snapshot);\n } catch (error) {\n if (error instanceof DaytonaError && error.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n return;\n }\n throw error;\n }\n }\n\n private toBuild(state: string, errorReason: string | null): SandboxBuild {\n const metadata = { build_ref: this.buildRef, image_uri: this.imageUri };\n switch (state) {\n case BUILD_STATE_ACTIVE:\n return { status: 'ready', reason: null, metadata };\n case BUILD_STATE_ERROR:\n case BUILD_STATE_BUILD_FAILED:\n return { status: 'failed', reason: errorReason ?? `Sandbox image build failed (${state}).`, metadata };\n default:\n // pending / building / pulling / removing / future states: not ready yet.\n return { status: 'pending', reason: `Sandbox image build in progress (${state}).`, metadata };\n }\n }\n\n /**\n * Registers the snapshot and returns its initial state without waiting for the build.\n *\n * The SDK's `snapshot.create` issues this same POST and then polls until the snapshot is active\n * or failed (minutes on a cold image pull). Configure only needs the registration result, so\n * credential errors surface on the request while build progress stays observable via `getSnapshot`.\n */\n private async registerSnapshot(): Promise<{ state: string; errorReason: string | null }> {\n let response: Response;\n try {\n response = await fetch(`${this.apiUrl}/snapshots`, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ name: this.buildRef, imageName: this.imageUri }),\n });\n } catch (error) {\n throw new Error('Daytona snapshot registration request failed.', { cause: error });\n }\n\n const body: unknown = await response.json().catch(() => null);\n if (!response.ok) {\n const message =\n isRecord(body) && typeof body['message'] === 'string'\n ? body['message']\n : `Daytona snapshot registration failed (${String(response.status)})`;\n throw new DaytonaError(message, response.status);\n }\n if (!isRecord(body) || typeof body['state'] !== 'string') {\n throw new DaytonaError(\"Failed to register snapshot. Daytona didn't return a snapshot state.\");\n }\n return {\n state: body['state'],\n errorReason: typeof body['errorReason'] === 'string' ? body['errorReason'] : null,\n };\n }\n\n async buildImage(): Promise<SandboxBuild> {\n const existing = await this.getSnapshot(this.buildRef);\n if (existing) {\n // Daytona parks unused snapshots as 'inactive' after ~2 idle weeks; reactivate rather than rebuild.\n if (existing.state === BUILD_STATE_INACTIVE) {\n const activated = await this.daytona.snapshot.activate(existing);\n return this.toBuild(activated.state, activated.errorReason);\n }\n if (!isFailedBuildState(existing.state)) {\n return this.toBuild(existing.state, existing.errorReason);\n }\n // A failed build keeps the deterministic name occupied and never self-heals, so\n // re-saving settings (which calls buildImage) could never retry. Drop it, then recreate.\n await this.deleteFailedBuild(existing);\n }\n\n try {\n const registered = await this.registerSnapshot();\n // Registration returns pending almost always; map whatever Daytona sent so a fast\n // active/error still surfaces correctly without a follow-up GET.\n return this.toBuild(registered.state, registered.errorReason);\n } catch (error) {\n // A losing concurrent create is not a build failure: the winner owns the deterministic name.\n if (error instanceof DaytonaError && error.statusCode === SNAPSHOT_CONFLICT_STATUS) {\n this.logger.info(`Daytona snapshot already created concurrently: name=${this.buildRef}`);\n return {\n status: 'pending',\n reason: 'Sandbox image build started by another server replica.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n throw error;\n }\n }\n\n async getImageBuildStatus(): Promise<SandboxBuild> {\n const snapshot = await this.getSnapshot(this.buildRef);\n // Read-only: a missing build reports pending; PUT /settings/sandbox-providers starts the build.\n if (!snapshot) {\n return {\n status: 'pending',\n reason: 'Sandbox image build not started.',\n metadata: { build_ref: this.buildRef, image_uri: this.imageUri },\n };\n }\n return this.toBuild(snapshot.state, snapshot.errorReason);\n }\n\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox, defaultTimeoutMs } = await this.getOrCreateSandbox(params.sandboxId);\n const response = await sandbox.process.executeCommand(\n params.command,\n params.cwd,\n params.env ?? {},\n params.timeoutSeconds ?? defaultTimeoutMs / 1000,\n );\n return {\n success: true,\n response: { exitCode: response.exitCode, result: response.result },\n };\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n if (e instanceof SandboxNotAvailableError) {\n throw e;\n }\n this.logger.error('Sandbox execution error', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n }\n });\n }\n\n private async getFileInfo(sandbox: Sandbox, path: string): Promise<SandboxFileInfo> {\n const details = await sandbox.fs.getFileDetails(path);\n return { size: details.size, isDir: details.isDir };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n\n const info = await this.getFileInfo(sandbox, params.path);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n return await sandbox.fs.downloadFile(params.path);\n });\n } catch (e: unknown) {\n if (e instanceof SandboxPathIsDirectoryError || e instanceof SandboxFileTooLargeError) {\n throw e;\n }\n if (e instanceof DaytonaError && e.statusCode === SANDBOX_NOT_FOUND_STATUS) {\n throw new SandboxFileNotFoundError(params.path);\n }\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n await sandbox.fs.uploadFile(params.content, params.remotePath);\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n throw e;\n }\n });\n }\n\n // Mints a signed, time-limited https preview URL exposing the given sandbox port.\n private async getPreviewUrl(params: { sandboxId: string; port: number; expiresInSeconds: number }): Promise<string> {\n return context.with(suppressTracing(context.active()), async () => {\n try {\n return await this.executeWithSandboxRecovery(params.sandboxId, async () => {\n const { sandbox } = await this.getOrCreateSandbox(params.sandboxId);\n const signed = await sandbox.getSignedPreviewUrl(params.port, params.expiresInSeconds);\n return signed.url;\n });\n } catch (e: unknown) {\n DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);\n this.logger.error('Failed to create signed preview URL', extractErrorLogFields(e));\n throw e;\n }\n });\n }\n\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: async (sandboxId: string) => {\n const previewUrl = await this.getPreviewUrl({\n sandboxId,\n port: this.natsBridgePort,\n expiresInSeconds: this.previewUrlExpirySeconds,\n });\n return httpUrlToWsUrl(previewUrl);\n },\n sandboxClientNatsUrl: `ws://localhost:${String(this.natsBridgePort)}`,\n logger: this.logger,\n mcpClientInstall: {\n remotePath: join('/opt', 'tf', 'mcp-client', 'mcp_client.py'),\n pathBinSymlink: join('/usr', 'local', 'bin', 'mcp-client'),\n },\n });\n }\n\n getAdditionalInstructions(): string | undefined {\n return undefined;\n }\n\n // Isolated container: image-absolute layout. GIT_CONFIG store --file needs an absolute path.\n // /opt/tf/{uploads,skills,tool-results,skill_downloader.py,.git-credentials}\n // /opt/tfy/mcp-client/mcp_client.py + /usr/local/bin/mcp-client (image PATH; no layout bin)\n getToolResultDumpDir(): string {\n return join('/opt', 'tf', 'tool-results');\n }\n\n getGitCredentialsPath(): string {\n return join('/opt', 'tf', '.git-credentials');\n }\n\n getFileUploadsDir(): string {\n return join('/opt', 'tf', 'uploads');\n }\n\n getSkillsDir(): string {\n return join('/opt', 'tf', 'skills');\n }\n\n getSkillDownloaderPath(): string {\n return join('/opt', 'tf', 'skill_downloader.py');\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -34,7 +34,23 @@ export interface SandboxExecParams {
|
|
|
34
34
|
/** Overrides the provider's default exec timeout (e.g. for long skill downloads). */
|
|
35
35
|
timeoutSeconds?: number | undefined;
|
|
36
36
|
}
|
|
37
|
-
|
|
37
|
+
/** File uploaded before a sandbox init command runs. */
|
|
38
|
+
export interface SandboxInitUpload {
|
|
39
|
+
remotePath: string;
|
|
40
|
+
content: Buffer;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Init step from a producer (e.g. skill mounter).
|
|
44
|
+
* Sandbox supplies `sandboxId` when it runs the command.
|
|
45
|
+
*/
|
|
46
|
+
export interface SandboxInit {
|
|
47
|
+
command: string;
|
|
48
|
+
env?: Record<string, string> | undefined;
|
|
49
|
+
/** Exec timeout for this init command (seconds). Skill downloads use a long value. */
|
|
50
|
+
timeoutSeconds: number;
|
|
51
|
+
/** Files to upload before the init command runs (e.g. requested skills JSON). */
|
|
52
|
+
uploads: readonly SandboxInitUpload[];
|
|
53
|
+
}
|
|
38
54
|
export type SandboxBuildStatus = 'pending' | 'ready' | 'failed';
|
|
39
55
|
/** Provider-specific opaque build metadata (string map). */
|
|
40
56
|
export type SandboxBuildMetadata = Record<string, string>;
|
|
@@ -72,10 +88,10 @@ export interface SandboxProvider {
|
|
|
72
88
|
getGitCredentialsPath(sandboxId: string): string;
|
|
73
89
|
/** Directory for user-uploaded files (absolute, or cwd-relative when the provider has no global FS). */
|
|
74
90
|
getFileUploadsDir(sandboxId: string): string;
|
|
75
|
-
/** Directory where
|
|
91
|
+
/** Directory where skill mounts are materialized. */
|
|
76
92
|
getSkillsDir(sandboxId: string): string;
|
|
77
|
-
/** Path the
|
|
78
|
-
|
|
93
|
+
/** Path the skill downloader script is written to before it runs. */
|
|
94
|
+
getSkillDownloaderPath(sandboxId: string): string;
|
|
79
95
|
/** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */
|
|
80
96
|
downloadFile(params: {
|
|
81
97
|
sandboxId: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Provider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/Provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,2FAA2F;AAC3F,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,eAAe,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAO1D;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACzC,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;
|
|
1
|
+
{"version":3,"file":"Provider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/Provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n/** File uploaded before a sandbox init command runs. */\nexport interface SandboxInitUpload {\n remotePath: string;\n content: Buffer;\n}\n\n/**\n * Init step from a producer (e.g. skill mounter).\n * Sandbox supplies `sandboxId` when it runs the command.\n */\nexport interface SandboxInit {\n command: string;\n env?: Record<string, string> | undefined;\n /** Exec timeout for this init command (seconds). Skill downloads use a long value. */\n timeoutSeconds: number;\n /** Files to upload before the init command runs (e.g. requested skills JSON). */\n uploads: readonly SandboxInitUpload[];\n}\n\nexport type SandboxBuildStatus = 'pending' | 'ready' | 'failed';\n\n/** Provider-specific opaque build metadata (string map). */\nexport type SandboxBuildMetadata = Record<string, string>;\n\nexport interface SandboxBuild {\n status: SandboxBuildStatus;\n /** Human-readable detail for the current status (shown to the user); null when ready. */\n reason: string | null;\n /** Provider-specific build details; null when the provider has none. */\n metadata: SandboxBuildMetadata | null;\n}\n\nexport interface SandboxProvider {\n /** Stable provider kind used in fancy sandbox ids and carry-forward (plain string). */\n readonly type: string;\n /**\n * Ensures the release image is being built into the provider's backing store and\n * returns its current status. Idempotent: an already-built image reports `ready`;\n * a fresh build starts in the background and reports `pending`.\n */\n buildImage(): Promise<SandboxBuild>;\n /** Current build status of the release image. Read-only: never kicks off a build. */\n getImageBuildStatus(): Promise<SandboxBuild>;\n createSandbox(): Promise<{ sandboxId: string }>;\n exec(params: SandboxExecParams): Promise<ExecResult>;\n /** Provider-specific instructions appended to the agent system prompt. */\n getAdditionalInstructions(): string | undefined;\n /** Directory inside the sandbox where large tool responses are dumped. */\n getToolResultDumpDir(sandboxId: string): string;\n /**\n * Git credential-store file (absolute, or cwd-relative when exec cwd is the jail).\n * `GIT_CONFIG` `store --file` is relative to the git process cwd — providers that\n * return a relative path must make it absolute in their own `exec`.\n */\n getGitCredentialsPath(sandboxId: string): string;\n /** Directory for user-uploaded files (absolute, or cwd-relative when the provider has no global FS). */\n getFileUploadsDir(sandboxId: string): string;\n /** Directory where skill mounts are materialized. */\n getSkillsDir(sandboxId: string): string;\n /** Path the skill downloader script is written to before it runs. */\n getSkillDownloaderPath(sandboxId: string): string;\n /** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */\n downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer>;\n /** Uploads a file to the sandbox. */\n uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void>;\n\n /**\n * Construct a Code Mode transport for this provider (no connect/listen yet).\n * Throws if the provider does not support Code Mode.\n */\n createCodeModeTransport(): CodeModeTransport;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBO,SAAS,YAAY,OAAuB;AACjD,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAGO,SAAS,kBAAkB,QAA0B;AAC1D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,OAAO,KAAK;AAAA,EAC9B;AACA,MAAI,OAAO,SAAS,aAAa,GAAG;AAClC,UAAM,IAAI,MAAM,cAAc,OAAO,OAAO,SAAS,QAAQ,CAAC,MAAM,OAAO,SAAS,MAAM,EAAE;AAAA,EAC9F;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n/** File uploaded before a sandbox init command runs. */\nexport interface SandboxInitUpload {\n remotePath: string;\n content: Buffer;\n}\n\n/**\n * Init step from a producer (e.g. skill mounter).\n * Sandbox supplies `sandboxId` when it runs the command.\n */\nexport interface SandboxInit {\n command: string;\n env?: Record<string, string> | undefined;\n /** Exec timeout for this init command (seconds). Skill downloads use a long value. */\n timeoutSeconds: number;\n /** Files to upload before the init command runs (e.g. requested skills JSON). */\n uploads: readonly SandboxInitUpload[];\n}\n\nexport type SandboxBuildStatus = 'pending' | 'ready' | 'failed';\n\n/** Provider-specific opaque build metadata (string map). */\nexport type SandboxBuildMetadata = Record<string, string>;\n\nexport interface SandboxBuild {\n status: SandboxBuildStatus;\n /** Human-readable detail for the current status (shown to the user); null when ready. */\n reason: string | null;\n /** Provider-specific build details; null when the provider has none. */\n metadata: SandboxBuildMetadata | null;\n}\n\nexport interface SandboxProvider {\n /** Stable provider kind used in fancy sandbox ids and carry-forward (plain string). */\n readonly type: string;\n /**\n * Ensures the release image is being built into the provider's backing store and\n * returns its current status. Idempotent: an already-built image reports `ready`;\n * a fresh build starts in the background and reports `pending`.\n */\n buildImage(): Promise<SandboxBuild>;\n /** Current build status of the release image. Read-only: never kicks off a build. */\n getImageBuildStatus(): Promise<SandboxBuild>;\n createSandbox(): Promise<{ sandboxId: string }>;\n exec(params: SandboxExecParams): Promise<ExecResult>;\n /** Provider-specific instructions appended to the agent system prompt. */\n getAdditionalInstructions(): string | undefined;\n /** Directory inside the sandbox where large tool responses are dumped. */\n getToolResultDumpDir(sandboxId: string): string;\n /**\n * Git credential-store file (absolute, or cwd-relative when exec cwd is the jail).\n * `GIT_CONFIG` `store --file` is relative to the git process cwd — providers that\n * return a relative path must make it absolute in their own `exec`.\n */\n getGitCredentialsPath(sandboxId: string): string;\n /** Directory for user-uploaded files (absolute, or cwd-relative when the provider has no global FS). */\n getFileUploadsDir(sandboxId: string): string;\n /** Directory where skill mounts are materialized. */\n getSkillsDir(sandboxId: string): string;\n /** Path the skill downloader script is written to before it runs. */\n getSkillDownloaderPath(sandboxId: string): string;\n /** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */\n downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer>;\n /** Uploads a file to the sandbox. */\n uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void>;\n\n /**\n * Construct a Code Mode transport for this provider (no connect/listen yet).\n * Throws if the provider does not support Code Mode.\n */\n createCodeModeTransport(): CodeModeTransport;\n}\n"],"mappings":";AAqBO,SAAS,YAAY,OAAuB;AACjD,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAGO,SAAS,kBAAkB,QAA0B;AAC1D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,OAAO,KAAK;AAAA,EAC9B;AACA,MAAI,OAAO,SAAS,aAAa,GAAG;AAClC,UAAM,IAAI,MAAM,cAAc,OAAO,OAAO,SAAS,QAAQ,CAAC,MAAM,OAAO,SAAS,MAAM,EAAE;AAAA,EAC9F;AACF;","names":[]}
|
|
@@ -12,7 +12,7 @@ export interface TFYSandboxProviderOptions {
|
|
|
12
12
|
logger: Logger;
|
|
13
13
|
}
|
|
14
14
|
export declare class TFYSandboxProvider implements SandboxProvider {
|
|
15
|
-
readonly type = "
|
|
15
|
+
readonly type = "truefoundry";
|
|
16
16
|
private readonly serverUrl;
|
|
17
17
|
private readonly natsBridgeUrl;
|
|
18
18
|
private readonly tenantName;
|
|
@@ -52,6 +52,6 @@ export declare class TFYSandboxProvider implements SandboxProvider {
|
|
|
52
52
|
getGitCredentialsPath(): string;
|
|
53
53
|
getFileUploadsDir(): string;
|
|
54
54
|
getSkillsDir(): string;
|
|
55
|
-
|
|
55
|
+
getSkillDownloaderPath(): string;
|
|
56
56
|
}
|
|
57
57
|
//# sourceMappingURL=TFYSandboxProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TFYSandboxProvider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAUvE,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAQpB,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGxD;AAWD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,
|
|
1
|
+
{"version":3,"file":"TFYSandboxProvider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAUvE,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAQpB,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGxD;AAWD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,iBAAiB;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA8D;IAE7F,YAAY,OAAO,EAAE,yBAAyB,EAO7C;IAGD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAIhC;IAEF,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAElC;IAED,mBAAmB,IAAI,OAAO,CAAC,YAAY,CAAC,CAE3C;IAED,aAAa,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAI9C;IAED;;;;OAIG;IACG,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CASzD;YAEa,aAAa;YAoBb,QAAQ;YAiDR,WAAW;IAiBnB,YAAY,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB/E;IAEK,UAAU,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASlG;IAGD,uBAAuB,IAAI,iBAAiB,CAO3C;IAED,yBAAyB,IAAI,MAAM,CAQlC;IAKD,oBAAoB,IAAI,MAAM,CAE7B;IAED,qBAAqB,IAAI,MAAM,CAE9B;IAED,iBAAiB,IAAI,MAAM,CAE1B;IAED,YAAY,IAAI,MAAM,CAErB;IAED,sBAAsB,IAAI,MAAM,CAE/B;CACF"}
|
|
@@ -61,7 +61,7 @@ function parsePwdAndPath(text) {
|
|
|
61
61
|
return { root, inheritedPath: lines[1] ?? "" };
|
|
62
62
|
}
|
|
63
63
|
var TFYSandboxProvider = class _TFYSandboxProvider {
|
|
64
|
-
type = "
|
|
64
|
+
type = "truefoundry";
|
|
65
65
|
serverUrl;
|
|
66
66
|
natsBridgeUrl;
|
|
67
67
|
tenantName;
|
|
@@ -226,13 +226,14 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
|
|
|
226
226
|
getAdditionalInstructions() {
|
|
227
227
|
return import_dedent.default`
|
|
228
228
|
SANDBOX RULES:
|
|
229
|
-
-
|
|
230
|
-
-
|
|
229
|
+
- The Agent's first sandbox command should be \`pwd\` to discover the working directory.
|
|
230
|
+
- uploads, skills, and tool-results live in that working directory (not /tmp or /opt).
|
|
231
|
+
- ALL file creation and writes MUST stay within that working directory.
|
|
231
232
|
- The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.
|
|
232
233
|
`;
|
|
233
234
|
}
|
|
234
235
|
// Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.
|
|
235
|
-
// uploads, skills, tool-results,
|
|
236
|
+
// uploads, skills, tool-results, skill_downloader.py, .git-credentials
|
|
236
237
|
// mcp-client/mcp_client.py (no /usr/local/bin symlink)
|
|
237
238
|
getToolResultDumpDir() {
|
|
238
239
|
return "tool-results";
|
|
@@ -246,8 +247,8 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
|
|
|
246
247
|
getSkillsDir() {
|
|
247
248
|
return "skills";
|
|
248
249
|
}
|
|
249
|
-
|
|
250
|
-
return "
|
|
250
|
+
getSkillDownloaderPath() {
|
|
251
|
+
return "skill_downloader.py";
|
|
251
252
|
}
|
|
252
253
|
};
|
|
253
254
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport dedent from 'dedent';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport { absolutizeRelativeExecEnv } from './execEnv';\nimport {\n ensureExecSuccess,\n shellEscape,\n type ExecResult,\n type SandboxBuild,\n type SandboxExecParams,\n type SandboxFileInfo,\n type SandboxProvider,\n} from './Provider';\n\nconst DEFAULT_TIMEOUT_SECONDS = 60;\n// Buffer for network latency + response processing on top of the server-side timeout.\nconst CLIENT_TIMEOUT_BUFFER_SECONDS = 5;\n\nconst TFY_MCP_CLIENT_BIN = 'mcp-client/bin';\n\n/** Put the layout CLI first on PATH (cwd-relative; {@link absolutizeRelativeExecEnv} makes it absolute). */\nexport function withMcpClientOnPath(path: string): string {\n const rest = path.split(':').filter(part => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);\n return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(':')}`;\n}\n\nfunction parsePwdAndPath(text: string): { root: string; inheritedPath: string } | undefined {\n const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\n const root = lines[0];\n if (!root?.startsWith('/')) {\n return undefined;\n }\n return { root, inheritedPath: lines[1] ?? '' };\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n readonly type = 'tfy';\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */\n private readonly sandboxLayouts = new Map<string, { root: string; inheritedPath: string }>();\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n /**\n * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative\n * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so\n * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.\n */\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });\n const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);\n const incoming = params.env ?? {};\n const env = absolutizeRelativeExecEnv({\n root,\n env: { ...incoming, PATH: withMcpClientOnPath(incoming['PATH'] ?? inheritedPath) },\n });\n return this.postExec({ ...params, env });\n }\n\n private async sandboxLayout(sandboxId: string): Promise<{ root: string; inheritedPath: string }> {\n const cached = this.sandboxLayouts.get(sandboxId);\n if (cached !== undefined) {\n return cached;\n }\n const result = await this.postExec({\n sandboxId,\n command: 'printf \\'%s\\\\n%s\\\\n\\' \"$(pwd)\" \"$PATH\"',\n });\n if (!result.success) {\n throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);\n }\n const parsed = parsePwdAndPath(result.response.result);\n if (parsed === undefined) {\n throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);\n }\n this.sandboxLayouts.set(sandboxId, parsed);\n return parsed;\n }\n\n private async postExec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const controller = new AbortController();\n const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1000;\n const timer = setTimeout(() => {\n controller.abort();\n }, clientTimeoutMs);\n\n try {\n const response = await fetch(`${this.serverUrl}/exec`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);\n return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };\n }\n\n const result = (await response.json()) as ExecResult;\n return result;\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'AbortError') {\n this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));\n return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };\n }\n this.logger.error('Sandbox exec failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n } finally {\n clearTimeout(timer);\n }\n });\n }\n\n private async getFileInfo(params: { sandboxId: string; path: string }): Promise<SandboxFileInfo> {\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `stat -L --printf='{\"size\":%s,\"type\":\"%F\"}' ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to stat file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n const parsed = JSON.parse(result.response.result) as StatResult;\n return { size: parsed.size, isDir: parsed.type === 'directory' };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n const info = await this.getFileInfo(params);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `base64 -w0 ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to download file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n return Buffer.from(result.response.result.trim(), 'base64');\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n const encoded = params.content.toString('base64');\n const escapedPath = shellEscape(params.remotePath);\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`,\n });\n ensureExecSuccess(result);\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n mcpClientInstall: { remotePath: join('mcp-client', 'mcp_client.py') },\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - uploads, skills, and tool-results live in the sandbox working directory (not /tmp or /opt).\n - ALL file creation and writes MUST stay within the sandbox working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.\n // uploads, skills, tool-results, git_downloader.py, .git-credentials\n // mcp-client/mcp_client.py (no /usr/local/bin symlink)\n getToolResultDumpDir(): string {\n return 'tool-results';\n }\n\n getGitCredentialsPath(): string {\n return '.git-credentials';\n }\n\n getFileUploadsDir(): string {\n return 'uploads';\n }\n\n getSkillsDir(): string {\n return 'skills';\n }\n\n getGitDownloaderPath(): string {\n return 'git_downloader.py';\n }\n}\n"],"mappings":"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","names":["dedent"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport dedent from 'dedent';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport { absolutizeRelativeExecEnv } from './execEnv';\nimport {\n ensureExecSuccess,\n shellEscape,\n type ExecResult,\n type SandboxBuild,\n type SandboxExecParams,\n type SandboxFileInfo,\n type SandboxProvider,\n} from './Provider';\n\nconst DEFAULT_TIMEOUT_SECONDS = 60;\n// Buffer for network latency + response processing on top of the server-side timeout.\nconst CLIENT_TIMEOUT_BUFFER_SECONDS = 5;\n\nconst TFY_MCP_CLIENT_BIN = 'mcp-client/bin';\n\n/** Put the layout CLI first on PATH (cwd-relative; {@link absolutizeRelativeExecEnv} makes it absolute). */\nexport function withMcpClientOnPath(path: string): string {\n const rest = path.split(':').filter(part => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);\n return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(':')}`;\n}\n\nfunction parsePwdAndPath(text: string): { root: string; inheritedPath: string } | undefined {\n const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\n const root = lines[0];\n if (!root?.startsWith('/')) {\n return undefined;\n }\n return { root, inheritedPath: lines[1] ?? '' };\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n readonly type = 'truefoundry';\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */\n private readonly sandboxLayouts = new Map<string, { root: string; inheritedPath: string }>();\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n /**\n * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative\n * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so\n * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.\n */\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });\n const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);\n const incoming = params.env ?? {};\n const env = absolutizeRelativeExecEnv({\n root,\n env: { ...incoming, PATH: withMcpClientOnPath(incoming['PATH'] ?? inheritedPath) },\n });\n return this.postExec({ ...params, env });\n }\n\n private async sandboxLayout(sandboxId: string): Promise<{ root: string; inheritedPath: string }> {\n const cached = this.sandboxLayouts.get(sandboxId);\n if (cached !== undefined) {\n return cached;\n }\n const result = await this.postExec({\n sandboxId,\n command: 'printf \\'%s\\\\n%s\\\\n\\' \"$(pwd)\" \"$PATH\"',\n });\n if (!result.success) {\n throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);\n }\n const parsed = parsePwdAndPath(result.response.result);\n if (parsed === undefined) {\n throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);\n }\n this.sandboxLayouts.set(sandboxId, parsed);\n return parsed;\n }\n\n private async postExec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const controller = new AbortController();\n const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1000;\n const timer = setTimeout(() => {\n controller.abort();\n }, clientTimeoutMs);\n\n try {\n const response = await fetch(`${this.serverUrl}/exec`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);\n return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };\n }\n\n const result = (await response.json()) as ExecResult;\n return result;\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'AbortError') {\n this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));\n return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };\n }\n this.logger.error('Sandbox exec failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n } finally {\n clearTimeout(timer);\n }\n });\n }\n\n private async getFileInfo(params: { sandboxId: string; path: string }): Promise<SandboxFileInfo> {\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `stat -L --printf='{\"size\":%s,\"type\":\"%F\"}' ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to stat file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n const parsed = JSON.parse(result.response.result) as StatResult;\n return { size: parsed.size, isDir: parsed.type === 'directory' };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n const info = await this.getFileInfo(params);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `base64 -w0 ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to download file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n return Buffer.from(result.response.result.trim(), 'base64');\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n const encoded = params.content.toString('base64');\n const escapedPath = shellEscape(params.remotePath);\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`,\n });\n ensureExecSuccess(result);\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n mcpClientInstall: { remotePath: join('mcp-client', 'mcp_client.py') },\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - The Agent's first sandbox command should be \\`pwd\\` to discover the working directory.\n - uploads, skills, and tool-results live in that working directory (not /tmp or /opt).\n - ALL file creation and writes MUST stay within that working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.\n // uploads, skills, tool-results, skill_downloader.py, .git-credentials\n // mcp-client/mcp_client.py (no /usr/local/bin symlink)\n getToolResultDumpDir(): string {\n return 'tool-results';\n }\n\n getGitCredentialsPath(): string {\n return '.git-credentials';\n }\n\n getFileUploadsDir(): string {\n return 'uploads';\n }\n\n getSkillsDir(): string {\n return 'skills';\n }\n\n getSkillDownloaderPath(): string {\n return 'skill_downloader.py';\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAAwB;AACxB,kBAAgC;AAChC,oBAAmB;AACnB,yBAA2B;AAC3B,mBAAqB;AAErB,4BAAsC;AAEtC,mCAAsC;AACtC,uBAA6C;AAC7C,2BAKO;AACP,qBAA0C;AAC1C,sBAQO;AAEP,IAAM,0BAA0B;AAEhC,IAAM,gCAAgC;AAEtC,IAAM,qBAAqB;AAGpB,SAAS,oBAAoB,MAAsB;AACxD,QAAM,OAAO,KAAK,MAAM,GAAG,EAAE,OAAO,UAAQ,KAAK,SAAS,KAAK,SAAS,kBAAkB;AAC1F,SAAO,KAAK,WAAW,IAAI,qBAAqB,GAAG,kBAAkB,IAAI,KAAK,KAAK,GAAG,CAAC;AACzF;AAEA,SAAS,gBAAgB,MAAmE;AAC1F,QAAM,QAAQ,KAAK,QAAQ,SAAS,IAAI,EAAE,MAAM,IAAI;AACpD,QAAM,OAAO,MAAM,CAAC;AACpB,MAAI,CAAC,MAAM,WAAW,GAAG,GAAG;AAC1B,WAAO;AAAA,EACT;AACA,SAAO,EAAE,MAAM,eAAe,MAAM,CAAC,KAAK,GAAG;AAC/C;AAgBO,IAAM,qBAAN,MAAM,oBAA8C;AAAA,EAChD,OAAO;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA,iBAAiB,oBAAI,IAAqD;AAAA,EAE3F,YAAY,SAAoC;AAC9C,SAAK,YAAY,QAAQ,UAAU,QAAQ,QAAQ,EAAE;AACrD,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,aAAa,QAAQ;AAC1B,SAAK,0BAA0B,QAAQ;AACvC,SAAK,4BAA4B,KAAK,MAAM,QAAQ,wBAAwB,0BAA0B,OAAQ,GAAI;AAClH,SAAK,SAAS,QAAQ,OAAO,MAAM,EAAE,QAAQ,qBAAqB,CAAC;AAAA,EACrE;AAAA;AAAA,EAGA,OAAwB,aAA2B;AAAA,IACjD,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EAEA,aAAoC;AAClC,WAAO,QAAQ,QAAQ,oBAAmB,UAAU;AAAA,EACtD;AAAA,EAEA,sBAA6C;AAC3C,WAAO,QAAQ,QAAQ,oBAAmB,UAAU;AAAA,EACtD;AAAA,EAEA,gBAAgD;AAC9C,UAAM,YAAY,GAAG,KAAK,UAAU,QAAI,+BAAW,CAAC;AACpD,SAAK,OAAO,MAAM,uBAAuB,SAAS,EAAE;AACpD,WAAO,QAAQ,QAAQ,EAAE,UAAU,CAAC;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,KAAK,QAAgD;AACzD,2DAA6B,EAAE,WAAW,OAAO,WAAW,YAAY,KAAK,WAAW,CAAC;AACzF,UAAM,EAAE,MAAM,cAAc,IAAI,MAAM,KAAK,cAAc,OAAO,SAAS;AACzE,UAAM,WAAW,OAAO,OAAO,CAAC;AAChC,UAAM,UAAM,0CAA0B;AAAA,MACpC;AAAA,MACA,KAAK,EAAE,GAAG,UAAU,MAAM,oBAAoB,SAAS,MAAM,KAAK,aAAa,EAAE;AAAA,IACnF,CAAC;AACD,WAAO,KAAK,SAAS,EAAE,GAAG,QAAQ,IAAI,CAAC;AAAA,EACzC;AAAA,EAEA,MAAc,cAAc,WAAqE;AAC/F,UAAM,SAAS,KAAK,eAAe,IAAI,SAAS;AAChD,QAAI,WAAW,QAAW;AACxB,aAAO;AAAA,IACT;AACA,UAAM,SAAS,MAAM,KAAK,SAAS;AAAA,MACjC;AAAA,MACA,SAAS;AAAA,IACX,CAAC;AACD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,gDAAgD,OAAO,KAAK,EAAE;AAAA,IAChF;AACA,UAAM,SAAS,gBAAgB,OAAO,SAAS,MAAM;AACrD,QAAI,WAAW,QAAW;AACxB,YAAM,IAAI,MAAM,gDAAgD,OAAO,SAAS,MAAM,EAAE;AAAA,IAC1F;AACA,SAAK,eAAe,IAAI,WAAW,MAAM;AACzC,WAAO;AAAA,EACT;AAAA,EAEA,MAAc,SAAS,QAAgD;AACrE,WAAO,mBAAQ,SAAK,6BAAgB,mBAAQ,OAAO,CAAC,GAAG,YAAiC;AAGtF,YAAM,iBAAiB,OAAO,kBAAkB,KAAK;AACrD,YAAM,OAAO;AAAA,QACX,YAAY,OAAO;AAAA,QACnB,SAAS,OAAO;AAAA,QAChB,KAAK,OAAO;AAAA,QACZ,KAAK,OAAO;AAAA,QACZ,SAAS;AAAA,MACX;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,mBAAmB,iBAAiB,iCAAiC;AAC3E,YAAM,QAAQ,WAAW,MAAM;AAC7B,mBAAW,MAAM;AAAA,MACnB,GAAG,eAAe;AAElB,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,GAAG,KAAK,SAAS,SAAS;AAAA,UACrD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,UACzB,QAAQ,WAAW;AAAA,QACrB,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,eAAK,OAAO,MAAM,2BAA2B,OAAO,SAAS,MAAM,CAAC,KAAK,IAAI,EAAE;AAC/E,iBAAO,EAAE,SAAS,OAAO,OAAO,2BAA2B,OAAO,SAAS,MAAM,CAAC,KAAK,IAAI,GAAG;AAAA,QAChG;AAEA,cAAM,SAAU,MAAM,SAAS,KAAK;AACpC,eAAO;AAAA,MACT,SAAS,GAAY;AACnB,YAAI,aAAa,SAAS,EAAE,SAAS,cAAc;AACjD,eAAK,OAAO,MAAM,gCAAgC,OAAO,cAAc,CAAC,SAAK,6CAAsB,CAAC,CAAC;AACrG,iBAAO,EAAE,SAAS,OAAO,OAAO,gCAAgC,OAAO,cAAc,CAAC,IAAI;AAAA,QAC5F;AACA,aAAK,OAAO,MAAM,2BAAuB,6CAAsB,CAAC,CAAC;AACjE,cAAM,UAAU,aAAa,QAAQ,EAAE,UAAU;AACjD,eAAO,EAAE,SAAS,OAAO,OAAO,QAAQ;AAAA,MAC1C,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,YAAY,QAAuE;AAC/F,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,WAAW,OAAO;AAAA,MAClB,SAAS,kDAA8C,6BAAY,OAAO,IAAI,CAAC;AAAA,IACjF,CAAC;AAED,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,wBAAwB,OAAO,KAAK,EAAE;AAAA,IACxD;AACA,QAAI,OAAO,SAAS,aAAa,GAAG;AAClC,YAAM,IAAI,8CAAyB,OAAO,IAAI;AAAA,IAChD;AAEA,UAAM,SAAS,KAAK,MAAM,OAAO,SAAS,MAAM;AAChD,WAAO,EAAE,MAAM,OAAO,MAAM,OAAO,OAAO,SAAS,YAAY;AAAA,EACjE;AAAA,EAEA,MAAM,aAAa,QAA8D;AAC/E,UAAM,OAAO,MAAM,KAAK,YAAY,MAAM;AAC1C,QAAI,KAAK,OAAO;AACd,YAAM,IAAI,iDAA4B,OAAO,IAAI;AAAA,IACnD;AACA,QAAI,KAAK,OAAO,KAAK,yBAAyB;AAC5C,YAAM,IAAI,8CAAyB,OAAO,MAAM,KAAK,MAAM,KAAK,uBAAuB;AAAA,IACzF;AAEA,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,WAAW,OAAO;AAAA,MAClB,SAAS,kBAAc,6BAAY,OAAO,IAAI,CAAC;AAAA,IACjD,CAAC;AAED,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,4BAA4B,OAAO,KAAK,EAAE;AAAA,IAC5D;AACA,QAAI,OAAO,SAAS,aAAa,GAAG;AAClC,YAAM,IAAI,8CAAyB,OAAO,IAAI;AAAA,IAChD;AAEA,WAAO,OAAO,KAAK,OAAO,SAAS,OAAO,KAAK,GAAG,QAAQ;AAAA,EAC5D;AAAA,EAEA,MAAM,WAAW,QAAmF;AAClG,UAAM,UAAU,OAAO,QAAQ,SAAS,QAAQ;AAChD,UAAM,kBAAc,6BAAY,OAAO,UAAU;AAEjD,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,WAAW,OAAO;AAAA,MAClB,SAAS,YAAQ,6BAAY,OAAO,CAAC,kBAAkB,WAAW;AAAA,IACpE,CAAC;AACD,2CAAkB,MAAM;AAAA,EAC1B;AAAA;AAAA,EAGA,0BAA6C;AAC3C,WAAO,IAAI,mDAAsB;AAAA,MAC/B,gBAAgB,MAAM,QAAQ,QAAQ,KAAK,aAAa;AAAA,MACxD,sBAAsB,kBAAkB,OAAO,6CAA4B,CAAC;AAAA,MAC5E,QAAQ,KAAK;AAAA,MACb,kBAAkB,EAAE,gBAAY,mBAAK,cAAc,eAAe,EAAE;AAAA,IACtE,CAAC;AAAA,EACH;AAAA,EAEA,4BAAoC;AAClC,WAAO,cAAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOT;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA+B;AAC7B,WAAO;AAAA,EACT;AAAA,EAEA,wBAAgC;AAC9B,WAAO;AAAA,EACT;AAAA,EAEA,oBAA4B;AAC1B,WAAO;AAAA,EACT;AAAA,EAEA,eAAuB;AACrB,WAAO;AAAA,EACT;AAAA,EAEA,yBAAiC;AAC/B,WAAO;AAAA,EACT;AACF;","names":["dedent"]}
|