@truefoundry/trueforge-core 0.1.3 → 0.1.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.map +1 -1
- package/dist/agent-session/TurnResourceResolver.js +1 -4
- package/dist/agent-session/TurnResourceResolver.js.map +1 -1
- package/dist/agent-session/TurnResourceResolver.mjs +1 -4
- 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 +7 -28
- package/dist/agent-session/schemas/agentSpec.d.ts.map +1 -1
- package/dist/agent-session/schemas/agentSpec.js +15 -43
- package/dist/agent-session/schemas/agentSpec.js.map +1 -1
- package/dist/agent-session/schemas/agentSpec.mjs +15 -43
- package/dist/agent-session/schemas/agentSpec.mjs.map +1 -1
- package/dist/agent-session/schemas/session.d.ts +0 -39
- package/dist/agent-session/schemas/session.d.ts.map +1 -1
- package/dist/core/index.d.ts +4 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +7 -0
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/runtime/AgentThread.d.ts.map +1 -1
- package/dist/core/runtime/AgentThread.js +25 -11
- package/dist/core/runtime/AgentThread.js.map +1 -1
- package/dist/core/runtime/AgentThread.mjs +25 -11
- package/dist/core/runtime/AgentThread.mjs.map +1 -1
- package/dist/core/sandbox/Sandbox.d.ts +15 -4
- package/dist/core/sandbox/Sandbox.d.ts.map +1 -1
- package/dist/core/sandbox/Sandbox.js +200 -80
- package/dist/core/sandbox/Sandbox.js.map +1 -1
- package/dist/core/sandbox/Sandbox.mjs +201 -81
- package/dist/core/sandbox/Sandbox.mjs.map +1 -1
- package/dist/core/sandbox/SandboxErrors.d.ts +7 -3
- package/dist/core/sandbox/SandboxErrors.d.ts.map +1 -1
- package/dist/core/sandbox/SandboxErrors.js +6 -6
- package/dist/core/sandbox/SandboxErrors.js.map +1 -1
- package/dist/core/sandbox/SandboxErrors.mjs +6 -6
- package/dist/core/sandbox/SandboxErrors.mjs.map +1 -1
- package/dist/core/sandbox/codeMode/CodeModeTransport.d.ts +22 -0
- package/dist/core/sandbox/codeMode/CodeModeTransport.d.ts.map +1 -1
- package/dist/core/sandbox/codeMode/CodeModeTransport.js.map +1 -1
- package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.d.ts +9 -1
- package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.d.ts.map +1 -1
- package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.js +12 -0
- package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.js.map +1 -1
- package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.mjs +12 -0
- package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.mjs.map +1 -1
- package/dist/core/sandbox/constants.d.ts +0 -2
- package/dist/core/sandbox/constants.d.ts.map +1 -1
- package/dist/core/sandbox/constants.js +2 -5
- package/dist/core/sandbox/constants.js.map +1 -1
- package/dist/core/sandbox/constants.mjs +1 -3
- package/dist/core/sandbox/constants.mjs.map +1 -1
- package/dist/core/sandbox/provider/DaytonaProvider.d.ts +4 -0
- package/dist/core/sandbox/provider/DaytonaProvider.d.ts.map +1 -1
- package/dist/core/sandbox/provider/DaytonaProvider.js +27 -5
- package/dist/core/sandbox/provider/DaytonaProvider.js.map +1 -1
- package/dist/core/sandbox/provider/DaytonaProvider.mjs +27 -4
- package/dist/core/sandbox/provider/DaytonaProvider.mjs.map +1 -1
- package/dist/core/sandbox/provider/Provider.d.ts +13 -1
- 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 +17 -2
- package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts.map +1 -1
- package/dist/core/sandbox/provider/TFYSandboxProvider.js +81 -17
- package/dist/core/sandbox/provider/TFYSandboxProvider.js.map +1 -1
- package/dist/core/sandbox/provider/TFYSandboxProvider.mjs +82 -13
- package/dist/core/sandbox/provider/TFYSandboxProvider.mjs.map +1 -1
- package/dist/core/sandbox/provider/execEnv.d.ts +6 -0
- package/dist/core/sandbox/provider/execEnv.d.ts.map +1 -0
- package/dist/core/sandbox/provider/execEnv.js +55 -0
- package/dist/core/sandbox/provider/execEnv.js.map +1 -0
- package/dist/core/sandbox/provider/execEnv.mjs +30 -0
- package/dist/core/sandbox/provider/execEnv.mjs.map +1 -0
- package/dist/core/sandbox/sandboxImage.d.ts +2 -5
- package/dist/core/sandbox/sandboxImage.d.ts.map +1 -1
- package/dist/core/sandbox/sandboxImage.js +8 -1
- package/dist/core/sandbox/sandboxImage.js.map +1 -1
- package/dist/core/sandbox/sandboxImage.mjs +6 -1
- package/dist/core/sandbox/sandboxImage.mjs.map +1 -1
- package/dist/core/sandbox/sandboxRef.d.ts +26 -0
- package/dist/core/sandbox/sandboxRef.d.ts.map +1 -0
- package/dist/core/sandbox/sandboxRef.js +69 -0
- package/dist/core/sandbox/sandboxRef.js.map +1 -0
- package/dist/core/sandbox/sandboxRef.mjs +41 -0
- package/dist/core/sandbox/sandboxRef.mjs.map +1 -0
- package/dist/core/sandbox/skills/ISkillMounter.d.ts +7 -2
- 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 +7 -2
- package/dist/core/sandbox/skills/SkillMounter.d.ts.map +1 -1
- package/dist/core/sandbox/skills/SkillMounter.js +5 -6
- package/dist/core/sandbox/skills/SkillMounter.js.map +1 -1
- package/dist/core/sandbox/skills/SkillMounter.mjs +5 -6
- package/dist/core/sandbox/skills/SkillMounter.mjs.map +1 -1
- package/dist/core/sandbox/skills/constants.d.ts +4 -2
- package/dist/core/sandbox/skills/constants.d.ts.map +1 -1
- package/dist/core/sandbox/skills/constants.js +2 -5
- package/dist/core/sandbox/skills/constants.js.map +1 -1
- package/dist/core/sandbox/skills/constants.mjs +2 -4
- package/dist/core/sandbox/skills/constants.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 +0 -2
- package/dist/core/sandbox/skills/index.js.map +1 -1
- package/dist/core/sandbox/skills/index.mjs +1 -2
- package/dist/core/sandbox/skills/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/sandbox/codeMode/nats/CodeModeNatsTransport.ts"],"sourcesContent":["import { wsconnect, type Msg, type NatsConnection } from '@nats-io/nats-core';\nimport { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'node:crypto';\nimport type { Logger } from 'winston';\nimport { WebSocket } from 'ws';\nimport { extractErrorLogFields } from '../../../util/errorLogFields';\nimport { withTimeout } from '../../../util/promiseUtils';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../../constants';\nimport type { CodeModeDispatcher } from '../CodeModeDispatcher';\nimport type { CodeModeTransport } from '../CodeModeTransport';\nimport { CodeModeRequestSchema, type CodeModeReply, type CodeModeRequest } from '../types';\n\n// `wsconnect` from @nats-io/nats-core relies on a global WebSocket constructor; in Node we\n// provide it from `ws`. Same pattern as `src/services/NatsService.ts`.\nObject.assign(global, { WebSocket });\n\nconst NATS_CONNECT_TIMEOUT_MS = 10_000;\nconst NATS_CONNECT_RETRIES = 3;\nconst NATS_CONNECT_BACKOFF_MS = 250;\nconst NATS_DRAIN_TIMEOUT_MS = 5_000;\n\nconst SANDBOX_NATS_SUBJECT_ROOT = 'sandbox.bridge';\nconst SANDBOX_NATS_MCP_LEAF = 'mcp';\n\n/**\n * NATS Code Mode transport. Session cache + connection-drop invalidate live here\n * (no `whenClosed` on the public interface).\n */\nexport class CodeModeNatsTransport implements CodeModeTransport {\n private readonly resolveHostUrl: (sandboxId: string) => Promise<string>;\n private readonly sandboxClientNatsUrl: string;\n private readonly logger: Logger;\n\n private sessionPromise: Promise<{ env: Record<string, string> }> | undefined;\n private nc: NatsConnection | undefined;\n private dispatcher: CodeModeDispatcher | undefined;\n\n constructor(params: {\n resolveHostUrl: (sandboxId: string) => Promise<string>;\n sandboxClientNatsUrl?: string;\n logger: Logger;\n }) {\n this.resolveHostUrl = params.resolveHostUrl;\n this.sandboxClientNatsUrl = params.sandboxClientNatsUrl ?? `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`;\n this.logger = params.logger.child({ module: 'CodeModeNatsTransport' });\n }\n\n start(params: {\n codeModeDispatcher: CodeModeDispatcher;\n sandboxId: string;\n requestTimeoutSeconds: number;\n }): Promise<{ env: Record<string, string> }> {\n this.dispatcher = params.codeModeDispatcher;\n this.sessionPromise ??= this.connectSession(params).catch((e: unknown) => {\n this.sessionPromise = undefined;\n throw e;\n });\n return this.sessionPromise;\n }\n\n async stop(): Promise<void> {\n const pending = this.sessionPromise;\n this.sessionPromise = undefined;\n if (pending !== undefined) {\n try {\n await pending;\n } catch {\n // Connect failed; nothing to drain.\n }\n }\n const nc = this.nc;\n this.nc = undefined;\n if (nc === undefined) {\n return;\n }\n try {\n await withTimeout(nc.drain(), NATS_DRAIN_TIMEOUT_MS);\n } catch (e) {\n this.logger.warn('Code Mode NATS drain failed/timed out; forcing close', extractErrorLogFields(e));\n await nc.close().catch(() => {\n /* no-op */\n });\n }\n }\n\n private async connectSession(params: {\n sandboxId: string;\n requestTimeoutSeconds: number;\n }): Promise<{ env: Record<string, string> }> {\n const url = await this.resolveHostUrl(params.sandboxId);\n const subjectPrefix = `${SANDBOX_NATS_SUBJECT_ROOT}.${randomUUID()}`;\n let lastErr: unknown;\n let nc: NatsConnection | undefined;\n for (let attempt = 1; attempt <= NATS_CONNECT_RETRIES; attempt++) {\n try {\n nc = await context.with(suppressTracing(context.active()), () =>\n wsconnect({ servers: [url], timeout: NATS_CONNECT_TIMEOUT_MS }),\n );\n this.logger.info(`Connected to sandbox NATS (attempt ${String(attempt)})`, { subjectPrefix });\n break;\n } catch (e) {\n lastErr = e;\n this.logger.warn(`Code Mode NATS connect attempt ${String(attempt)} failed`, extractErrorLogFields(e));\n if (attempt < NATS_CONNECT_RETRIES) {\n await new Promise(resolve => setTimeout(resolve, NATS_CONNECT_BACKOFF_MS));\n }\n }\n }\n if (nc === undefined) {\n throw lastErr instanceof Error ? lastErr : new Error('Code Mode NATS connect failed');\n }\n\n this.nc = nc;\n this.subscribe(nc, subjectPrefix);\n\n const cachedPromise = this.sessionPromise;\n void nc.closed().then(() => {\n if (this.sessionPromise === cachedPromise) {\n this.sessionPromise = undefined;\n this.nc = undefined;\n this.logger.info('Code Mode NATS connection closed; session invalidated', {\n sandboxId: params.sandboxId,\n });\n }\n });\n\n return {\n env: {\n TFY_NATS_URL: this.sandboxClientNatsUrl,\n TFY_NATS_SUBJECT_PREFIX: subjectPrefix,\n TFY_CM_REQUEST_TIMEOUT_SECONDS: String(params.requestTimeoutSeconds),\n },\n };\n }\n\n private subscribe(nc: NatsConnection, subjectPrefix: string): void {\n nc.subscribe(`${subjectPrefix}.${SANDBOX_NATS_MCP_LEAF}`, {\n callback: (err, msg) => {\n void this.handleMessage(err, msg);\n },\n });\n }\n\n private async handleMessage(err: Error | null, msg: Msg): Promise<void> {\n if (err) {\n this.logger.error('Code Mode NATS subscription error', extractErrorLogFields(err));\n return;\n }\n if (!msg.reply) {\n this.logger.warn(`Code Mode NATS request on '${msg.subject}' has no reply subject; dropping`);\n return;\n }\n\n const dispatcher = this.dispatcher;\n if (dispatcher === undefined) {\n try {\n msg.respond(this.encode({ ok: false, error: 'Code Mode dispatcher is not configured', source: 'internal' }));\n } catch (e) {\n this.logger.warn(`Failed to send dispatcher-missing reply on '${msg.subject}'`, extractErrorLogFields(e));\n }\n return;\n }\n\n const carrier: Record<string, string> = {};\n const traceparent = msg.headers?.get('traceparent');\n const tracestate = msg.headers?.get('tracestate');\n if (traceparent) {\n carrier['traceparent'] = traceparent;\n }\n if (tracestate) {\n carrier['tracestate'] = tracestate;\n }\n\n let request: CodeModeRequest;\n try {\n request = this.decode(msg);\n } catch (e) {\n this.logger.error(`Failed to decode Code Mode NATS request on '${msg.subject}'`, extractErrorLogFields(e));\n try {\n msg.respond(this.encode({ ok: false, error: 'Malformed Code Mode request', source: 'caller' }));\n } catch (respondErr) {\n this.logger.warn(`Failed to send decode-error reply on '${msg.subject}'`, extractErrorLogFields(respondErr));\n }\n return;\n }\n\n const reply = await dispatcher.dispatch({ request, traceCarrier: carrier });\n try {\n msg.respond(this.encode(reply));\n } catch (e) {\n this.logger.error('Failed to publish Code Mode NATS reply', extractErrorLogFields(e));\n }\n }\n\n private encode(reply: CodeModeReply): Uint8Array {\n return new TextEncoder().encode(JSON.stringify(reply));\n }\n\n private decode(msg: Msg): CodeModeRequest {\n const raw: unknown = JSON.parse(new TextDecoder().decode(msg.data));\n return CodeModeRequestSchema.parse(raw);\n }\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/sandbox/codeMode/nats/CodeModeNatsTransport.ts"],"sourcesContent":["import { wsconnect, type Msg, type NatsConnection } from '@nats-io/nats-core';\nimport { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'node:crypto';\nimport type { Logger } from 'winston';\nimport { WebSocket } from 'ws';\nimport { extractErrorLogFields } from '../../../util/errorLogFields';\nimport { withTimeout } from '../../../util/promiseUtils';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../../constants';\nimport { sandboxScripts } from '../../sandboxScripts.gen';\nimport type { CodeModeDispatcher } from '../CodeModeDispatcher';\nimport type { CodeModeClientInstall, CodeModeTransport } from '../CodeModeTransport';\nimport { CodeModeRequestSchema, type CodeModeReply, type CodeModeRequest } from '../types';\n\n// `wsconnect` from @nats-io/nats-core relies on a global WebSocket constructor; in Node we\n// provide it from `ws`. Same pattern as `src/services/NatsService.ts`.\nObject.assign(global, { WebSocket });\n\nconst NATS_CONNECT_TIMEOUT_MS = 10_000;\nconst NATS_CONNECT_RETRIES = 3;\nconst NATS_CONNECT_BACKOFF_MS = 250;\nconst NATS_DRAIN_TIMEOUT_MS = 5_000;\n\nconst SANDBOX_NATS_SUBJECT_ROOT = 'sandbox.bridge';\nconst SANDBOX_NATS_MCP_LEAF = 'mcp';\n\n/**\n * NATS Code Mode transport. Session cache + connection-drop invalidate live here\n * (no `whenClosed` on the public interface).\n */\nexport class CodeModeNatsTransport implements CodeModeTransport {\n private readonly resolveHostUrl: (sandboxId: string) => Promise<string>;\n private readonly sandboxClientNatsUrl: string;\n private readonly logger: Logger;\n\n private sessionPromise: Promise<{ env: Record<string, string> }> | undefined;\n private nc: NatsConnection | undefined;\n private dispatcher: CodeModeDispatcher | undefined;\n\n private readonly mcpClientRemotePath: string;\n private readonly mcpClientPathBinSymlink: string | undefined;\n\n constructor(params: {\n resolveHostUrl: (sandboxId: string) => Promise<string>;\n sandboxClientNatsUrl?: string;\n logger: Logger;\n /** Provider-owned MCP client layout (absolute or cwd-relative). */\n mcpClientInstall: { remotePath: string; pathBinSymlink?: string | undefined };\n }) {\n this.resolveHostUrl = params.resolveHostUrl;\n this.sandboxClientNatsUrl = params.sandboxClientNatsUrl ?? `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`;\n this.logger = params.logger.child({ module: 'CodeModeNatsTransport' });\n this.mcpClientRemotePath = params.mcpClientInstall.remotePath;\n this.mcpClientPathBinSymlink = params.mcpClientInstall.pathBinSymlink;\n }\n\n getClientInstall(): CodeModeClientInstall {\n return {\n content: sandboxScripts.mcpClient,\n remotePath: this.mcpClientRemotePath,\n ...(this.mcpClientPathBinSymlink === undefined ? {} : { pathBinSymlink: this.mcpClientPathBinSymlink }),\n };\n }\n\n start(params: {\n codeModeDispatcher: CodeModeDispatcher;\n sandboxId: string;\n requestTimeoutSeconds: number;\n }): Promise<{ env: Record<string, string> }> {\n this.dispatcher = params.codeModeDispatcher;\n this.sessionPromise ??= this.connectSession(params).catch((e: unknown) => {\n this.sessionPromise = undefined;\n throw e;\n });\n return this.sessionPromise;\n }\n\n async stop(): Promise<void> {\n const pending = this.sessionPromise;\n this.sessionPromise = undefined;\n if (pending !== undefined) {\n try {\n await pending;\n } catch {\n // Connect failed; nothing to drain.\n }\n }\n const nc = this.nc;\n this.nc = undefined;\n if (nc === undefined) {\n return;\n }\n try {\n await withTimeout(nc.drain(), NATS_DRAIN_TIMEOUT_MS);\n } catch (e) {\n this.logger.warn('Code Mode NATS drain failed/timed out; forcing close', extractErrorLogFields(e));\n await nc.close().catch(() => {\n /* no-op */\n });\n }\n }\n\n private async connectSession(params: {\n sandboxId: string;\n requestTimeoutSeconds: number;\n }): Promise<{ env: Record<string, string> }> {\n const url = await this.resolveHostUrl(params.sandboxId);\n const subjectPrefix = `${SANDBOX_NATS_SUBJECT_ROOT}.${randomUUID()}`;\n let lastErr: unknown;\n let nc: NatsConnection | undefined;\n for (let attempt = 1; attempt <= NATS_CONNECT_RETRIES; attempt++) {\n try {\n nc = await context.with(suppressTracing(context.active()), () =>\n wsconnect({ servers: [url], timeout: NATS_CONNECT_TIMEOUT_MS }),\n );\n this.logger.info(`Connected to sandbox NATS (attempt ${String(attempt)})`, { subjectPrefix });\n break;\n } catch (e) {\n lastErr = e;\n this.logger.warn(`Code Mode NATS connect attempt ${String(attempt)} failed`, extractErrorLogFields(e));\n if (attempt < NATS_CONNECT_RETRIES) {\n await new Promise(resolve => setTimeout(resolve, NATS_CONNECT_BACKOFF_MS));\n }\n }\n }\n if (nc === undefined) {\n throw lastErr instanceof Error ? lastErr : new Error('Code Mode NATS connect failed');\n }\n\n this.nc = nc;\n this.subscribe(nc, subjectPrefix);\n\n const cachedPromise = this.sessionPromise;\n void nc.closed().then(() => {\n if (this.sessionPromise === cachedPromise) {\n this.sessionPromise = undefined;\n this.nc = undefined;\n this.logger.info('Code Mode NATS connection closed; session invalidated', {\n sandboxId: params.sandboxId,\n });\n }\n });\n\n return {\n env: {\n TFY_NATS_URL: this.sandboxClientNatsUrl,\n TFY_NATS_SUBJECT_PREFIX: subjectPrefix,\n TFY_CM_REQUEST_TIMEOUT_SECONDS: String(params.requestTimeoutSeconds),\n },\n };\n }\n\n private subscribe(nc: NatsConnection, subjectPrefix: string): void {\n nc.subscribe(`${subjectPrefix}.${SANDBOX_NATS_MCP_LEAF}`, {\n callback: (err, msg) => {\n void this.handleMessage(err, msg);\n },\n });\n }\n\n private async handleMessage(err: Error | null, msg: Msg): Promise<void> {\n if (err) {\n this.logger.error('Code Mode NATS subscription error', extractErrorLogFields(err));\n return;\n }\n if (!msg.reply) {\n this.logger.warn(`Code Mode NATS request on '${msg.subject}' has no reply subject; dropping`);\n return;\n }\n\n const dispatcher = this.dispatcher;\n if (dispatcher === undefined) {\n try {\n msg.respond(this.encode({ ok: false, error: 'Code Mode dispatcher is not configured', source: 'internal' }));\n } catch (e) {\n this.logger.warn(`Failed to send dispatcher-missing reply on '${msg.subject}'`, extractErrorLogFields(e));\n }\n return;\n }\n\n const carrier: Record<string, string> = {};\n const traceparent = msg.headers?.get('traceparent');\n const tracestate = msg.headers?.get('tracestate');\n if (traceparent) {\n carrier['traceparent'] = traceparent;\n }\n if (tracestate) {\n carrier['tracestate'] = tracestate;\n }\n\n let request: CodeModeRequest;\n try {\n request = this.decode(msg);\n } catch (e) {\n this.logger.error(`Failed to decode Code Mode NATS request on '${msg.subject}'`, extractErrorLogFields(e));\n try {\n msg.respond(this.encode({ ok: false, error: 'Malformed Code Mode request', source: 'caller' }));\n } catch (respondErr) {\n this.logger.warn(`Failed to send decode-error reply on '${msg.subject}'`, extractErrorLogFields(respondErr));\n }\n return;\n }\n\n const reply = await dispatcher.dispatch({ request, traceCarrier: carrier });\n try {\n msg.respond(this.encode(reply));\n } catch (e) {\n this.logger.error('Failed to publish Code Mode NATS reply', extractErrorLogFields(e));\n }\n }\n\n private encode(reply: CodeModeReply): Uint8Array {\n return new TextEncoder().encode(JSON.stringify(reply));\n }\n\n private decode(msg: Msg): CodeModeRequest {\n const raw: unknown = JSON.parse(new TextDecoder().decode(msg.data));\n return CodeModeRequestSchema.parse(raw);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
/** Stable directory inside the sandbox for user file uploads (mkdir during sandbox init). */
|
|
2
|
-
export declare const SANDBOX_FILE_UPLOADS_DIR = "/tmp/uploads";
|
|
3
1
|
/** Default port of the pod-local NATS WebSocket broker used by the sandbox→gateway MCP bridge. */
|
|
4
2
|
export declare const DEFAULT_SANDBOX_NATS_WS_PORT = 4444;
|
|
5
3
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/constants.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/constants.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,eAAO,MAAM,4BAA4B,OAAO,CAAC;AAEjD;;;GAGG;AACH,eAAO,MAAM,kCAAkC,OAAO,CAAC"}
|
|
@@ -21,17 +21,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var constants_exports = {};
|
|
22
22
|
__export(constants_exports, {
|
|
23
23
|
DEFAULT_PREVIEW_URL_EXPIRY_SECONDS: () => DEFAULT_PREVIEW_URL_EXPIRY_SECONDS,
|
|
24
|
-
DEFAULT_SANDBOX_NATS_WS_PORT: () => DEFAULT_SANDBOX_NATS_WS_PORT
|
|
25
|
-
SANDBOX_FILE_UPLOADS_DIR: () => SANDBOX_FILE_UPLOADS_DIR
|
|
24
|
+
DEFAULT_SANDBOX_NATS_WS_PORT: () => DEFAULT_SANDBOX_NATS_WS_PORT
|
|
26
25
|
});
|
|
27
26
|
module.exports = __toCommonJS(constants_exports);
|
|
28
|
-
var SANDBOX_FILE_UPLOADS_DIR = "/tmp/uploads";
|
|
29
27
|
var DEFAULT_SANDBOX_NATS_WS_PORT = 4444;
|
|
30
28
|
var DEFAULT_PREVIEW_URL_EXPIRY_SECONDS = 3600;
|
|
31
29
|
// Annotate the CommonJS export names for ESM import in node:
|
|
32
30
|
0 && (module.exports = {
|
|
33
31
|
DEFAULT_PREVIEW_URL_EXPIRY_SECONDS,
|
|
34
|
-
DEFAULT_SANDBOX_NATS_WS_PORT
|
|
35
|
-
SANDBOX_FILE_UPLOADS_DIR
|
|
32
|
+
DEFAULT_SANDBOX_NATS_WS_PORT
|
|
36
33
|
});
|
|
37
34
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/sandbox/constants.ts"],"sourcesContent":["/**
|
|
1
|
+
{"version":3,"sources":["../../../src/core/sandbox/constants.ts"],"sourcesContent":["/** Default port of the pod-local NATS WebSocket broker used by the sandbox→gateway MCP bridge. */\nexport const DEFAULT_SANDBOX_NATS_WS_PORT = 4444;\n\n/**\n * Default lifetime of a Daytona signed preview URL for the NATS bridge.\n * Matches the gateway's default AGENT_RESPONSE_MAX_EXECUTION_TIME_MS (1 hour).\n */\nexport const DEFAULT_PREVIEW_URL_EXPIRY_SECONDS = 3600;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACO,IAAM,+BAA+B;AAMrC,IAAM,qCAAqC;","names":[]}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
// src/core/sandbox/constants.ts
|
|
2
|
-
var SANDBOX_FILE_UPLOADS_DIR = "/tmp/uploads";
|
|
3
2
|
var DEFAULT_SANDBOX_NATS_WS_PORT = 4444;
|
|
4
3
|
var DEFAULT_PREVIEW_URL_EXPIRY_SECONDS = 3600;
|
|
5
4
|
export {
|
|
6
5
|
DEFAULT_PREVIEW_URL_EXPIRY_SECONDS,
|
|
7
|
-
DEFAULT_SANDBOX_NATS_WS_PORT
|
|
8
|
-
SANDBOX_FILE_UPLOADS_DIR
|
|
6
|
+
DEFAULT_SANDBOX_NATS_WS_PORT
|
|
9
7
|
};
|
|
10
8
|
//# sourceMappingURL=constants.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/core/sandbox/constants.ts"],"sourcesContent":["/**
|
|
1
|
+
{"version":3,"sources":["../../../src/core/sandbox/constants.ts"],"sourcesContent":["/** Default port of the pod-local NATS WebSocket broker used by the sandbox→gateway MCP bridge. */\nexport const DEFAULT_SANDBOX_NATS_WS_PORT = 4444;\n\n/**\n * Default lifetime of a Daytona signed preview URL for the NATS bridge.\n * Matches the gateway's default AGENT_RESPONSE_MAX_EXECUTION_TIME_MS (1 hour).\n */\nexport const DEFAULT_PREVIEW_URL_EXPIRY_SECONDS = 3600;\n"],"mappings":";AACO,IAAM,+BAA+B;AAMrC,IAAM,qCAAqC;","names":[]}
|
|
@@ -33,6 +33,7 @@ export interface DaytonaSandboxProviderOptions {
|
|
|
33
33
|
logger: Logger;
|
|
34
34
|
}
|
|
35
35
|
export declare class DaytonaSandboxProvider implements SandboxProvider {
|
|
36
|
+
readonly type = "daytona";
|
|
36
37
|
private readonly tenantName;
|
|
37
38
|
/** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */
|
|
38
39
|
private readonly imageUri;
|
|
@@ -94,5 +95,8 @@ export declare class DaytonaSandboxProvider implements SandboxProvider {
|
|
|
94
95
|
getAdditionalInstructions(): string | undefined;
|
|
95
96
|
getToolResultDumpDir(): string;
|
|
96
97
|
getGitCredentialsPath(): string;
|
|
98
|
+
getFileUploadsDir(): string;
|
|
99
|
+
getSkillsDir(): string;
|
|
100
|
+
getGitDownloaderPath(): string;
|
|
97
101
|
}
|
|
98
102
|
//# sourceMappingURL=DaytonaProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DaytonaProvider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/DaytonaProvider.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAgB,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"DaytonaProvider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/DaytonaProvider.ts"],"names":[],"mappings":"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"}
|
|
@@ -26,7 +26,8 @@ module.exports = __toCommonJS(DaytonaProvider_exports);
|
|
|
26
26
|
var import_sdk = require("@daytona/sdk");
|
|
27
27
|
var import_api = require("@opentelemetry/api");
|
|
28
28
|
var import_core = require("@opentelemetry/core");
|
|
29
|
-
var
|
|
29
|
+
var import_node_crypto = require("crypto");
|
|
30
|
+
var import_posix = require("path/posix");
|
|
30
31
|
var import_errorLogFields = require("../../util/errorLogFields.js");
|
|
31
32
|
var import_SandboxErrors = require("../SandboxErrors.js");
|
|
32
33
|
var import_CodeModeNatsTransport = require("../codeMode/nats/CodeModeNatsTransport.js");
|
|
@@ -67,6 +68,7 @@ function httpUrlToWsUrl(url) {
|
|
|
67
68
|
return parsed.toString();
|
|
68
69
|
}
|
|
69
70
|
var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
71
|
+
type = "daytona";
|
|
70
72
|
tenantName;
|
|
71
73
|
/** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */
|
|
72
74
|
imageUri;
|
|
@@ -104,13 +106,14 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
104
106
|
}
|
|
105
107
|
async getOrCreateSandbox(sandboxId) {
|
|
106
108
|
if (sandboxId) {
|
|
109
|
+
(0, import_SandboxErrors.validateSandboxOwnedByTenant)({ sandboxId, tenantName: this.tenantName });
|
|
107
110
|
const cached = _DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);
|
|
108
111
|
if (cached) {
|
|
109
112
|
return cached;
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
const sandbox = sandboxId ? await this.restoreExistingSandbox(sandboxId) : await this.daytona.create({
|
|
113
|
-
name: `${this.tenantName}.${(0,
|
|
116
|
+
name: `${this.tenantName}.${(0, import_node_crypto.randomUUID)()}`,
|
|
114
117
|
snapshot: this.buildRef,
|
|
115
118
|
autoStopInterval: this.autoStopIntervalInMinutes,
|
|
116
119
|
autoArchiveInterval: this.autoArchiveIntervalInMinutes,
|
|
@@ -323,6 +326,9 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
323
326
|
});
|
|
324
327
|
} catch (e) {
|
|
325
328
|
_DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);
|
|
329
|
+
if (e instanceof import_SandboxErrors.SandboxNotAvailableError) {
|
|
330
|
+
throw e;
|
|
331
|
+
}
|
|
326
332
|
this.logger.error("Sandbox execution error", (0, import_errorLogFields.extractErrorLogFields)(e));
|
|
327
333
|
const message = e instanceof Error ? e.message : "Unknown error";
|
|
328
334
|
return { success: false, error: message };
|
|
@@ -399,17 +405,33 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
399
405
|
return httpUrlToWsUrl(previewUrl);
|
|
400
406
|
},
|
|
401
407
|
sandboxClientNatsUrl: `ws://localhost:${String(this.natsBridgePort)}`,
|
|
402
|
-
logger: this.logger
|
|
408
|
+
logger: this.logger,
|
|
409
|
+
mcpClientInstall: {
|
|
410
|
+
remotePath: (0, import_posix.join)("/opt", "tf", "mcp-client", "mcp_client.py"),
|
|
411
|
+
pathBinSymlink: (0, import_posix.join)("/usr", "local", "bin", "mcp-client")
|
|
412
|
+
}
|
|
403
413
|
});
|
|
404
414
|
}
|
|
405
415
|
getAdditionalInstructions() {
|
|
406
416
|
return void 0;
|
|
407
417
|
}
|
|
418
|
+
// Isolated container: image-absolute layout. GIT_CONFIG store --file needs an absolute path.
|
|
419
|
+
// /opt/tf/{uploads,skills,tool-results,git_downloader.py,.git-credentials}
|
|
420
|
+
// /opt/tfy/mcp-client/mcp_client.py + /usr/local/bin/mcp-client (image PATH; no layout bin)
|
|
408
421
|
getToolResultDumpDir() {
|
|
409
|
-
return "/
|
|
422
|
+
return (0, import_posix.join)("/opt", "tf", "tool-results");
|
|
410
423
|
}
|
|
411
424
|
getGitCredentialsPath() {
|
|
412
|
-
return "/
|
|
425
|
+
return (0, import_posix.join)("/opt", "tf", ".git-credentials");
|
|
426
|
+
}
|
|
427
|
+
getFileUploadsDir() {
|
|
428
|
+
return (0, import_posix.join)("/opt", "tf", "uploads");
|
|
429
|
+
}
|
|
430
|
+
getSkillsDir() {
|
|
431
|
+
return (0, import_posix.join)("/opt", "tf", "skills");
|
|
432
|
+
}
|
|
433
|
+
getGitDownloaderPath() {
|
|
434
|
+
return (0, import_posix.join)("/opt", "tf", "git_downloader.py");
|
|
413
435
|
}
|
|
414
436
|
};
|
|
415
437
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -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 'crypto';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxNotAvailableError,\n SandboxPathIsDirectoryError,\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 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 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 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 });\n }\n\n getAdditionalInstructions(): string | undefined {\n return undefined;\n }\n\n getToolResultDumpDir(): string {\n return '/tmp/tool-results';\n }\n\n getGitCredentialsPath(): string {\n // Isolated container per sandbox; absolute path so GIT_CONFIG_* needs no $HOME expansion.\n return '/tmp/.git-credentials';\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,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":[]}
|
|
@@ -3,12 +3,14 @@ import { DaytonaError } from "@daytona/sdk";
|
|
|
3
3
|
import { context } from "@opentelemetry/api";
|
|
4
4
|
import { suppressTracing } from "@opentelemetry/core";
|
|
5
5
|
import { randomUUID } from "crypto";
|
|
6
|
+
import { join } from "path/posix";
|
|
6
7
|
import { extractErrorLogFields } from "../../util/errorLogFields.mjs";
|
|
7
8
|
import {
|
|
8
9
|
SandboxFileNotFoundError,
|
|
9
10
|
SandboxFileTooLargeError,
|
|
10
11
|
SandboxNotAvailableError,
|
|
11
|
-
SandboxPathIsDirectoryError
|
|
12
|
+
SandboxPathIsDirectoryError,
|
|
13
|
+
validateSandboxOwnedByTenant
|
|
12
14
|
} from "../SandboxErrors.mjs";
|
|
13
15
|
import { CodeModeNatsTransport } from "../codeMode/nats/CodeModeNatsTransport.mjs";
|
|
14
16
|
import { DEFAULT_PREVIEW_URL_EXPIRY_SECONDS, DEFAULT_SANDBOX_NATS_WS_PORT } from "../constants.mjs";
|
|
@@ -48,6 +50,7 @@ function httpUrlToWsUrl(url) {
|
|
|
48
50
|
return parsed.toString();
|
|
49
51
|
}
|
|
50
52
|
var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
53
|
+
type = "daytona";
|
|
51
54
|
tenantName;
|
|
52
55
|
/** Release-owned sandbox image reference; built into a Daytona snapshot and cloned per sandbox. */
|
|
53
56
|
imageUri;
|
|
@@ -85,6 +88,7 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
85
88
|
}
|
|
86
89
|
async getOrCreateSandbox(sandboxId) {
|
|
87
90
|
if (sandboxId) {
|
|
91
|
+
validateSandboxOwnedByTenant({ sandboxId, tenantName: this.tenantName });
|
|
88
92
|
const cached = _DaytonaSandboxProvider.cachedSandboxes.get(sandboxId);
|
|
89
93
|
if (cached) {
|
|
90
94
|
return cached;
|
|
@@ -304,6 +308,9 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
304
308
|
});
|
|
305
309
|
} catch (e) {
|
|
306
310
|
_DaytonaSandboxProvider.cachedSandboxes.delete(params.sandboxId);
|
|
311
|
+
if (e instanceof SandboxNotAvailableError) {
|
|
312
|
+
throw e;
|
|
313
|
+
}
|
|
307
314
|
this.logger.error("Sandbox execution error", extractErrorLogFields(e));
|
|
308
315
|
const message = e instanceof Error ? e.message : "Unknown error";
|
|
309
316
|
return { success: false, error: message };
|
|
@@ -380,17 +387,33 @@ var DaytonaSandboxProvider = class _DaytonaSandboxProvider {
|
|
|
380
387
|
return httpUrlToWsUrl(previewUrl);
|
|
381
388
|
},
|
|
382
389
|
sandboxClientNatsUrl: `ws://localhost:${String(this.natsBridgePort)}`,
|
|
383
|
-
logger: this.logger
|
|
390
|
+
logger: this.logger,
|
|
391
|
+
mcpClientInstall: {
|
|
392
|
+
remotePath: join("/opt", "tf", "mcp-client", "mcp_client.py"),
|
|
393
|
+
pathBinSymlink: join("/usr", "local", "bin", "mcp-client")
|
|
394
|
+
}
|
|
384
395
|
});
|
|
385
396
|
}
|
|
386
397
|
getAdditionalInstructions() {
|
|
387
398
|
return void 0;
|
|
388
399
|
}
|
|
400
|
+
// Isolated container: image-absolute layout. GIT_CONFIG store --file needs an absolute path.
|
|
401
|
+
// /opt/tf/{uploads,skills,tool-results,git_downloader.py,.git-credentials}
|
|
402
|
+
// /opt/tfy/mcp-client/mcp_client.py + /usr/local/bin/mcp-client (image PATH; no layout bin)
|
|
389
403
|
getToolResultDumpDir() {
|
|
390
|
-
return "/
|
|
404
|
+
return join("/opt", "tf", "tool-results");
|
|
391
405
|
}
|
|
392
406
|
getGitCredentialsPath() {
|
|
393
|
-
return "/
|
|
407
|
+
return join("/opt", "tf", ".git-credentials");
|
|
408
|
+
}
|
|
409
|
+
getFileUploadsDir() {
|
|
410
|
+
return join("/opt", "tf", "uploads");
|
|
411
|
+
}
|
|
412
|
+
getSkillsDir() {
|
|
413
|
+
return join("/opt", "tf", "skills");
|
|
414
|
+
}
|
|
415
|
+
getGitDownloaderPath() {
|
|
416
|
+
return join("/opt", "tf", "git_downloader.py");
|
|
394
417
|
}
|
|
395
418
|
};
|
|
396
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 'crypto';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxNotAvailableError,\n SandboxPathIsDirectoryError,\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 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 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 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 });\n }\n\n getAdditionalInstructions(): string | undefined {\n return undefined;\n }\n\n getToolResultDumpDir(): string {\n return '/tmp/tool-results';\n }\n\n getGitCredentialsPath(): string {\n // Isolated container per sandbox; absolute path so GIT_CONFIG_* needs no $HOME expansion.\n return '/tmp/.git-credentials';\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,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":[]}
|