@truefoundry/trueforge-core 0.1.0-rc.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/dist/core/index.d.ts +9 -3
  2. package/dist/core/index.d.ts.map +1 -1
  3. package/dist/core/index.js +25 -0
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/index.mjs +24 -2
  6. package/dist/core/index.mjs.map +1 -1
  7. package/dist/core/llm/VercelAILLM.d.ts +1 -0
  8. package/dist/core/llm/VercelAILLM.d.ts.map +1 -1
  9. package/dist/core/llm/VercelAILLM.js +9 -14
  10. package/dist/core/llm/VercelAILLM.js.map +1 -1
  11. package/dist/core/llm/VercelAILLM.mjs +9 -15
  12. package/dist/core/llm/VercelAILLM.mjs.map +1 -1
  13. package/dist/core/runtime/AgentThread.d.ts.map +1 -1
  14. package/dist/core/runtime/AgentThread.js +3 -1
  15. package/dist/core/runtime/AgentThread.js.map +1 -1
  16. package/dist/core/runtime/AgentThread.mjs +3 -1
  17. package/dist/core/runtime/AgentThread.mjs.map +1 -1
  18. package/dist/core/sandbox/Sandbox.d.ts +7 -9
  19. package/dist/core/sandbox/Sandbox.d.ts.map +1 -1
  20. package/dist/core/sandbox/Sandbox.js +45 -61
  21. package/dist/core/sandbox/Sandbox.js.map +1 -1
  22. package/dist/core/sandbox/Sandbox.mjs +46 -62
  23. package/dist/core/sandbox/Sandbox.mjs.map +1 -1
  24. package/dist/core/sandbox/codeMode/CodeModeDispatcher.d.ts +29 -0
  25. package/dist/core/sandbox/codeMode/CodeModeDispatcher.d.ts.map +1 -0
  26. package/dist/core/sandbox/codeMode/CodeModeDispatcher.js +128 -0
  27. package/dist/core/sandbox/codeMode/CodeModeDispatcher.js.map +1 -0
  28. package/dist/core/sandbox/codeMode/CodeModeDispatcher.mjs +107 -0
  29. package/dist/core/sandbox/codeMode/CodeModeDispatcher.mjs.map +1 -0
  30. package/dist/core/sandbox/codeMode/CodeModeTransport.d.ts +21 -0
  31. package/dist/core/sandbox/codeMode/CodeModeTransport.d.ts.map +1 -0
  32. package/dist/core/sandbox/codeMode/CodeModeTransport.js +19 -0
  33. package/dist/core/sandbox/codeMode/CodeModeTransport.js.map +1 -0
  34. package/dist/core/sandbox/codeMode/CodeModeTransport.mjs +1 -0
  35. package/dist/core/sandbox/codeMode/CodeModeTransport.mjs.map +1 -0
  36. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.d.ts +34 -0
  37. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.d.ts.map +1 -0
  38. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.js +187 -0
  39. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.js.map +1 -0
  40. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.mjs +162 -0
  41. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.mjs.map +1 -0
  42. package/dist/core/sandbox/codeMode/types.d.ts +34 -0
  43. package/dist/core/sandbox/codeMode/types.d.ts.map +1 -0
  44. package/dist/core/sandbox/codeMode/types.js +59 -0
  45. package/dist/core/sandbox/codeMode/types.js.map +1 -0
  46. package/dist/core/sandbox/codeMode/types.mjs +32 -0
  47. package/dist/core/sandbox/codeMode/types.mjs.map +1 -0
  48. package/dist/core/sandbox/provider/DaytonaProvider.d.ts +2 -1
  49. package/dist/core/sandbox/provider/DaytonaProvider.d.ts.map +1 -1
  50. package/dist/core/sandbox/provider/DaytonaProvider.js +13 -6
  51. package/dist/core/sandbox/provider/DaytonaProvider.js.map +1 -1
  52. package/dist/core/sandbox/provider/DaytonaProvider.mjs +13 -6
  53. package/dist/core/sandbox/provider/DaytonaProvider.mjs.map +1 -1
  54. package/dist/core/sandbox/provider/Provider.d.ts +4 -3
  55. package/dist/core/sandbox/provider/Provider.d.ts.map +1 -1
  56. package/dist/core/sandbox/provider/Provider.js.map +1 -1
  57. package/dist/core/sandbox/provider/Provider.mjs.map +1 -1
  58. package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts +2 -1
  59. package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts.map +1 -1
  60. package/dist/core/sandbox/provider/TFYSandboxProvider.js +8 -2
  61. package/dist/core/sandbox/provider/TFYSandboxProvider.js.map +1 -1
  62. package/dist/core/sandbox/provider/TFYSandboxProvider.mjs +8 -2
  63. package/dist/core/sandbox/provider/TFYSandboxProvider.mjs.map +1 -1
  64. package/dist/core/sandbox/sandboxImage.d.ts +1 -1
  65. package/dist/core/sandbox/sandboxImage.d.ts.map +1 -1
  66. package/dist/core/sandbox/sandboxImage.js +1 -1
  67. package/dist/core/sandbox/sandboxImage.js.map +1 -1
  68. package/dist/core/sandbox/sandboxImage.mjs +1 -1
  69. package/dist/core/sandbox/sandboxImage.mjs.map +1 -1
  70. package/dist/core/sandbox/sandboxScripts.gen.d.ts +1 -1
  71. package/dist/core/sandbox/sandboxScripts.gen.d.ts.map +1 -1
  72. package/dist/core/sandbox/sandboxScripts.gen.js +8 -8
  73. package/dist/core/sandbox/sandboxScripts.gen.js.map +1 -1
  74. package/dist/core/sandbox/sandboxScripts.gen.mjs +8 -8
  75. package/dist/core/sandbox/sandboxScripts.gen.mjs.map +1 -1
  76. package/dist/core/util/errorLogFields.d.ts +5 -0
  77. package/dist/core/util/errorLogFields.d.ts.map +1 -1
  78. package/dist/core/util/errorLogFields.js +38 -2
  79. package/dist/core/util/errorLogFields.js.map +1 -1
  80. package/dist/core/util/errorLogFields.mjs +37 -2
  81. package/dist/core/util/errorLogFields.mjs.map +1 -1
  82. package/package.json +1 -1
  83. package/dist/core/sandbox/SandboxNatsBridge.d.ts +0 -33
  84. package/dist/core/sandbox/SandboxNatsBridge.d.ts.map +0 -1
  85. package/dist/core/sandbox/SandboxNatsBridge.js +0 -222
  86. package/dist/core/sandbox/SandboxNatsBridge.js.map +0 -1
  87. package/dist/core/sandbox/SandboxNatsBridge.mjs +0 -201
  88. package/dist/core/sandbox/SandboxNatsBridge.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/sandbox/Sandbox.ts"],"sourcesContent":["import { context, propagation } from '@opentelemetry/api';\nimport dedent from 'dedent';\nimport type { Logger } from 'winston';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../InstructionBuilder';\nimport { estimateTokensForString } from '../llm/usage';\nimport type { MappedMCPTool } from '../mcp/convertMCPServers';\nimport { toolResultResponse, type CallToolResponse, type IToolSet } from '../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP } from '../mcp/LocalToolMCP';\nimport {\n DEFERRED_TOOLS_SERVER_ID,\n GET_TOOL_INFO_NAME,\n GET_TOOL_OUTPUT_SCHEMA_NAME,\n LIST_TOOLS_NAME,\n} from '../runtime/DeferredTool';\nimport type { AgentTracing } from '../tracing/AgentTracing';\nimport { extractErrorLogFields } from '../util/errorLogFields';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT, SANDBOX_FILE_UPLOADS_DIR } from './constants';\nimport { ensureExecSuccess, shellEscape, type SandboxProvider } from './provider/Provider';\nimport { validateNoPathTraversal, validateSandboxOwnedByTenant } from './SandboxErrors';\nimport { SandboxNatsBridge } from './SandboxNatsBridge';\nimport { sandboxScripts } from './sandboxScripts.gen';\n// Import submodules, not the ./skills barrel, to avoid a cycle (the mounters import from Sandbox).\nimport { SKILLS_DIR } from './skills/constants';\nimport type { ISkillMounter } from './skills/ISkillMounter';\n\nexport interface SandboxInfo {\n sandbox_id: string;\n}\n\n// Downloader/setup scripts can run longer than a normal exec.\nexport const SKILL_DOWNLOAD_TIMEOUT_SECONDS = 180;\n\n// Keeps the sandbox-side NATS wait longer than the host's own MCP deadline, so the host\n// surfaces the real MCP error instead of the bridge client giving up first.\nconst NATS_REQUEST_TIMEOUT_BUFFER_SECONDS = 30;\n\n// Write a script (base64-encoded, never interpolated raw) to a path and run it. Skill mounters reuse it.\nexport function buildWriteAndRunScriptCommand(params: { scriptPath: string; scriptContent: string }): string {\n const b64 = Buffer.from(params.scriptContent, 'utf-8').toString('base64');\n return [\n `mkdir -p \"$(dirname ${params.scriptPath})\"`,\n `rm -f ${params.scriptPath}`,\n `echo ${shellEscape(b64)} | base64 -d > ${params.scriptPath}`,\n `chmod a-w ${params.scriptPath}`,\n `python3 ${params.scriptPath}`,\n ].join(' && ');\n}\n\n/** Write or clear the git credential-store file at an absolute path (no global git config mutation). */\nfunction buildSyncGitCredentialsCommand(credentialsContent: string | null, credentialsPath: string): string {\n if (credentialsContent === null) {\n return `rm -f ${credentialsPath}`;\n }\n // Base64 avoids interpolating credentials directly into the shell command.\n const b64 = Buffer.from(credentialsContent, 'utf-8').toString('base64');\n return (\n `mkdir -p \"$(dirname ${credentialsPath})\" && ` +\n `echo '${b64}' | base64 -d > ${credentialsPath} && ` +\n `chmod 600 ${credentialsPath}`\n );\n}\n\n/**\n * Process-scoped credential.helper via GIT_CONFIG_* (avoids shared ~/.gitconfig).\n * Required for TFY sandbox where many logical sandboxes share one pod/HOME.\n */\nfunction buildGitCredentialHelperEnv(credentialsPath: string): Record<string, string> {\n return {\n GIT_CONFIG_COUNT: '1',\n GIT_CONFIG_KEY_0: 'credential.helper',\n GIT_CONFIG_VALUE_0: `store --file ${credentialsPath}`,\n };\n}\n\nexport interface SandboxStoredFile {\n filePath: string;\n sandboxCreated?: SandboxInfo | undefined;\n}\n\nexport interface SandboxOptions {\n provider: SandboxProvider;\n existingSandboxId?: string | undefined;\n skillMounter?: ISkillMounter | undefined;\n fileDownloadEnabled?: boolean | undefined;\n /** Pre-resolved credential-store file content (null = clear / no git auth). */\n resolvedGitCredentialsContent?: string | null | undefined;\n /**\n * Blocks destructive tools in code mode so they go through the approval flow\n * instead. Must be `true` — approvals are always enabled (the kill switch is gone).\n */\n blockDestructiveToolsInCodeMode: true;\n /** Used to derive the Code Mode NATS wait as request + connect. */\n mcpRequestTimeoutMs: number;\n mcpConnectTimeoutMs: number;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n tracing: AgentTracing;\n logger: Logger;\n}\n\nexport const SANDBOX_EXEC_TOOL_NAME = 'exec';\nconst SANDBOX_MCP_REMINDER_TAG = 'sandbox-mcp-code-mode';\nconst SANDBOX_FILE_OUTPUT_TAG = 'sandbox-file-output';\nexport const SANDBOX_SCHEMA_INFER_TAG = 'sandbox-schema-infer';\nconst SANDBOX_FILE_UPLOADS_TAG = 'sandbox-file-uploads';\n\n// Stable directory inside the sandbox where the MCP client library is uploaded.\n// MCP client upload dir. Two lookup channels:\n// - Python imports → PYTHONPATH points here (set by injectMCPClientEnv).\n// - Shell `mcp-client` → symlinked into MCP_CLIENT_BIN_SYMLINK (on default PATH);\n// basename drops `.py` so the agent doesn't reflexively prefix with `python`.\nconst MCP_CLIENT_DIR = '/opt/tfy/mcp-client';\nconst MCP_CLIENT_PATH = `${MCP_CLIENT_DIR}/mcp_client.py`;\nconst MCP_CLIENT_BIN_SYMLINK = '/usr/local/bin/mcp-client';\n\n// Per-server config sent to mcp_client.py via TFY_MCP_SERVERS.\ninterface SandboxMcpServerConfig {\n allowed_tools: string[];\n}\n\n// Snapshots the active W3C trace context (`traceparent` + optional `tracestate`) into env\n// vars consumed by mcp_client.py. Read with TFY_TRACEPARENT / TFY_TRACESTATE on the sandbox\n// side and attached as NATS message headers; the bridge extracts them on each message so the\n// resulting MCP spans nest under the originating Sandbox: exec span.\nfunction injectTraceContextEnv(): Record<string, string> {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const env: Record<string, string> = {};\n if (carrier['traceparent']) env['TFY_TRACEPARENT'] = carrier['traceparent'];\n if (carrier['tracestate']) env['TFY_TRACESTATE'] = carrier['tracestate'];\n return env;\n}\n\n/**\n * Injects environment variables required for mcp_client.py into sandbox_exec arguments.\n *\n * `natsBridgeSubjectPrefix` MUST be the prefix the gateway-side bridge subscribed to; the\n * sandbox script publishes to `<prefix>.mcp` (operation in the payload's `op` field) and only\n * this bridge receives them. See SandboxNatsBridge.\n */\nfunction injectMCPClientEnv(params: {\n env?: Record<string, string> | undefined;\n mcpServers: Record<string, SandboxMcpServerConfig>;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n natsBridgeSubjectPrefix?: string | undefined;\n natsRequestTimeoutSeconds: number;\n}): Record<string, string> {\n return {\n ...(params.execExtraEnv ?? {}),\n ...(params.env ?? {}),\n PYTHONPATH: MCP_CLIENT_DIR,\n ...(Object.keys(params.mcpServers).length > 0 && {\n TFY_MCP_SERVERS: Buffer.from(JSON.stringify(params.mcpServers)).toString('base64'),\n }),\n ...(params.natsBridgeSubjectPrefix && {\n TFY_NATS_URL: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n TFY_NATS_SUBJECT_PREFIX: params.natsBridgeSubjectPrefix,\n TFY_NATS_REQUEST_TIMEOUT_SECONDS: String(params.natsRequestTimeoutSeconds),\n // W3C trace context captured per-exec so each NATS request carries the originating\n // Sandbox: exec span as parent. Without this, MCP spans dispatched via the bridge\n // inherit whatever ALS context the NATS callback runs under (stale, shared across execs).\n ...injectTraceContextEnv(),\n }),\n // Approvals are always enabled: destructive tools cannot run in code mode.\n TFY_ENABLE_AGENT_APPROVALS: 'true',\n };\n}\n\nconst sandboxExecSchema = z.object({\n intent: z\n .string()\n .describe(\n 'A brief, user-facing explanation of the purpose of this command. Avoid technical jargon and code details.',\n ),\n command: z.string().describe('The shell command to execute in the sandbox.'),\n cwd: z.string().optional().describe('Working directory for command execution.'),\n env: z.record(z.string(), z.string()).optional().describe('Additional environment variables to set.'),\n});\n\nconst SANDBOX_EXEC_DESCRIPTION = `Execute shell commands in a persistent sandbox environment.\nThe sandbox persists across calls within the same session, allowing stateful operations.\nPre-installed: Python 3.13, pydantic, git, curl, helm, jq, ripgrep, genson.\nIMPORTANT: The Agent must never list, print, or expose environment variables as they contain sensitive credentials.\nIMPORTANT: Never read or expose any git credential files or ~/.git-credentials.\n`;\n\nexport function createSandboxLargeToolResponseGuidance(): string {\n return `For use cases where the Agent needs a subset of fields from the MCP response or needs deterministic processing, write code with the provided MCP client and use <${SANDBOX_MCP_REMINDER_TAG}>.`;\n}\n\nexport const SANDBOX_MCP_SERVER_ID = 'sandbox';\ntype SandboxExecInput = z.infer<typeof sandboxExecSchema>;\n\nexport class Sandbox extends LocalToolMCP {\n readonly name = SANDBOX_MCP_SERVER_ID;\n readonly displayName = 'Sandbox';\n override readonly description = 'Persistent sandbox environment for code execution';\n static readonly FILE_UPLOADS_DIR = SANDBOX_FILE_UPLOADS_DIR;\n\n private readonly provider: SandboxProvider;\n private readonly existingSandboxId?: string | undefined;\n private readonly tenantName: string;\n private existingSandboxInfo: SandboxInfo | undefined;\n // Cached promise to prevent concurrent sub-agents from creating duplicate sandboxes.\n private sandboxCreationPromise?: Promise<SandboxInfo> | undefined;\n private sandboxInitPromise?: Promise<void> | undefined;\n private codeExecToolSets: readonly IToolSet[] = [];\n private readonly fileDownloadEnabled: boolean;\n private readonly natsRequestTimeoutSeconds: number;\n private readonly execExtraEnv?: Readonly<Record<string, string>> | undefined;\n private readonly skillMounter?: ISkillMounter | undefined;\n private cachedSkillsSection?: string | undefined;\n private readonly mcpClientScriptBase64: string;\n private readonly logger: Logger;\n // Pre-resolved credential-store file content (null = clear / no git auth).\n private readonly resolvedGitCredentialsContent: string | null;\n private natsBridgePromise?: Promise<SandboxNatsBridge | undefined> | undefined;\n\n private tools = [\n defineTool({\n name: SANDBOX_EXEC_TOOL_NAME,\n description: SANDBOX_EXEC_DESCRIPTION,\n schema: sandboxExecSchema,\n handler: input => this.handleExec(input),\n }),\n ];\n\n constructor(options: SandboxOptions) {\n super({ tracing: options.tracing });\n this.provider = options.provider;\n this.existingSandboxId = options.existingSandboxId;\n this.tenantName = options.execExtraEnv?.['TFY_TENANT_NAME'] ?? '';\n this.skillMounter = options.skillMounter;\n this.fileDownloadEnabled = options.fileDownloadEnabled ?? false;\n const mcpBoundTimeoutMs = options.mcpRequestTimeoutMs + options.mcpConnectTimeoutMs;\n this.natsRequestTimeoutSeconds = Math.ceil(mcpBoundTimeoutMs / 1000) + NATS_REQUEST_TIMEOUT_BUFFER_SECONDS;\n this.execExtraEnv = options.execExtraEnv;\n // Scripts are internal to Sandbox: the upload paths, env contract, and prompt\n // text are all hardcoded here, so injecting different content was never a\n // real extension point. Consumers don't see or provide them.\n this.mcpClientScriptBase64 = Buffer.from(sandboxScripts.mcpClient, 'utf-8').toString('base64');\n this.logger = options.logger.child({ module: 'Sandbox' });\n this.resolvedGitCredentialsContent = options.resolvedGitCredentialsContent ?? null;\n\n if (this.existingSandboxId) {\n validateSandboxOwnedByTenant(this.existingSandboxId, this.tenantName);\n this.existingSandboxInfo = { sandbox_id: this.existingSandboxId };\n }\n }\n\n private buildMcpServersEnvelope(): Record<string, SandboxMcpServerConfig> {\n const out: Record<string, SandboxMcpServerConfig> = {};\n for (const server of this.codeExecToolSets) {\n const allowedTools = server.getAllowedToolNamesForSandbox?.() ?? [];\n out[server.name] = { allowed_tools: allowedTools };\n }\n return out;\n }\n\n protected getTools() {\n return this.tools;\n }\n\n configureCodeMode(servers: readonly IToolSet[]): void {\n if (!servers.length) {\n return;\n }\n this.codeExecToolSets = servers;\n }\n\n estimateSkillsTokens(): number {\n return Math.round(estimateTokensForString(this.renderSkillsSection()));\n }\n\n // Single source for the <skills> section, so the prompt and the token estimate can't drift.\n private renderSkillsSection(): string {\n if (this.cachedSkillsSection === undefined) {\n const skills = new InstructionBuilder('skills');\n this.skillMounter?.instruction(skills);\n this.cachedSkillsSection = skills.build();\n }\n return this.cachedSkillsSection;\n }\n\n buildInstruction(builder: InstructionBuilder): void {\n const providerInstructions = this.provider.getAdditionalInstructions();\n\n const sandboxInstructions = builder.beginSection('sandbox');\n sandboxInstructions.addContent('The Agent has access to a persistent sandbox environment for executing code.');\n sandboxInstructions.addContent('The Agent must NOT read or modify any git credential files.');\n\n this.buildSchemaSection(sandboxInstructions);\n this.buildSkillsSection(sandboxInstructions);\n this.buildMCPSection(sandboxInstructions);\n this.buildFileUploadsSection(sandboxInstructions);\n\n if (this.fileDownloadEnabled) {\n this.buildFileOutputSection(sandboxInstructions);\n }\n\n if (providerInstructions) {\n sandboxInstructions.addContent(providerInstructions);\n }\n }\n\n private buildFileUploadsSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_UPLOADS_TAG,\n `User-uploaded files are placed in ${Sandbox.FILE_UPLOADS_DIR}/. The Agent must not modify files in this directory. Always create a copy at a different location and work on the copy.`,\n );\n }\n\n private buildSchemaSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_SCHEMA_INFER_TAG,\n dedent`\n To infer the schema of a file, the Agent MUST execute:\n head -c 200 file_path && echo \"==\" && genson file_path\n This serves two purpose:\n 1. The first 200 characters of the file is printed. Do NOT print more than 200 characters.\n 2. genson infers and returns JSON schema if the file is JSON.\n Even if the file is not JSON, (1) helps to take the next step in less number of turns\n `,\n );\n }\n\n private buildFileOutputSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_OUTPUT_TAG,\n dedent`\n ## File outputs\n\n The user does not have direct access to the sandbox filesystem. When the Agent wants to output a file to the user, it MUST:\n 1. Ensure that the file is present in the sandbox.\n 2. Then emit a fenced sandbox_artifacts block referencing the file.\n\n \\`\\`\\`sandbox_artifacts\n [Human-readable label](/absolute/path/to/file.ext)\n \\`\\`\\`\n\n For large outputs or content that can be generated through code, prefer writing to a file and emitting the sandbox_artifacts block.\n\n Rules:\n - Use absolute paths, not ~/ or relative paths.\n - One link per line. Markdown link syntax: [label](path).`,\n );\n }\n\n private buildSkillsSection(builder: InstructionBuilder): void {\n builder.addContent(this.renderSkillsSection());\n }\n\n private buildMCPSection(builder: InstructionBuilder): void {\n const toolSetNames = this.codeExecToolSets.map(m => m.name).join(',');\n if (!toolSetNames) return;\n\n builder.addSection(\n SANDBOX_MCP_REMINDER_TAG,\n dedent`\n The following MCP servers are accessible for Code Mode from ${SANDBOX_EXEC_TOOL_NAME} via the pre-installed mcp_client module: ${toolSetNames}\n Tool and functions are same.\n Tool and MCP tools are same.\n\n Use \\`from mcp_client import call_tool\\` in code, or \\`mcp-client\\` in shell — both work from any directory with no setup.\n\n For deferred MCP servers, the Agent MUST:\n 1. Discover tools via ${LIST_TOOLS_NAME} from ${DEFERRED_TOOLS_SERVER_ID}. Never call ${GET_TOOL_OUTPUT_SCHEMA_NAME}.\n 2. Discover inputSchema, outputSchema via ${GET_TOOL_INFO_NAME} for the selected tools.\n\n For non-deferred MCP servers, the Agent MUST:\n 1. Discover outputSchema via ${GET_TOOL_OUTPUT_SCHEMA_NAME} for the selected tools.\n\n For both deferred and non-deferred if outputSchema is absent,\n the Agent MUST say: \"Given the MCP Server does not define an outputSchema let me figure it out first before entering Code Mode.\"\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > file && ${SANDBOX_SCHEMA_INFER_TAG}.\n the Agent MUST execute a single command for above.\n If the Agent does not get the outputSchema, the Agent will encounters scenarios like:\n Agent: \"Let me fix the code - it seems the x might be a string rather than an object in some cases:\"\n The Agent MUST not face the above scenario.\n \n Only after the Agent has the above details, it can enter Code Mode.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n CLI: mcp-client call-tool {server} {tool_a} '{\"param\":\"value\"}'\n # The output of the above command MUST be passed through tools like jq or head -c\n # or should be written to a file. Goal is to reduce content that the Agent prints.\n\n Code: from mcp_client import call_tool\n async def main():\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n # In most cases, it is safe to assume response is a dict.\n # To know the dict keys, the Agent still needs to figure out the outputSchema.\n # The Agent MUST write the code to a file first before executing it.\n\n Code Mode MUST be used in following scenarios:\n\n 1. Reduce structured tool output by executing logic on top of it. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n from mcp_client import call_tool\n from collections import Counter\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n print(Counter(r[\"a\"] for r in result[\"b\"] if r[\"c\"])) # Here we need outputSchema to know a, b and c exists.\n\n 2. Piping result of one tool call to another tool request. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool1, let me enter Code Mode.\n\n from mcp_client import call_tool\n result1 = await call_tool(\"{server}\", \"{tool1}\", body={\"param1\": \"value1\"})\n result2 = await call_tool(\"{server}\", \"{tool2}\", body={\"param2\": result1[\"value1\"]}) # Here we need outputSchema to know value1 exists.\n print(result2) # Note in this case, we do not need outputSchema of result2 as we are not using any fields.\n\n The Agent MUST NOT use Code Mode just to print the full raw response of a tool. The Agent MUST call the tool directly in this case, there are other mechanisms in place to ensure\n large tool call outputs are not loaded in context.\n \n The Agent must not use Code Mode in the following patterns:\n from mcp_client import call_tool\n import json\n result = await call_tool(\"{server}\", \"{tool}\", body={\"param1\": \"value1\"})\n print(result) # Never do this. Call tool without Code Mode.\n print(json.dumps(result, ...)) # Never do this. Call tool without Code Mode.\n print(result[\"a\"][:100]) # Never do this. Call tool without Code Mode.\n\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > data && cat data # Never do this. Call tool without Code Mode.\n `,\n );\n }\n\n private get requiredSandboxInfo(): SandboxInfo {\n if (!this.existingSandboxInfo) {\n throw new Error('Sandbox info not available');\n }\n return this.existingSandboxInfo;\n }\n\n private async ensureSandboxCreated(): Promise<{\n sandboxInfo: SandboxInfo;\n sandboxCreated: SandboxInfo | undefined;\n }> {\n if (this.existingSandboxInfo) {\n return { sandboxInfo: this.existingSandboxInfo, sandboxCreated: undefined };\n }\n // Provider returns camelCase `{ sandboxId }`; rename to the snake_case\n // `SandboxInfo` shape used everywhere downstream (Redis JSON, wire event,\n // in-memory state).\n this.sandboxCreationPromise ??= this.provider\n .createSandbox()\n .then(({ sandboxId }) => {\n validateSandboxOwnedByTenant(sandboxId, this.tenantName);\n return { sandbox_id: sandboxId };\n })\n .catch((e: unknown) => {\n this.sandboxCreationPromise = undefined;\n throw e;\n });\n\n const sandboxInfo = await this.sandboxCreationPromise;\n // Concurrent callers may both await the same creation promise; only the first\n // should report sandboxCreated. Read via a local to avoid control-flow narrowing\n // that treats existingSandboxInfo as always undefined after the early return above.\n const prior = this.existingSandboxInfo as SandboxInfo | undefined;\n const isNew = prior === undefined;\n this.existingSandboxInfo = sandboxInfo;\n return { sandboxInfo, sandboxCreated: isNew ? sandboxInfo : undefined };\n }\n\n private async handleExec(input: SandboxExecInput): Promise<CallToolResponse> {\n const { sandboxInfo, sandboxCreated } = await this.ensureSandboxCreated();\n const sandboxCreatedFlag = Boolean(sandboxCreated);\n try {\n await this.ensureSandboxInitialized();\n } catch (e) {\n this.logger.error('Sandbox initialization failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Sandbox initialization failed';\n return toolResultResponse({\n text: `Sandbox initialization failed: ${message}`,\n isError: true,\n overrides: { sandboxCreated: sandboxCreatedFlag, sandboxInfo },\n });\n }\n const bridge = await this.ensureNatsBridgeConnected(sandboxInfo.sandbox_id);\n const mcpClientEnv = injectMCPClientEnv({\n env: input.env,\n mcpServers: this.buildMcpServersEnvelope(),\n execExtraEnv: this.execExtraEnv,\n natsBridgeSubjectPrefix: bridge?.subjectPrefix,\n natsRequestTimeoutSeconds: this.natsRequestTimeoutSeconds,\n });\n const gitAuthEnv =\n this.resolvedGitCredentialsContent !== null\n ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(sandboxInfo.sandbox_id))\n : {};\n const env = { ...mcpClientEnv, ...gitAuthEnv };\n\n const result = await this.provider.exec({\n sandboxId: sandboxInfo.sandbox_id,\n command: input.command,\n cwd: input.cwd,\n env,\n });\n\n return {\n result: {\n content: [{ type: 'text', text: JSON.stringify(result) }],\n isError: !result.success,\n },\n wasInitialized: undefined,\n sandboxCreated: sandboxCreatedFlag,\n sandboxInfo,\n };\n }\n\n async getToolResultDumpDir(): Promise<{ dir: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n const dir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id).replace(/\\/+$/, '');\n return { dir, sandboxCreated };\n }\n\n async uploadFile(params: {\n targetDir: string;\n fileName: string;\n content: Buffer;\n }): Promise<{ filePath: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n await this.ensureSandboxInitialized();\n const { sandbox_id: sandboxId } = this.requiredSandboxInfo;\n const fileName = params.fileName.replace(/^\\/+/, '');\n const targetDir = params.targetDir.replace(/\\/+$/, '');\n const filePath = `${targetDir}/${fileName}`;\n await this.provider.uploadFile({ sandboxId, remotePath: filePath, content: params.content });\n return { filePath, sandboxCreated };\n }\n\n async uploadUserFile(input: { fileName: string; content: Buffer; mime: string }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const result = await this.uploadFile({\n targetDir: Sandbox.FILE_UPLOADS_DIR,\n fileName: input.fileName,\n content: input.content,\n });\n return {\n filePath: result.filePath,\n ...(result.sandboxCreated && { sandboxCreated: result.sandboxCreated }),\n };\n }\n\n async writeArtifact(input: {\n fileName: string;\n content: Buffer;\n sourceTool?: MappedMCPTool | undefined;\n }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const { dir, sandboxCreated: dumpDirCreated } = await this.getToolResultDumpDir();\n const result = await this.uploadFile({\n targetDir: dir,\n fileName: input.fileName,\n content: input.content,\n });\n const sandboxCreated = dumpDirCreated ?? result.sandboxCreated;\n return {\n filePath: result.filePath,\n ...(sandboxCreated && { sandboxCreated }),\n };\n }\n\n /**\n * Upload the MCP client Python script to the sandbox on the first tool call.\n * The sandbox filesystem is persistent within a session, so this only needs to run once.\n */\n private async ensureSandboxInitialized(): Promise<void> {\n this.sandboxInitPromise ??= this.initSandboxEnvironment().catch((e: unknown) => {\n this.sandboxInitPromise = undefined;\n throw e;\n });\n await this.sandboxInitPromise;\n }\n\n private async writeGitCredentials(): Promise<void> {\n const sandboxId = this.requiredSandboxInfo.sandbox_id;\n const credentialsPath = this.provider.getGitCredentialsPath(sandboxId);\n const result = await this.provider.exec({\n sandboxId,\n command: buildSyncGitCredentialsCommand(this.resolvedGitCredentialsContent, credentialsPath),\n });\n ensureExecSuccess(result);\n }\n\n private async initSandboxEnvironment(): Promise<void> {\n const toolResultDumpDir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id);\n\n this.logger.info('Uploading MCP client script and preparing skills directory in sandbox');\n\n const initSteps = [\n `mkdir -p ${MCP_CLIENT_DIR} ${Sandbox.FILE_UPLOADS_DIR} ${toolResultDumpDir} ${SKILLS_DIR}`,\n `rm -f ${MCP_CLIENT_PATH}`,\n `echo '${this.mcpClientScriptBase64}' | base64 -d > ${MCP_CLIENT_PATH}`,\n // Make executable + symlink onto PATH so `mcp-client` runs by name.\n `chmod 0555 ${MCP_CLIENT_PATH}`,\n `ln -sf ${MCP_CLIENT_PATH} ${MCP_CLIENT_BIN_SYMLINK}`,\n ];\n\n // Fold skill installation into this single init exec. The mounter returns a declarative\n // command + env + timeout; runs even when empty so its downloader can prune a reused sandbox.\n const skillInit = this.skillMounter?.getSandboxInit();\n if (skillInit) {\n initSteps.push(skillInit.command);\n }\n\n const script = initSteps.join(' && ');\n const result = await this.provider.exec({\n sandboxId: this.requiredSandboxInfo.sandbox_id,\n command: script,\n env: skillInit?.env,\n timeoutSeconds: skillInit?.timeoutSeconds,\n });\n ensureExecSuccess(result);\n this.logger.info(\n `Sandbox initialized: MCP client at ${MCP_CLIENT_PATH} (symlinked to ${MCP_CLIENT_BIN_SYMLINK}); skills dir ${SKILLS_DIR}`,\n );\n\n await this.writeGitCredentials();\n }\n\n /**\n * Lazily connect the sandbox→gateway NATS bridge. Single-flight and idempotent.\n * Skipped when there are no MCP servers exposed to code mode (nothing to bridge). Connect\n * failures resolve to `undefined` (no NATS env for that exec); mcp_client then errors at call\n * time — there is no HTTPS fallback.\n */\n private async ensureNatsBridgeConnected(sandboxId: string): Promise<SandboxNatsBridge | undefined> {\n // No MCP servers means the bridge has nothing to dispatch to. Avoid the connect cost.\n if (this.codeExecToolSets.length === 0) return undefined;\n this.natsBridgePromise ??= this.connectNatsBridge(sandboxId).catch((e: unknown) => {\n // Connect-level errors (network, timeout, broker not up) are not sticky: a later exec\n // may succeed (e.g. after sandbox warmup). Wipe the cached promise so retries happen.\n this.logger.error('Sandbox NATS bridge connect failed', {\n ...extractErrorLogFields(e),\n sandboxId,\n });\n this.natsBridgePromise = undefined;\n return undefined;\n });\n return this.natsBridgePromise;\n }\n\n private async connectNatsBridge(sandboxId: string): Promise<SandboxNatsBridge> {\n const wsUrl = await this.provider.getNatsBridgeUrl(sandboxId);\n const toolSets = new Map<string, IToolSet>();\n for (const server of this.codeExecToolSets) {\n toolSets.set(server.name, server);\n }\n const bridge = await SandboxNatsBridge.connect({ url: wsUrl, toolSets, logger: this.logger });\n this.logger.info('Sandbox NATS bridge connected', {\n sandboxId,\n toolSetCount: toolSets.size,\n });\n this.attachBridgeInvalidationOnClose(bridge, sandboxId);\n return bridge;\n }\n\n private attachBridgeInvalidationOnClose(bridge: SandboxNatsBridge, sandboxId: string): void {\n const cachedPromise = this.natsBridgePromise;\n // fire and forget\n void bridge.whenClosed().then(() => {\n if (this.natsBridgePromise === cachedPromise) {\n this.natsBridgePromise = undefined;\n this.logger.info('Sandbox NATS bridge closed; cache invalidated, next exec will reconnect', { sandboxId });\n }\n });\n }\n\n // Tears down per-Sandbox resources. Awaits the in-flight connect (if any) and explicitly\n // closes the bridge — dropping the field alone would leak the WS socket and live\n // subscriptions, and orphan a bridge that finishes connecting after close() returned.\n async close(): Promise<void> {\n const pendingPromise = this.natsBridgePromise;\n this.natsBridgePromise = undefined;\n if (!pendingPromise) return;\n let bridge: SandboxNatsBridge | undefined;\n try {\n bridge = await pendingPromise;\n } catch {\n // Connect-error path already logged inside ensureNatsBridgeConnected's .catch.\n }\n if (!bridge) return;\n try {\n await bridge.close();\n } catch (e) {\n this.logger.warn('Error closing sandbox NATS bridge', extractErrorLogFields(e));\n }\n }\n}\n"],"mappings":"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","names":["import_constants","dedent"]}
1
+ {"version":3,"sources":["../../../src/core/sandbox/Sandbox.ts"],"sourcesContent":["import { context, propagation } from '@opentelemetry/api';\nimport dedent from 'dedent';\nimport type { Logger } from 'winston';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../InstructionBuilder';\nimport { estimateTokensForString } from '../llm/usage';\nimport type { MappedMCPTool } from '../mcp/convertMCPServers';\nimport { toolResultResponse, type CallToolResponse, type IToolSet } from '../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP } from '../mcp/LocalToolMCP';\nimport {\n DEFERRED_TOOLS_SERVER_ID,\n GET_TOOL_INFO_NAME,\n GET_TOOL_OUTPUT_SCHEMA_NAME,\n LIST_TOOLS_NAME,\n} from '../runtime/DeferredTool';\nimport type { AgentTracing } from '../tracing/AgentTracing';\nimport { extractErrorLogFields } from '../util/errorLogFields';\nimport { CodeModeDispatcher } from './codeMode/CodeModeDispatcher';\nimport type { CodeModeTransport } from './codeMode/CodeModeTransport';\nimport { SANDBOX_FILE_UPLOADS_DIR } from './constants';\nimport { ensureExecSuccess, shellEscape, type SandboxProvider } from './provider/Provider';\nimport { validateNoPathTraversal, validateSandboxOwnedByTenant } from './SandboxErrors';\nimport { sandboxScripts } from './sandboxScripts.gen';\n// Import submodules, not the ./skills barrel, to avoid a cycle (the mounters import from Sandbox).\nimport { SKILLS_DIR } from './skills/constants';\nimport type { ISkillMounter } from './skills/ISkillMounter';\n\nexport interface SandboxInfo {\n sandbox_id: string;\n}\n\n// Downloader/setup scripts can run longer than a normal exec.\nexport const SKILL_DOWNLOAD_TIMEOUT_SECONDS = 180;\n\n// Keeps the sandbox-side NATS wait longer than the host's own MCP deadline, so the host\n// surfaces the real MCP error instead of the bridge client giving up first.\nconst NATS_REQUEST_TIMEOUT_BUFFER_SECONDS = 30;\n\n// Write a script (base64-encoded, never interpolated raw) to a path and run it. Skill mounters reuse it.\nexport function buildWriteAndRunScriptCommand(params: { scriptPath: string; scriptContent: string }): string {\n const b64 = Buffer.from(params.scriptContent, 'utf-8').toString('base64');\n return [\n `mkdir -p \"$(dirname ${params.scriptPath})\"`,\n `rm -f ${params.scriptPath}`,\n `echo ${shellEscape(b64)} | base64 -d > ${params.scriptPath}`,\n `chmod a-w ${params.scriptPath}`,\n `python3 ${params.scriptPath}`,\n ].join(' && ');\n}\n\n/** Write or clear the git credential-store file at an absolute path (no global git config mutation). */\nfunction buildSyncGitCredentialsCommand(credentialsContent: string | null, credentialsPath: string): string {\n if (credentialsContent === null) {\n return `rm -f ${credentialsPath}`;\n }\n // Base64 avoids interpolating credentials directly into the shell command.\n const b64 = Buffer.from(credentialsContent, 'utf-8').toString('base64');\n return (\n `mkdir -p \"$(dirname ${credentialsPath})\" && ` +\n `echo '${b64}' | base64 -d > ${credentialsPath} && ` +\n `chmod 600 ${credentialsPath}`\n );\n}\n\n/**\n * Process-scoped credential.helper via GIT_CONFIG_* (avoids shared ~/.gitconfig).\n * Required for TFY sandbox where many logical sandboxes share one pod/HOME.\n */\nfunction buildGitCredentialHelperEnv(credentialsPath: string): Record<string, string> {\n return {\n GIT_CONFIG_COUNT: '1',\n GIT_CONFIG_KEY_0: 'credential.helper',\n GIT_CONFIG_VALUE_0: `store --file ${credentialsPath}`,\n };\n}\n\nexport interface SandboxStoredFile {\n filePath: string;\n sandboxCreated?: SandboxInfo | undefined;\n}\n\nexport interface SandboxOptions {\n provider: SandboxProvider;\n existingSandboxId?: string | undefined;\n skillMounter?: ISkillMounter | undefined;\n fileDownloadEnabled?: boolean | undefined;\n /** Pre-resolved credential-store file content (null = clear / no git auth). */\n resolvedGitCredentialsContent?: string | null | undefined;\n /**\n * Blocks destructive tools in code mode so they go through the approval flow\n * instead. Must be `true` — approvals are always enabled (the kill switch is gone).\n */\n blockDestructiveToolsInCodeMode: true;\n /** Used to derive the Code Mode NATS wait as request + connect. */\n mcpRequestTimeoutMs: number;\n mcpConnectTimeoutMs: number;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n tracing: AgentTracing;\n logger: Logger;\n}\n\nexport const SANDBOX_EXEC_TOOL_NAME = 'exec';\nconst SANDBOX_MCP_REMINDER_TAG = 'sandbox-mcp-code-mode';\nconst SANDBOX_FILE_OUTPUT_TAG = 'sandbox-file-output';\nexport const SANDBOX_SCHEMA_INFER_TAG = 'sandbox-schema-infer';\nconst SANDBOX_FILE_UPLOADS_TAG = 'sandbox-file-uploads';\n\n// Stable directory inside the sandbox where the MCP client library is uploaded.\n// MCP client upload dir. Two lookup channels:\n// - Python imports → PYTHONPATH points here (set by injectMCPClientEnv).\n// - Shell `mcp-client` → symlinked into MCP_CLIENT_BIN_SYMLINK (on default PATH);\n// basename drops `.py` so the agent doesn't reflexively prefix with `python`.\nconst MCP_CLIENT_DIR = '/opt/tfy/mcp-client';\nconst MCP_CLIENT_PATH = `${MCP_CLIENT_DIR}/mcp_client.py`;\nconst MCP_CLIENT_BIN_SYMLINK = '/usr/local/bin/mcp-client';\n\n// Per-server config sent to mcp_client.py via TFY_MCP_SERVERS.\ninterface SandboxMcpServerConfig {\n allowed_tools: string[];\n}\n\n// Snapshots the active W3C trace context (`traceparent` + optional `tracestate`) into env\n// vars consumed by mcp_client.py. Read with TFY_TRACEPARENT / TFY_TRACESTATE on the sandbox\n// side and attached as NATS message headers; the bridge extracts them on each message so the\n// resulting MCP spans nest under the originating Sandbox: exec span.\nfunction injectTraceContextEnv(): Record<string, string> {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const env: Record<string, string> = {};\n if (carrier['traceparent']) env['TFY_TRACEPARENT'] = carrier['traceparent'];\n if (carrier['tracestate']) env['TFY_TRACESTATE'] = carrier['tracestate'];\n return env;\n}\n\n/**\n * Injects environment variables required for mcp_client.py into sandbox_exec arguments.\n * Transport-specific vars (NATS URL/prefix/timeout) come from `codeModeEnv` via transport.start().\n */\nfunction injectMCPClientEnv(params: {\n env?: Record<string, string> | undefined;\n mcpServers: Record<string, SandboxMcpServerConfig>;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n codeModeEnv?: Record<string, string> | undefined;\n}): Record<string, string> {\n const codeModeEnv = params.codeModeEnv ?? {};\n const hasCodeModeTransport = Object.keys(codeModeEnv).length > 0;\n return {\n ...(params.execExtraEnv ?? {}),\n ...(params.env ?? {}),\n PYTHONPATH: MCP_CLIENT_DIR,\n ...(Object.keys(params.mcpServers).length > 0 && {\n TFY_MCP_SERVERS: Buffer.from(JSON.stringify(params.mcpServers)).toString('base64'),\n }),\n ...codeModeEnv,\n ...(hasCodeModeTransport && {\n // W3C trace context captured per-exec so each Code Mode request carries the originating\n // Sandbox: exec span as parent.\n ...injectTraceContextEnv(),\n }),\n // Approvals are always enabled: destructive tools cannot run in code mode.\n TFY_ENABLE_AGENT_APPROVALS: 'true',\n };\n}\n\nconst sandboxExecSchema = z.object({\n intent: z\n .string()\n .describe(\n 'A brief, user-facing explanation of the purpose of this command. Avoid technical jargon and code details.',\n ),\n command: z.string().describe('The shell command to execute in the sandbox.'),\n cwd: z.string().optional().describe('Working directory for command execution.'),\n env: z.record(z.string(), z.string()).optional().describe('Additional environment variables to set.'),\n});\n\nconst SANDBOX_EXEC_DESCRIPTION = `Execute shell commands in a persistent sandbox environment.\nThe sandbox persists across calls within the same session, allowing stateful operations.\nPre-installed: Python 3.13, pydantic, git, curl, helm, jq, ripgrep, genson.\nIMPORTANT: The Agent must never list, print, or expose environment variables as they contain sensitive credentials.\nIMPORTANT: Never read or expose any git credential files or ~/.git-credentials.\n`;\n\nexport function createSandboxLargeToolResponseGuidance(): string {\n return `For use cases where the Agent needs a subset of fields from the MCP response or needs deterministic processing, write code with the provided MCP client and use <${SANDBOX_MCP_REMINDER_TAG}>.`;\n}\n\nexport const SANDBOX_MCP_SERVER_ID = 'sandbox';\ntype SandboxExecInput = z.infer<typeof sandboxExecSchema>;\n\nexport class Sandbox extends LocalToolMCP {\n readonly name = SANDBOX_MCP_SERVER_ID;\n readonly displayName = 'Sandbox';\n override readonly description = 'Persistent sandbox environment for code execution';\n static readonly FILE_UPLOADS_DIR = SANDBOX_FILE_UPLOADS_DIR;\n\n private readonly provider: SandboxProvider;\n private readonly existingSandboxId?: string | undefined;\n private readonly tenantName: string;\n private existingSandboxInfo: SandboxInfo | undefined;\n // Cached promise to prevent concurrent sub-agents from creating duplicate sandboxes.\n private sandboxCreationPromise?: Promise<SandboxInfo> | undefined;\n private sandboxInitPromise?: Promise<void> | undefined;\n private codeExecToolSets: readonly IToolSet[] = [];\n private readonly fileDownloadEnabled: boolean;\n private readonly requestTimeoutSeconds: number;\n private readonly execExtraEnv?: Readonly<Record<string, string>> | undefined;\n private readonly skillMounter?: ISkillMounter | undefined;\n private cachedSkillsSection?: string | undefined;\n private readonly mcpClientScriptBase64: string;\n private readonly logger: Logger;\n // Pre-resolved credential-store file content (null = clear / no git auth).\n private readonly resolvedGitCredentialsContent: string | null;\n private codeModeDispatcher: CodeModeDispatcher | undefined;\n private codeModeTransport: CodeModeTransport | undefined;\n\n private tools = [\n defineTool({\n name: SANDBOX_EXEC_TOOL_NAME,\n description: SANDBOX_EXEC_DESCRIPTION,\n schema: sandboxExecSchema,\n handler: input => this.handleExec(input),\n }),\n ];\n\n constructor(options: SandboxOptions) {\n super({ tracing: options.tracing });\n this.provider = options.provider;\n this.existingSandboxId = options.existingSandboxId;\n this.tenantName = options.execExtraEnv?.['TFY_TENANT_NAME'] ?? '';\n this.skillMounter = options.skillMounter;\n this.fileDownloadEnabled = options.fileDownloadEnabled ?? false;\n const mcpBoundTimeoutMs = options.mcpRequestTimeoutMs + options.mcpConnectTimeoutMs;\n this.requestTimeoutSeconds = Math.ceil(mcpBoundTimeoutMs / 1000) + NATS_REQUEST_TIMEOUT_BUFFER_SECONDS;\n this.execExtraEnv = options.execExtraEnv;\n // Scripts are internal to Sandbox: the upload paths, env contract, and prompt\n // text are all hardcoded here, so injecting different content was never a\n // real extension point. Consumers don't see or provide them.\n this.mcpClientScriptBase64 = Buffer.from(sandboxScripts.mcpClient, 'utf-8').toString('base64');\n this.logger = options.logger.child({ module: 'Sandbox' });\n this.resolvedGitCredentialsContent = options.resolvedGitCredentialsContent ?? null;\n\n if (this.existingSandboxId) {\n validateSandboxOwnedByTenant(this.existingSandboxId, this.tenantName);\n this.existingSandboxInfo = { sandbox_id: this.existingSandboxId };\n }\n }\n\n private buildMcpServersEnvelope(): Record<string, SandboxMcpServerConfig> {\n const out: Record<string, SandboxMcpServerConfig> = {};\n for (const server of this.codeExecToolSets) {\n const allowedTools = server.getAllowedToolNamesForSandbox?.() ?? [];\n out[server.name] = { allowed_tools: allowedTools };\n }\n return out;\n }\n\n protected getTools() {\n return this.tools;\n }\n\n configureCodeMode(servers: readonly IToolSet[]): void {\n if (!servers.length) {\n return;\n }\n if (this.codeModeDispatcher !== undefined || this.codeModeTransport !== undefined) {\n throw new Error('Code Mode is already configured for this Sandbox');\n }\n this.codeExecToolSets = servers;\n this.codeModeDispatcher = new CodeModeDispatcher({ toolSets: servers, logger: this.logger });\n this.codeModeTransport = this.provider.createCodeModeTransport();\n }\n\n estimateSkillsTokens(): number {\n return Math.round(estimateTokensForString(this.renderSkillsSection()));\n }\n\n // Single source for the <skills> section, so the prompt and the token estimate can't drift.\n private renderSkillsSection(): string {\n if (this.cachedSkillsSection === undefined) {\n const skills = new InstructionBuilder('skills');\n this.skillMounter?.instruction(skills);\n this.cachedSkillsSection = skills.build();\n }\n return this.cachedSkillsSection;\n }\n\n buildInstruction(builder: InstructionBuilder): void {\n const providerInstructions = this.provider.getAdditionalInstructions();\n\n const sandboxInstructions = builder.beginSection('sandbox');\n sandboxInstructions.addContent('The Agent has access to a persistent sandbox environment for executing code.');\n sandboxInstructions.addContent('The Agent must NOT read or modify any git credential files.');\n\n this.buildSchemaSection(sandboxInstructions);\n this.buildSkillsSection(sandboxInstructions);\n this.buildMCPSection(sandboxInstructions);\n this.buildFileUploadsSection(sandboxInstructions);\n\n if (this.fileDownloadEnabled) {\n this.buildFileOutputSection(sandboxInstructions);\n }\n\n if (providerInstructions) {\n sandboxInstructions.addContent(providerInstructions);\n }\n }\n\n private buildFileUploadsSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_UPLOADS_TAG,\n `User-uploaded files are placed in ${Sandbox.FILE_UPLOADS_DIR}/. The Agent must not modify files in this directory. Always create a copy at a different location and work on the copy.`,\n );\n }\n\n private buildSchemaSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_SCHEMA_INFER_TAG,\n dedent`\n To infer the schema of a file, the Agent MUST execute:\n head -c 200 file_path && echo \"==\" && genson file_path\n This serves two purpose:\n 1. The first 200 characters of the file is printed. Do NOT print more than 200 characters.\n 2. genson infers and returns JSON schema if the file is JSON.\n Even if the file is not JSON, (1) helps to take the next step in less number of turns\n `,\n );\n }\n\n private buildFileOutputSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_OUTPUT_TAG,\n dedent`\n ## File outputs\n\n The user does not have direct access to the sandbox filesystem. When the Agent wants to output a file to the user, it MUST:\n 1. Ensure that the file is present in the sandbox.\n 2. Then emit a fenced sandbox_artifacts block referencing the file.\n\n \\`\\`\\`sandbox_artifacts\n [Human-readable label](/absolute/path/to/file.ext)\n \\`\\`\\`\n\n For large outputs or content that can be generated through code, prefer writing to a file and emitting the sandbox_artifacts block.\n\n Rules:\n - Use absolute paths, not ~/ or relative paths.\n - One link per line. Markdown link syntax: [label](path).`,\n );\n }\n\n private buildSkillsSection(builder: InstructionBuilder): void {\n builder.addContent(this.renderSkillsSection());\n }\n\n private buildMCPSection(builder: InstructionBuilder): void {\n const toolSetNames = this.codeExecToolSets.map(m => m.name).join(',');\n if (!toolSetNames) return;\n\n builder.addSection(\n SANDBOX_MCP_REMINDER_TAG,\n dedent`\n The following MCP servers are accessible for Code Mode from ${SANDBOX_EXEC_TOOL_NAME} via the pre-installed mcp_client module: ${toolSetNames}\n Tool and functions are same.\n Tool and MCP tools are same.\n\n Use \\`from mcp_client import call_tool\\` in code, or \\`mcp-client\\` in shell — both work from any directory with no setup.\n\n For deferred MCP servers, the Agent MUST:\n 1. Discover tools via ${LIST_TOOLS_NAME} from ${DEFERRED_TOOLS_SERVER_ID}. Never call ${GET_TOOL_OUTPUT_SCHEMA_NAME}.\n 2. Discover inputSchema, outputSchema via ${GET_TOOL_INFO_NAME} for the selected tools.\n\n For non-deferred MCP servers, the Agent MUST:\n 1. Discover outputSchema via ${GET_TOOL_OUTPUT_SCHEMA_NAME} for the selected tools.\n\n For both deferred and non-deferred if outputSchema is absent,\n the Agent MUST say: \"Given the MCP Server does not define an outputSchema let me figure it out first before entering Code Mode.\"\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > file && ${SANDBOX_SCHEMA_INFER_TAG}.\n the Agent MUST execute a single command for above.\n If the Agent does not get the outputSchema, the Agent will encounters scenarios like:\n Agent: \"Let me fix the code - it seems the x might be a string rather than an object in some cases:\"\n The Agent MUST not face the above scenario.\n \n Only after the Agent has the above details, it can enter Code Mode.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n CLI: mcp-client call-tool {server} {tool_a} '{\"param\":\"value\"}'\n # The output of the above command MUST be passed through tools like jq or head -c\n # or should be written to a file. Goal is to reduce content that the Agent prints.\n\n Code: from mcp_client import call_tool\n async def main():\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n # In most cases, it is safe to assume response is a dict.\n # To know the dict keys, the Agent still needs to figure out the outputSchema.\n # The Agent MUST write the code to a file first before executing it.\n\n Code Mode MUST be used in following scenarios:\n\n 1. Reduce structured tool output by executing logic on top of it. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n from mcp_client import call_tool\n from collections import Counter\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n print(Counter(r[\"a\"] for r in result[\"b\"] if r[\"c\"])) # Here we need outputSchema to know a, b and c exists.\n\n 2. Piping result of one tool call to another tool request. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool1, let me enter Code Mode.\n\n from mcp_client import call_tool\n result1 = await call_tool(\"{server}\", \"{tool1}\", body={\"param1\": \"value1\"})\n result2 = await call_tool(\"{server}\", \"{tool2}\", body={\"param2\": result1[\"value1\"]}) # Here we need outputSchema to know value1 exists.\n print(result2) # Note in this case, we do not need outputSchema of result2 as we are not using any fields.\n\n The Agent MUST NOT use Code Mode just to print the full raw response of a tool. The Agent MUST call the tool directly in this case, there are other mechanisms in place to ensure\n large tool call outputs are not loaded in context.\n \n The Agent must not use Code Mode in the following patterns:\n from mcp_client import call_tool\n import json\n result = await call_tool(\"{server}\", \"{tool}\", body={\"param1\": \"value1\"})\n print(result) # Never do this. Call tool without Code Mode.\n print(json.dumps(result, ...)) # Never do this. Call tool without Code Mode.\n print(result[\"a\"][:100]) # Never do this. Call tool without Code Mode.\n\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > data && cat data # Never do this. Call tool without Code Mode.\n `,\n );\n }\n\n private get requiredSandboxInfo(): SandboxInfo {\n if (!this.existingSandboxInfo) {\n throw new Error('Sandbox info not available');\n }\n return this.existingSandboxInfo;\n }\n\n private async ensureSandboxCreated(): Promise<{\n sandboxInfo: SandboxInfo;\n sandboxCreated: SandboxInfo | undefined;\n }> {\n if (this.existingSandboxInfo) {\n return { sandboxInfo: this.existingSandboxInfo, sandboxCreated: undefined };\n }\n // Provider returns camelCase `{ sandboxId }`; rename to the snake_case\n // `SandboxInfo` shape used everywhere downstream (Redis JSON, wire event,\n // in-memory state).\n this.sandboxCreationPromise ??= this.provider\n .createSandbox()\n .then(({ sandboxId }) => {\n validateSandboxOwnedByTenant(sandboxId, this.tenantName);\n return { sandbox_id: sandboxId };\n })\n .catch((e: unknown) => {\n this.sandboxCreationPromise = undefined;\n throw e;\n });\n\n const sandboxInfo = await this.sandboxCreationPromise;\n // Concurrent callers may both await the same creation promise; only the first\n // should report sandboxCreated. Read via a local to avoid control-flow narrowing\n // that treats existingSandboxInfo as always undefined after the early return above.\n const prior = this.existingSandboxInfo as SandboxInfo | undefined;\n const isNew = prior === undefined;\n this.existingSandboxInfo = sandboxInfo;\n return { sandboxInfo, sandboxCreated: isNew ? sandboxInfo : undefined };\n }\n\n private async handleExec(input: SandboxExecInput): Promise<CallToolResponse> {\n const { sandboxInfo, sandboxCreated } = await this.ensureSandboxCreated();\n const sandboxCreatedFlag = Boolean(sandboxCreated);\n try {\n await this.ensureSandboxInitialized();\n } catch (e) {\n this.logger.error('Sandbox initialization failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Sandbox initialization failed';\n return toolResultResponse({\n text: `Sandbox initialization failed: ${message}`,\n isError: true,\n overrides: { sandboxCreated: sandboxCreatedFlag, sandboxInfo },\n });\n }\n const codeModeEnv = await this.ensureCodeModeStarted(sandboxInfo.sandbox_id);\n const mcpClientEnv = injectMCPClientEnv({\n env: input.env,\n mcpServers: this.buildMcpServersEnvelope(),\n execExtraEnv: this.execExtraEnv,\n codeModeEnv,\n });\n const gitAuthEnv =\n this.resolvedGitCredentialsContent !== null\n ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(sandboxInfo.sandbox_id))\n : {};\n const env = { ...mcpClientEnv, ...gitAuthEnv };\n\n const result = await this.provider.exec({\n sandboxId: sandboxInfo.sandbox_id,\n command: input.command,\n cwd: input.cwd,\n env,\n });\n\n return {\n result: {\n content: [{ type: 'text', text: JSON.stringify(result) }],\n isError: !result.success,\n },\n wasInitialized: undefined,\n sandboxCreated: sandboxCreatedFlag,\n sandboxInfo,\n };\n }\n\n async getToolResultDumpDir(): Promise<{ dir: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n const dir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id).replace(/\\/+$/, '');\n return { dir, sandboxCreated };\n }\n\n async uploadFile(params: {\n targetDir: string;\n fileName: string;\n content: Buffer;\n }): Promise<{ filePath: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n await this.ensureSandboxInitialized();\n const { sandbox_id: sandboxId } = this.requiredSandboxInfo;\n const fileName = params.fileName.replace(/^\\/+/, '');\n const targetDir = params.targetDir.replace(/\\/+$/, '');\n const filePath = `${targetDir}/${fileName}`;\n await this.provider.uploadFile({ sandboxId, remotePath: filePath, content: params.content });\n return { filePath, sandboxCreated };\n }\n\n async uploadUserFile(input: { fileName: string; content: Buffer; mime: string }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const result = await this.uploadFile({\n targetDir: Sandbox.FILE_UPLOADS_DIR,\n fileName: input.fileName,\n content: input.content,\n });\n return {\n filePath: result.filePath,\n ...(result.sandboxCreated && { sandboxCreated: result.sandboxCreated }),\n };\n }\n\n async writeArtifact(input: {\n fileName: string;\n content: Buffer;\n sourceTool?: MappedMCPTool | undefined;\n }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const { dir, sandboxCreated: dumpDirCreated } = await this.getToolResultDumpDir();\n const result = await this.uploadFile({\n targetDir: dir,\n fileName: input.fileName,\n content: input.content,\n });\n const sandboxCreated = dumpDirCreated ?? result.sandboxCreated;\n return {\n filePath: result.filePath,\n ...(sandboxCreated && { sandboxCreated }),\n };\n }\n\n /**\n * Upload the MCP client Python script to the sandbox on the first tool call.\n * The sandbox filesystem is persistent within a session, so this only needs to run once.\n */\n private async ensureSandboxInitialized(): Promise<void> {\n this.sandboxInitPromise ??= this.initSandboxEnvironment().catch((e: unknown) => {\n this.sandboxInitPromise = undefined;\n throw e;\n });\n await this.sandboxInitPromise;\n }\n\n private async writeGitCredentials(): Promise<void> {\n const sandboxId = this.requiredSandboxInfo.sandbox_id;\n const credentialsPath = this.provider.getGitCredentialsPath(sandboxId);\n const result = await this.provider.exec({\n sandboxId,\n command: buildSyncGitCredentialsCommand(this.resolvedGitCredentialsContent, credentialsPath),\n });\n ensureExecSuccess(result);\n }\n\n private async initSandboxEnvironment(): Promise<void> {\n const toolResultDumpDir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id);\n\n this.logger.info('Uploading MCP client script and preparing skills directory in sandbox');\n\n const initSteps = [\n `mkdir -p ${MCP_CLIENT_DIR} ${Sandbox.FILE_UPLOADS_DIR} ${toolResultDumpDir} ${SKILLS_DIR}`,\n `rm -f ${MCP_CLIENT_PATH}`,\n `echo '${this.mcpClientScriptBase64}' | base64 -d > ${MCP_CLIENT_PATH}`,\n // Make executable + symlink onto PATH so `mcp-client` runs by name.\n `chmod 0555 ${MCP_CLIENT_PATH}`,\n `ln -sf ${MCP_CLIENT_PATH} ${MCP_CLIENT_BIN_SYMLINK}`,\n ];\n\n // Fold skill installation into this single init exec. The mounter returns a declarative\n // command + env + timeout; runs even when empty so its downloader can prune a reused sandbox.\n const skillInit = this.skillMounter?.getSandboxInit();\n if (skillInit) {\n initSteps.push(skillInit.command);\n }\n\n const script = initSteps.join(' && ');\n const result = await this.provider.exec({\n sandboxId: this.requiredSandboxInfo.sandbox_id,\n command: script,\n env: skillInit?.env,\n timeoutSeconds: skillInit?.timeoutSeconds,\n });\n ensureExecSuccess(result);\n this.logger.info(\n `Sandbox initialized: MCP client at ${MCP_CLIENT_PATH} (symlinked to ${MCP_CLIENT_BIN_SYMLINK}); skills dir ${SKILLS_DIR}`,\n );\n\n await this.writeGitCredentials();\n }\n\n /**\n * Lazily start Code Mode transport. Single-flight and idempotent inside the transport.\n * Skipped when code mode was never configured. Connect failures resolve to `undefined`\n * (no Code Mode env for that exec); mcp_client then errors at call time — no HTTPS fallback.\n */\n private async ensureCodeModeStarted(sandboxId: string): Promise<Record<string, string> | undefined> {\n const dispatcher = this.codeModeDispatcher;\n const transport = this.codeModeTransport;\n if (dispatcher === undefined || transport === undefined) {\n return undefined;\n }\n try {\n const { env } = await transport.start({\n codeModeDispatcher: dispatcher,\n sandboxId,\n requestTimeoutSeconds: this.requestTimeoutSeconds,\n });\n return env;\n } catch (e) {\n // Connect-level errors are not sticky: transport clears its session so a later exec may retry.\n this.logger.error('Code Mode transport start failed', {\n ...extractErrorLogFields(e),\n sandboxId,\n });\n return undefined;\n }\n }\n\n // Tears down per-Sandbox Code Mode resources. Dispatcher close first (in-flight requests get\n // source: transport), then transport.stop() — always safe whether start succeeded, failed, or never ran.\n async close(): Promise<void> {\n this.codeModeDispatcher?.close();\n this.codeModeDispatcher = undefined;\n const transport = this.codeModeTransport;\n this.codeModeTransport = undefined;\n if (transport === undefined) return;\n try {\n await transport.stop();\n } catch (e) {\n this.logger.warn('Error stopping Code Mode transport', extractErrorLogFields(e));\n }\n }\n}\n"],"mappings":"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","names":["import_constants","dedent"]}
@@ -13,10 +13,10 @@ import {
13
13
  LIST_TOOLS_NAME
14
14
  } from "../runtime/DeferredTool.mjs";
15
15
  import { extractErrorLogFields } from "../util/errorLogFields.mjs";
16
- import { DEFAULT_SANDBOX_NATS_WS_PORT, SANDBOX_FILE_UPLOADS_DIR } from "./constants.mjs";
16
+ import { CodeModeDispatcher } from "./codeMode/CodeModeDispatcher.mjs";
17
+ import { SANDBOX_FILE_UPLOADS_DIR } from "./constants.mjs";
17
18
  import { ensureExecSuccess, shellEscape } from "./provider/Provider.mjs";
18
19
  import { validateNoPathTraversal, validateSandboxOwnedByTenant } from "./SandboxErrors.mjs";
19
- import { SandboxNatsBridge } from "./SandboxNatsBridge.mjs";
20
20
  import { sandboxScripts } from "./sandboxScripts.gen.mjs";
21
21
  import { SKILLS_DIR } from "./skills/constants.mjs";
22
22
  var SKILL_DOWNLOAD_TIMEOUT_SECONDS = 180;
@@ -62,6 +62,8 @@ function injectTraceContextEnv() {
62
62
  return env;
63
63
  }
64
64
  function injectMCPClientEnv(params) {
65
+ const codeModeEnv = params.codeModeEnv ?? {};
66
+ const hasCodeModeTransport = Object.keys(codeModeEnv).length > 0;
65
67
  return {
66
68
  ...params.execExtraEnv ?? {},
67
69
  ...params.env ?? {},
@@ -69,13 +71,10 @@ function injectMCPClientEnv(params) {
69
71
  ...Object.keys(params.mcpServers).length > 0 && {
70
72
  TFY_MCP_SERVERS: Buffer.from(JSON.stringify(params.mcpServers)).toString("base64")
71
73
  },
72
- ...params.natsBridgeSubjectPrefix && {
73
- TFY_NATS_URL: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,
74
- TFY_NATS_SUBJECT_PREFIX: params.natsBridgeSubjectPrefix,
75
- TFY_NATS_REQUEST_TIMEOUT_SECONDS: String(params.natsRequestTimeoutSeconds),
76
- // W3C trace context captured per-exec so each NATS request carries the originating
77
- // Sandbox: exec span as parent. Without this, MCP spans dispatched via the bridge
78
- // inherit whatever ALS context the NATS callback runs under (stale, shared across execs).
74
+ ...codeModeEnv,
75
+ ...hasCodeModeTransport && {
76
+ // W3C trace context captured per-exec so each Code Mode request carries the originating
77
+ // Sandbox: exec span as parent.
79
78
  ...injectTraceContextEnv()
80
79
  },
81
80
  // Approvals are always enabled: destructive tools cannot run in code mode.
@@ -114,7 +113,7 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
114
113
  sandboxInitPromise;
115
114
  codeExecToolSets = [];
116
115
  fileDownloadEnabled;
117
- natsRequestTimeoutSeconds;
116
+ requestTimeoutSeconds;
118
117
  execExtraEnv;
119
118
  skillMounter;
120
119
  cachedSkillsSection;
@@ -122,7 +121,8 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
122
121
  logger;
123
122
  // Pre-resolved credential-store file content (null = clear / no git auth).
124
123
  resolvedGitCredentialsContent;
125
- natsBridgePromise;
124
+ codeModeDispatcher;
125
+ codeModeTransport;
126
126
  tools = [
127
127
  defineTool({
128
128
  name: SANDBOX_EXEC_TOOL_NAME,
@@ -139,7 +139,7 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
139
139
  this.skillMounter = options.skillMounter;
140
140
  this.fileDownloadEnabled = options.fileDownloadEnabled ?? false;
141
141
  const mcpBoundTimeoutMs = options.mcpRequestTimeoutMs + options.mcpConnectTimeoutMs;
142
- this.natsRequestTimeoutSeconds = Math.ceil(mcpBoundTimeoutMs / 1e3) + NATS_REQUEST_TIMEOUT_BUFFER_SECONDS;
142
+ this.requestTimeoutSeconds = Math.ceil(mcpBoundTimeoutMs / 1e3) + NATS_REQUEST_TIMEOUT_BUFFER_SECONDS;
143
143
  this.execExtraEnv = options.execExtraEnv;
144
144
  this.mcpClientScriptBase64 = Buffer.from(sandboxScripts.mcpClient, "utf-8").toString("base64");
145
145
  this.logger = options.logger.child({ module: "Sandbox" });
@@ -164,7 +164,12 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
164
164
  if (!servers.length) {
165
165
  return;
166
166
  }
167
+ if (this.codeModeDispatcher !== void 0 || this.codeModeTransport !== void 0) {
168
+ throw new Error("Code Mode is already configured for this Sandbox");
169
+ }
167
170
  this.codeExecToolSets = servers;
171
+ this.codeModeDispatcher = new CodeModeDispatcher({ toolSets: servers, logger: this.logger });
172
+ this.codeModeTransport = this.provider.createCodeModeTransport();
168
173
  }
169
174
  estimateSkillsTokens() {
170
175
  return Math.round(estimateTokensForString(this.renderSkillsSection()));
@@ -348,13 +353,12 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
348
353
  overrides: { sandboxCreated: sandboxCreatedFlag, sandboxInfo }
349
354
  });
350
355
  }
351
- const bridge = await this.ensureNatsBridgeConnected(sandboxInfo.sandbox_id);
356
+ const codeModeEnv = await this.ensureCodeModeStarted(sandboxInfo.sandbox_id);
352
357
  const mcpClientEnv = injectMCPClientEnv({
353
358
  env: input.env,
354
359
  mcpServers: this.buildMcpServersEnvelope(),
355
360
  execExtraEnv: this.execExtraEnv,
356
- natsBridgeSubjectPrefix: bridge?.subjectPrefix,
357
- natsRequestTimeoutSeconds: this.natsRequestTimeoutSeconds
361
+ codeModeEnv
358
362
  });
359
363
  const gitAuthEnv = this.resolvedGitCredentialsContent !== null ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(sandboxInfo.sandbox_id)) : {};
360
364
  const env = { ...mcpClientEnv, ...gitAuthEnv };
@@ -464,63 +468,43 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
464
468
  await this.writeGitCredentials();
465
469
  }
466
470
  /**
467
- * Lazily connect the sandbox→gateway NATS bridge. Single-flight and idempotent.
468
- * Skipped when there are no MCP servers exposed to code mode (nothing to bridge). Connect
469
- * failures resolve to `undefined` (no NATS env for that exec); mcp_client then errors at call
470
- * time — there is no HTTPS fallback.
471
+ * Lazily start Code Mode transport. Single-flight and idempotent inside the transport.
472
+ * Skipped when code mode was never configured. Connect failures resolve to `undefined`
473
+ * (no Code Mode env for that exec); mcp_client then errors at call time — no HTTPS fallback.
471
474
  */
472
- async ensureNatsBridgeConnected(sandboxId) {
473
- if (this.codeExecToolSets.length === 0) return void 0;
474
- this.natsBridgePromise ??= this.connectNatsBridge(sandboxId).catch((e) => {
475
- this.logger.error("Sandbox NATS bridge connect failed", {
475
+ async ensureCodeModeStarted(sandboxId) {
476
+ const dispatcher = this.codeModeDispatcher;
477
+ const transport = this.codeModeTransport;
478
+ if (dispatcher === void 0 || transport === void 0) {
479
+ return void 0;
480
+ }
481
+ try {
482
+ const { env } = await transport.start({
483
+ codeModeDispatcher: dispatcher,
484
+ sandboxId,
485
+ requestTimeoutSeconds: this.requestTimeoutSeconds
486
+ });
487
+ return env;
488
+ } catch (e) {
489
+ this.logger.error("Code Mode transport start failed", {
476
490
  ...extractErrorLogFields(e),
477
491
  sandboxId
478
492
  });
479
- this.natsBridgePromise = void 0;
480
493
  return void 0;
481
- });
482
- return this.natsBridgePromise;
483
- }
484
- async connectNatsBridge(sandboxId) {
485
- const wsUrl = await this.provider.getNatsBridgeUrl(sandboxId);
486
- const toolSets = /* @__PURE__ */ new Map();
487
- for (const server of this.codeExecToolSets) {
488
- toolSets.set(server.name, server);
489
494
  }
490
- const bridge = await SandboxNatsBridge.connect({ url: wsUrl, toolSets, logger: this.logger });
491
- this.logger.info("Sandbox NATS bridge connected", {
492
- sandboxId,
493
- toolSetCount: toolSets.size
494
- });
495
- this.attachBridgeInvalidationOnClose(bridge, sandboxId);
496
- return bridge;
497
495
  }
498
- attachBridgeInvalidationOnClose(bridge, sandboxId) {
499
- const cachedPromise = this.natsBridgePromise;
500
- void bridge.whenClosed().then(() => {
501
- if (this.natsBridgePromise === cachedPromise) {
502
- this.natsBridgePromise = void 0;
503
- this.logger.info("Sandbox NATS bridge closed; cache invalidated, next exec will reconnect", { sandboxId });
504
- }
505
- });
506
- }
507
- // Tears down per-Sandbox resources. Awaits the in-flight connect (if any) and explicitly
508
- // closes the bridge — dropping the field alone would leak the WS socket and live
509
- // subscriptions, and orphan a bridge that finishes connecting after close() returned.
496
+ // Tears down per-Sandbox Code Mode resources. Dispatcher close first (in-flight requests get
497
+ // source: transport), then transport.stop() — always safe whether start succeeded, failed, or never ran.
510
498
  async close() {
511
- const pendingPromise = this.natsBridgePromise;
512
- this.natsBridgePromise = void 0;
513
- if (!pendingPromise) return;
514
- let bridge;
515
- try {
516
- bridge = await pendingPromise;
517
- } catch {
518
- }
519
- if (!bridge) return;
499
+ this.codeModeDispatcher?.close();
500
+ this.codeModeDispatcher = void 0;
501
+ const transport = this.codeModeTransport;
502
+ this.codeModeTransport = void 0;
503
+ if (transport === void 0) return;
520
504
  try {
521
- await bridge.close();
505
+ await transport.stop();
522
506
  } catch (e) {
523
- this.logger.warn("Error closing sandbox NATS bridge", extractErrorLogFields(e));
507
+ this.logger.warn("Error stopping Code Mode transport", extractErrorLogFields(e));
524
508
  }
525
509
  }
526
510
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/sandbox/Sandbox.ts"],"sourcesContent":["import { context, propagation } from '@opentelemetry/api';\nimport dedent from 'dedent';\nimport type { Logger } from 'winston';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../InstructionBuilder';\nimport { estimateTokensForString } from '../llm/usage';\nimport type { MappedMCPTool } from '../mcp/convertMCPServers';\nimport { toolResultResponse, type CallToolResponse, type IToolSet } from '../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP } from '../mcp/LocalToolMCP';\nimport {\n DEFERRED_TOOLS_SERVER_ID,\n GET_TOOL_INFO_NAME,\n GET_TOOL_OUTPUT_SCHEMA_NAME,\n LIST_TOOLS_NAME,\n} from '../runtime/DeferredTool';\nimport type { AgentTracing } from '../tracing/AgentTracing';\nimport { extractErrorLogFields } from '../util/errorLogFields';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT, SANDBOX_FILE_UPLOADS_DIR } from './constants';\nimport { ensureExecSuccess, shellEscape, type SandboxProvider } from './provider/Provider';\nimport { validateNoPathTraversal, validateSandboxOwnedByTenant } from './SandboxErrors';\nimport { SandboxNatsBridge } from './SandboxNatsBridge';\nimport { sandboxScripts } from './sandboxScripts.gen';\n// Import submodules, not the ./skills barrel, to avoid a cycle (the mounters import from Sandbox).\nimport { SKILLS_DIR } from './skills/constants';\nimport type { ISkillMounter } from './skills/ISkillMounter';\n\nexport interface SandboxInfo {\n sandbox_id: string;\n}\n\n// Downloader/setup scripts can run longer than a normal exec.\nexport const SKILL_DOWNLOAD_TIMEOUT_SECONDS = 180;\n\n// Keeps the sandbox-side NATS wait longer than the host's own MCP deadline, so the host\n// surfaces the real MCP error instead of the bridge client giving up first.\nconst NATS_REQUEST_TIMEOUT_BUFFER_SECONDS = 30;\n\n// Write a script (base64-encoded, never interpolated raw) to a path and run it. Skill mounters reuse it.\nexport function buildWriteAndRunScriptCommand(params: { scriptPath: string; scriptContent: string }): string {\n const b64 = Buffer.from(params.scriptContent, 'utf-8').toString('base64');\n return [\n `mkdir -p \"$(dirname ${params.scriptPath})\"`,\n `rm -f ${params.scriptPath}`,\n `echo ${shellEscape(b64)} | base64 -d > ${params.scriptPath}`,\n `chmod a-w ${params.scriptPath}`,\n `python3 ${params.scriptPath}`,\n ].join(' && ');\n}\n\n/** Write or clear the git credential-store file at an absolute path (no global git config mutation). */\nfunction buildSyncGitCredentialsCommand(credentialsContent: string | null, credentialsPath: string): string {\n if (credentialsContent === null) {\n return `rm -f ${credentialsPath}`;\n }\n // Base64 avoids interpolating credentials directly into the shell command.\n const b64 = Buffer.from(credentialsContent, 'utf-8').toString('base64');\n return (\n `mkdir -p \"$(dirname ${credentialsPath})\" && ` +\n `echo '${b64}' | base64 -d > ${credentialsPath} && ` +\n `chmod 600 ${credentialsPath}`\n );\n}\n\n/**\n * Process-scoped credential.helper via GIT_CONFIG_* (avoids shared ~/.gitconfig).\n * Required for TFY sandbox where many logical sandboxes share one pod/HOME.\n */\nfunction buildGitCredentialHelperEnv(credentialsPath: string): Record<string, string> {\n return {\n GIT_CONFIG_COUNT: '1',\n GIT_CONFIG_KEY_0: 'credential.helper',\n GIT_CONFIG_VALUE_0: `store --file ${credentialsPath}`,\n };\n}\n\nexport interface SandboxStoredFile {\n filePath: string;\n sandboxCreated?: SandboxInfo | undefined;\n}\n\nexport interface SandboxOptions {\n provider: SandboxProvider;\n existingSandboxId?: string | undefined;\n skillMounter?: ISkillMounter | undefined;\n fileDownloadEnabled?: boolean | undefined;\n /** Pre-resolved credential-store file content (null = clear / no git auth). */\n resolvedGitCredentialsContent?: string | null | undefined;\n /**\n * Blocks destructive tools in code mode so they go through the approval flow\n * instead. Must be `true` — approvals are always enabled (the kill switch is gone).\n */\n blockDestructiveToolsInCodeMode: true;\n /** Used to derive the Code Mode NATS wait as request + connect. */\n mcpRequestTimeoutMs: number;\n mcpConnectTimeoutMs: number;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n tracing: AgentTracing;\n logger: Logger;\n}\n\nexport const SANDBOX_EXEC_TOOL_NAME = 'exec';\nconst SANDBOX_MCP_REMINDER_TAG = 'sandbox-mcp-code-mode';\nconst SANDBOX_FILE_OUTPUT_TAG = 'sandbox-file-output';\nexport const SANDBOX_SCHEMA_INFER_TAG = 'sandbox-schema-infer';\nconst SANDBOX_FILE_UPLOADS_TAG = 'sandbox-file-uploads';\n\n// Stable directory inside the sandbox where the MCP client library is uploaded.\n// MCP client upload dir. Two lookup channels:\n// - Python imports → PYTHONPATH points here (set by injectMCPClientEnv).\n// - Shell `mcp-client` → symlinked into MCP_CLIENT_BIN_SYMLINK (on default PATH);\n// basename drops `.py` so the agent doesn't reflexively prefix with `python`.\nconst MCP_CLIENT_DIR = '/opt/tfy/mcp-client';\nconst MCP_CLIENT_PATH = `${MCP_CLIENT_DIR}/mcp_client.py`;\nconst MCP_CLIENT_BIN_SYMLINK = '/usr/local/bin/mcp-client';\n\n// Per-server config sent to mcp_client.py via TFY_MCP_SERVERS.\ninterface SandboxMcpServerConfig {\n allowed_tools: string[];\n}\n\n// Snapshots the active W3C trace context (`traceparent` + optional `tracestate`) into env\n// vars consumed by mcp_client.py. Read with TFY_TRACEPARENT / TFY_TRACESTATE on the sandbox\n// side and attached as NATS message headers; the bridge extracts them on each message so the\n// resulting MCP spans nest under the originating Sandbox: exec span.\nfunction injectTraceContextEnv(): Record<string, string> {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const env: Record<string, string> = {};\n if (carrier['traceparent']) env['TFY_TRACEPARENT'] = carrier['traceparent'];\n if (carrier['tracestate']) env['TFY_TRACESTATE'] = carrier['tracestate'];\n return env;\n}\n\n/**\n * Injects environment variables required for mcp_client.py into sandbox_exec arguments.\n *\n * `natsBridgeSubjectPrefix` MUST be the prefix the gateway-side bridge subscribed to; the\n * sandbox script publishes to `<prefix>.mcp` (operation in the payload's `op` field) and only\n * this bridge receives them. See SandboxNatsBridge.\n */\nfunction injectMCPClientEnv(params: {\n env?: Record<string, string> | undefined;\n mcpServers: Record<string, SandboxMcpServerConfig>;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n natsBridgeSubjectPrefix?: string | undefined;\n natsRequestTimeoutSeconds: number;\n}): Record<string, string> {\n return {\n ...(params.execExtraEnv ?? {}),\n ...(params.env ?? {}),\n PYTHONPATH: MCP_CLIENT_DIR,\n ...(Object.keys(params.mcpServers).length > 0 && {\n TFY_MCP_SERVERS: Buffer.from(JSON.stringify(params.mcpServers)).toString('base64'),\n }),\n ...(params.natsBridgeSubjectPrefix && {\n TFY_NATS_URL: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n TFY_NATS_SUBJECT_PREFIX: params.natsBridgeSubjectPrefix,\n TFY_NATS_REQUEST_TIMEOUT_SECONDS: String(params.natsRequestTimeoutSeconds),\n // W3C trace context captured per-exec so each NATS request carries the originating\n // Sandbox: exec span as parent. Without this, MCP spans dispatched via the bridge\n // inherit whatever ALS context the NATS callback runs under (stale, shared across execs).\n ...injectTraceContextEnv(),\n }),\n // Approvals are always enabled: destructive tools cannot run in code mode.\n TFY_ENABLE_AGENT_APPROVALS: 'true',\n };\n}\n\nconst sandboxExecSchema = z.object({\n intent: z\n .string()\n .describe(\n 'A brief, user-facing explanation of the purpose of this command. Avoid technical jargon and code details.',\n ),\n command: z.string().describe('The shell command to execute in the sandbox.'),\n cwd: z.string().optional().describe('Working directory for command execution.'),\n env: z.record(z.string(), z.string()).optional().describe('Additional environment variables to set.'),\n});\n\nconst SANDBOX_EXEC_DESCRIPTION = `Execute shell commands in a persistent sandbox environment.\nThe sandbox persists across calls within the same session, allowing stateful operations.\nPre-installed: Python 3.13, pydantic, git, curl, helm, jq, ripgrep, genson.\nIMPORTANT: The Agent must never list, print, or expose environment variables as they contain sensitive credentials.\nIMPORTANT: Never read or expose any git credential files or ~/.git-credentials.\n`;\n\nexport function createSandboxLargeToolResponseGuidance(): string {\n return `For use cases where the Agent needs a subset of fields from the MCP response or needs deterministic processing, write code with the provided MCP client and use <${SANDBOX_MCP_REMINDER_TAG}>.`;\n}\n\nexport const SANDBOX_MCP_SERVER_ID = 'sandbox';\ntype SandboxExecInput = z.infer<typeof sandboxExecSchema>;\n\nexport class Sandbox extends LocalToolMCP {\n readonly name = SANDBOX_MCP_SERVER_ID;\n readonly displayName = 'Sandbox';\n override readonly description = 'Persistent sandbox environment for code execution';\n static readonly FILE_UPLOADS_DIR = SANDBOX_FILE_UPLOADS_DIR;\n\n private readonly provider: SandboxProvider;\n private readonly existingSandboxId?: string | undefined;\n private readonly tenantName: string;\n private existingSandboxInfo: SandboxInfo | undefined;\n // Cached promise to prevent concurrent sub-agents from creating duplicate sandboxes.\n private sandboxCreationPromise?: Promise<SandboxInfo> | undefined;\n private sandboxInitPromise?: Promise<void> | undefined;\n private codeExecToolSets: readonly IToolSet[] = [];\n private readonly fileDownloadEnabled: boolean;\n private readonly natsRequestTimeoutSeconds: number;\n private readonly execExtraEnv?: Readonly<Record<string, string>> | undefined;\n private readonly skillMounter?: ISkillMounter | undefined;\n private cachedSkillsSection?: string | undefined;\n private readonly mcpClientScriptBase64: string;\n private readonly logger: Logger;\n // Pre-resolved credential-store file content (null = clear / no git auth).\n private readonly resolvedGitCredentialsContent: string | null;\n private natsBridgePromise?: Promise<SandboxNatsBridge | undefined> | undefined;\n\n private tools = [\n defineTool({\n name: SANDBOX_EXEC_TOOL_NAME,\n description: SANDBOX_EXEC_DESCRIPTION,\n schema: sandboxExecSchema,\n handler: input => this.handleExec(input),\n }),\n ];\n\n constructor(options: SandboxOptions) {\n super({ tracing: options.tracing });\n this.provider = options.provider;\n this.existingSandboxId = options.existingSandboxId;\n this.tenantName = options.execExtraEnv?.['TFY_TENANT_NAME'] ?? '';\n this.skillMounter = options.skillMounter;\n this.fileDownloadEnabled = options.fileDownloadEnabled ?? false;\n const mcpBoundTimeoutMs = options.mcpRequestTimeoutMs + options.mcpConnectTimeoutMs;\n this.natsRequestTimeoutSeconds = Math.ceil(mcpBoundTimeoutMs / 1000) + NATS_REQUEST_TIMEOUT_BUFFER_SECONDS;\n this.execExtraEnv = options.execExtraEnv;\n // Scripts are internal to Sandbox: the upload paths, env contract, and prompt\n // text are all hardcoded here, so injecting different content was never a\n // real extension point. Consumers don't see or provide them.\n this.mcpClientScriptBase64 = Buffer.from(sandboxScripts.mcpClient, 'utf-8').toString('base64');\n this.logger = options.logger.child({ module: 'Sandbox' });\n this.resolvedGitCredentialsContent = options.resolvedGitCredentialsContent ?? null;\n\n if (this.existingSandboxId) {\n validateSandboxOwnedByTenant(this.existingSandboxId, this.tenantName);\n this.existingSandboxInfo = { sandbox_id: this.existingSandboxId };\n }\n }\n\n private buildMcpServersEnvelope(): Record<string, SandboxMcpServerConfig> {\n const out: Record<string, SandboxMcpServerConfig> = {};\n for (const server of this.codeExecToolSets) {\n const allowedTools = server.getAllowedToolNamesForSandbox?.() ?? [];\n out[server.name] = { allowed_tools: allowedTools };\n }\n return out;\n }\n\n protected getTools() {\n return this.tools;\n }\n\n configureCodeMode(servers: readonly IToolSet[]): void {\n if (!servers.length) {\n return;\n }\n this.codeExecToolSets = servers;\n }\n\n estimateSkillsTokens(): number {\n return Math.round(estimateTokensForString(this.renderSkillsSection()));\n }\n\n // Single source for the <skills> section, so the prompt and the token estimate can't drift.\n private renderSkillsSection(): string {\n if (this.cachedSkillsSection === undefined) {\n const skills = new InstructionBuilder('skills');\n this.skillMounter?.instruction(skills);\n this.cachedSkillsSection = skills.build();\n }\n return this.cachedSkillsSection;\n }\n\n buildInstruction(builder: InstructionBuilder): void {\n const providerInstructions = this.provider.getAdditionalInstructions();\n\n const sandboxInstructions = builder.beginSection('sandbox');\n sandboxInstructions.addContent('The Agent has access to a persistent sandbox environment for executing code.');\n sandboxInstructions.addContent('The Agent must NOT read or modify any git credential files.');\n\n this.buildSchemaSection(sandboxInstructions);\n this.buildSkillsSection(sandboxInstructions);\n this.buildMCPSection(sandboxInstructions);\n this.buildFileUploadsSection(sandboxInstructions);\n\n if (this.fileDownloadEnabled) {\n this.buildFileOutputSection(sandboxInstructions);\n }\n\n if (providerInstructions) {\n sandboxInstructions.addContent(providerInstructions);\n }\n }\n\n private buildFileUploadsSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_UPLOADS_TAG,\n `User-uploaded files are placed in ${Sandbox.FILE_UPLOADS_DIR}/. The Agent must not modify files in this directory. Always create a copy at a different location and work on the copy.`,\n );\n }\n\n private buildSchemaSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_SCHEMA_INFER_TAG,\n dedent`\n To infer the schema of a file, the Agent MUST execute:\n head -c 200 file_path && echo \"==\" && genson file_path\n This serves two purpose:\n 1. The first 200 characters of the file is printed. Do NOT print more than 200 characters.\n 2. genson infers and returns JSON schema if the file is JSON.\n Even if the file is not JSON, (1) helps to take the next step in less number of turns\n `,\n );\n }\n\n private buildFileOutputSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_OUTPUT_TAG,\n dedent`\n ## File outputs\n\n The user does not have direct access to the sandbox filesystem. When the Agent wants to output a file to the user, it MUST:\n 1. Ensure that the file is present in the sandbox.\n 2. Then emit a fenced sandbox_artifacts block referencing the file.\n\n \\`\\`\\`sandbox_artifacts\n [Human-readable label](/absolute/path/to/file.ext)\n \\`\\`\\`\n\n For large outputs or content that can be generated through code, prefer writing to a file and emitting the sandbox_artifacts block.\n\n Rules:\n - Use absolute paths, not ~/ or relative paths.\n - One link per line. Markdown link syntax: [label](path).`,\n );\n }\n\n private buildSkillsSection(builder: InstructionBuilder): void {\n builder.addContent(this.renderSkillsSection());\n }\n\n private buildMCPSection(builder: InstructionBuilder): void {\n const toolSetNames = this.codeExecToolSets.map(m => m.name).join(',');\n if (!toolSetNames) return;\n\n builder.addSection(\n SANDBOX_MCP_REMINDER_TAG,\n dedent`\n The following MCP servers are accessible for Code Mode from ${SANDBOX_EXEC_TOOL_NAME} via the pre-installed mcp_client module: ${toolSetNames}\n Tool and functions are same.\n Tool and MCP tools are same.\n\n Use \\`from mcp_client import call_tool\\` in code, or \\`mcp-client\\` in shell — both work from any directory with no setup.\n\n For deferred MCP servers, the Agent MUST:\n 1. Discover tools via ${LIST_TOOLS_NAME} from ${DEFERRED_TOOLS_SERVER_ID}. Never call ${GET_TOOL_OUTPUT_SCHEMA_NAME}.\n 2. Discover inputSchema, outputSchema via ${GET_TOOL_INFO_NAME} for the selected tools.\n\n For non-deferred MCP servers, the Agent MUST:\n 1. Discover outputSchema via ${GET_TOOL_OUTPUT_SCHEMA_NAME} for the selected tools.\n\n For both deferred and non-deferred if outputSchema is absent,\n the Agent MUST say: \"Given the MCP Server does not define an outputSchema let me figure it out first before entering Code Mode.\"\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > file && ${SANDBOX_SCHEMA_INFER_TAG}.\n the Agent MUST execute a single command for above.\n If the Agent does not get the outputSchema, the Agent will encounters scenarios like:\n Agent: \"Let me fix the code - it seems the x might be a string rather than an object in some cases:\"\n The Agent MUST not face the above scenario.\n \n Only after the Agent has the above details, it can enter Code Mode.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n CLI: mcp-client call-tool {server} {tool_a} '{\"param\":\"value\"}'\n # The output of the above command MUST be passed through tools like jq or head -c\n # or should be written to a file. Goal is to reduce content that the Agent prints.\n\n Code: from mcp_client import call_tool\n async def main():\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n # In most cases, it is safe to assume response is a dict.\n # To know the dict keys, the Agent still needs to figure out the outputSchema.\n # The Agent MUST write the code to a file first before executing it.\n\n Code Mode MUST be used in following scenarios:\n\n 1. Reduce structured tool output by executing logic on top of it. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n from mcp_client import call_tool\n from collections import Counter\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n print(Counter(r[\"a\"] for r in result[\"b\"] if r[\"c\"])) # Here we need outputSchema to know a, b and c exists.\n\n 2. Piping result of one tool call to another tool request. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool1, let me enter Code Mode.\n\n from mcp_client import call_tool\n result1 = await call_tool(\"{server}\", \"{tool1}\", body={\"param1\": \"value1\"})\n result2 = await call_tool(\"{server}\", \"{tool2}\", body={\"param2\": result1[\"value1\"]}) # Here we need outputSchema to know value1 exists.\n print(result2) # Note in this case, we do not need outputSchema of result2 as we are not using any fields.\n\n The Agent MUST NOT use Code Mode just to print the full raw response of a tool. The Agent MUST call the tool directly in this case, there are other mechanisms in place to ensure\n large tool call outputs are not loaded in context.\n \n The Agent must not use Code Mode in the following patterns:\n from mcp_client import call_tool\n import json\n result = await call_tool(\"{server}\", \"{tool}\", body={\"param1\": \"value1\"})\n print(result) # Never do this. Call tool without Code Mode.\n print(json.dumps(result, ...)) # Never do this. Call tool without Code Mode.\n print(result[\"a\"][:100]) # Never do this. Call tool without Code Mode.\n\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > data && cat data # Never do this. Call tool without Code Mode.\n `,\n );\n }\n\n private get requiredSandboxInfo(): SandboxInfo {\n if (!this.existingSandboxInfo) {\n throw new Error('Sandbox info not available');\n }\n return this.existingSandboxInfo;\n }\n\n private async ensureSandboxCreated(): Promise<{\n sandboxInfo: SandboxInfo;\n sandboxCreated: SandboxInfo | undefined;\n }> {\n if (this.existingSandboxInfo) {\n return { sandboxInfo: this.existingSandboxInfo, sandboxCreated: undefined };\n }\n // Provider returns camelCase `{ sandboxId }`; rename to the snake_case\n // `SandboxInfo` shape used everywhere downstream (Redis JSON, wire event,\n // in-memory state).\n this.sandboxCreationPromise ??= this.provider\n .createSandbox()\n .then(({ sandboxId }) => {\n validateSandboxOwnedByTenant(sandboxId, this.tenantName);\n return { sandbox_id: sandboxId };\n })\n .catch((e: unknown) => {\n this.sandboxCreationPromise = undefined;\n throw e;\n });\n\n const sandboxInfo = await this.sandboxCreationPromise;\n // Concurrent callers may both await the same creation promise; only the first\n // should report sandboxCreated. Read via a local to avoid control-flow narrowing\n // that treats existingSandboxInfo as always undefined after the early return above.\n const prior = this.existingSandboxInfo as SandboxInfo | undefined;\n const isNew = prior === undefined;\n this.existingSandboxInfo = sandboxInfo;\n return { sandboxInfo, sandboxCreated: isNew ? sandboxInfo : undefined };\n }\n\n private async handleExec(input: SandboxExecInput): Promise<CallToolResponse> {\n const { sandboxInfo, sandboxCreated } = await this.ensureSandboxCreated();\n const sandboxCreatedFlag = Boolean(sandboxCreated);\n try {\n await this.ensureSandboxInitialized();\n } catch (e) {\n this.logger.error('Sandbox initialization failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Sandbox initialization failed';\n return toolResultResponse({\n text: `Sandbox initialization failed: ${message}`,\n isError: true,\n overrides: { sandboxCreated: sandboxCreatedFlag, sandboxInfo },\n });\n }\n const bridge = await this.ensureNatsBridgeConnected(sandboxInfo.sandbox_id);\n const mcpClientEnv = injectMCPClientEnv({\n env: input.env,\n mcpServers: this.buildMcpServersEnvelope(),\n execExtraEnv: this.execExtraEnv,\n natsBridgeSubjectPrefix: bridge?.subjectPrefix,\n natsRequestTimeoutSeconds: this.natsRequestTimeoutSeconds,\n });\n const gitAuthEnv =\n this.resolvedGitCredentialsContent !== null\n ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(sandboxInfo.sandbox_id))\n : {};\n const env = { ...mcpClientEnv, ...gitAuthEnv };\n\n const result = await this.provider.exec({\n sandboxId: sandboxInfo.sandbox_id,\n command: input.command,\n cwd: input.cwd,\n env,\n });\n\n return {\n result: {\n content: [{ type: 'text', text: JSON.stringify(result) }],\n isError: !result.success,\n },\n wasInitialized: undefined,\n sandboxCreated: sandboxCreatedFlag,\n sandboxInfo,\n };\n }\n\n async getToolResultDumpDir(): Promise<{ dir: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n const dir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id).replace(/\\/+$/, '');\n return { dir, sandboxCreated };\n }\n\n async uploadFile(params: {\n targetDir: string;\n fileName: string;\n content: Buffer;\n }): Promise<{ filePath: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n await this.ensureSandboxInitialized();\n const { sandbox_id: sandboxId } = this.requiredSandboxInfo;\n const fileName = params.fileName.replace(/^\\/+/, '');\n const targetDir = params.targetDir.replace(/\\/+$/, '');\n const filePath = `${targetDir}/${fileName}`;\n await this.provider.uploadFile({ sandboxId, remotePath: filePath, content: params.content });\n return { filePath, sandboxCreated };\n }\n\n async uploadUserFile(input: { fileName: string; content: Buffer; mime: string }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const result = await this.uploadFile({\n targetDir: Sandbox.FILE_UPLOADS_DIR,\n fileName: input.fileName,\n content: input.content,\n });\n return {\n filePath: result.filePath,\n ...(result.sandboxCreated && { sandboxCreated: result.sandboxCreated }),\n };\n }\n\n async writeArtifact(input: {\n fileName: string;\n content: Buffer;\n sourceTool?: MappedMCPTool | undefined;\n }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const { dir, sandboxCreated: dumpDirCreated } = await this.getToolResultDumpDir();\n const result = await this.uploadFile({\n targetDir: dir,\n fileName: input.fileName,\n content: input.content,\n });\n const sandboxCreated = dumpDirCreated ?? result.sandboxCreated;\n return {\n filePath: result.filePath,\n ...(sandboxCreated && { sandboxCreated }),\n };\n }\n\n /**\n * Upload the MCP client Python script to the sandbox on the first tool call.\n * The sandbox filesystem is persistent within a session, so this only needs to run once.\n */\n private async ensureSandboxInitialized(): Promise<void> {\n this.sandboxInitPromise ??= this.initSandboxEnvironment().catch((e: unknown) => {\n this.sandboxInitPromise = undefined;\n throw e;\n });\n await this.sandboxInitPromise;\n }\n\n private async writeGitCredentials(): Promise<void> {\n const sandboxId = this.requiredSandboxInfo.sandbox_id;\n const credentialsPath = this.provider.getGitCredentialsPath(sandboxId);\n const result = await this.provider.exec({\n sandboxId,\n command: buildSyncGitCredentialsCommand(this.resolvedGitCredentialsContent, credentialsPath),\n });\n ensureExecSuccess(result);\n }\n\n private async initSandboxEnvironment(): Promise<void> {\n const toolResultDumpDir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id);\n\n this.logger.info('Uploading MCP client script and preparing skills directory in sandbox');\n\n const initSteps = [\n `mkdir -p ${MCP_CLIENT_DIR} ${Sandbox.FILE_UPLOADS_DIR} ${toolResultDumpDir} ${SKILLS_DIR}`,\n `rm -f ${MCP_CLIENT_PATH}`,\n `echo '${this.mcpClientScriptBase64}' | base64 -d > ${MCP_CLIENT_PATH}`,\n // Make executable + symlink onto PATH so `mcp-client` runs by name.\n `chmod 0555 ${MCP_CLIENT_PATH}`,\n `ln -sf ${MCP_CLIENT_PATH} ${MCP_CLIENT_BIN_SYMLINK}`,\n ];\n\n // Fold skill installation into this single init exec. The mounter returns a declarative\n // command + env + timeout; runs even when empty so its downloader can prune a reused sandbox.\n const skillInit = this.skillMounter?.getSandboxInit();\n if (skillInit) {\n initSteps.push(skillInit.command);\n }\n\n const script = initSteps.join(' && ');\n const result = await this.provider.exec({\n sandboxId: this.requiredSandboxInfo.sandbox_id,\n command: script,\n env: skillInit?.env,\n timeoutSeconds: skillInit?.timeoutSeconds,\n });\n ensureExecSuccess(result);\n this.logger.info(\n `Sandbox initialized: MCP client at ${MCP_CLIENT_PATH} (symlinked to ${MCP_CLIENT_BIN_SYMLINK}); skills dir ${SKILLS_DIR}`,\n );\n\n await this.writeGitCredentials();\n }\n\n /**\n * Lazily connect the sandbox→gateway NATS bridge. Single-flight and idempotent.\n * Skipped when there are no MCP servers exposed to code mode (nothing to bridge). Connect\n * failures resolve to `undefined` (no NATS env for that exec); mcp_client then errors at call\n * time — there is no HTTPS fallback.\n */\n private async ensureNatsBridgeConnected(sandboxId: string): Promise<SandboxNatsBridge | undefined> {\n // No MCP servers means the bridge has nothing to dispatch to. Avoid the connect cost.\n if (this.codeExecToolSets.length === 0) return undefined;\n this.natsBridgePromise ??= this.connectNatsBridge(sandboxId).catch((e: unknown) => {\n // Connect-level errors (network, timeout, broker not up) are not sticky: a later exec\n // may succeed (e.g. after sandbox warmup). Wipe the cached promise so retries happen.\n this.logger.error('Sandbox NATS bridge connect failed', {\n ...extractErrorLogFields(e),\n sandboxId,\n });\n this.natsBridgePromise = undefined;\n return undefined;\n });\n return this.natsBridgePromise;\n }\n\n private async connectNatsBridge(sandboxId: string): Promise<SandboxNatsBridge> {\n const wsUrl = await this.provider.getNatsBridgeUrl(sandboxId);\n const toolSets = new Map<string, IToolSet>();\n for (const server of this.codeExecToolSets) {\n toolSets.set(server.name, server);\n }\n const bridge = await SandboxNatsBridge.connect({ url: wsUrl, toolSets, logger: this.logger });\n this.logger.info('Sandbox NATS bridge connected', {\n sandboxId,\n toolSetCount: toolSets.size,\n });\n this.attachBridgeInvalidationOnClose(bridge, sandboxId);\n return bridge;\n }\n\n private attachBridgeInvalidationOnClose(bridge: SandboxNatsBridge, sandboxId: string): void {\n const cachedPromise = this.natsBridgePromise;\n // fire and forget\n void bridge.whenClosed().then(() => {\n if (this.natsBridgePromise === cachedPromise) {\n this.natsBridgePromise = undefined;\n this.logger.info('Sandbox NATS bridge closed; cache invalidated, next exec will reconnect', { sandboxId });\n }\n });\n }\n\n // Tears down per-Sandbox resources. Awaits the in-flight connect (if any) and explicitly\n // closes the bridge — dropping the field alone would leak the WS socket and live\n // subscriptions, and orphan a bridge that finishes connecting after close() returned.\n async close(): Promise<void> {\n const pendingPromise = this.natsBridgePromise;\n this.natsBridgePromise = undefined;\n if (!pendingPromise) return;\n let bridge: SandboxNatsBridge | undefined;\n try {\n bridge = await pendingPromise;\n } catch {\n // Connect-error path already logged inside ensureNatsBridgeConnected's .catch.\n }\n if (!bridge) return;\n try {\n await bridge.close();\n } catch (e) {\n this.logger.warn('Error closing sandbox NATS bridge', extractErrorLogFields(e));\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/core/sandbox/Sandbox.ts"],"sourcesContent":["import { context, propagation } from '@opentelemetry/api';\nimport dedent from 'dedent';\nimport type { Logger } from 'winston';\nimport { z } from 'zod';\nimport { InstructionBuilder } from '../InstructionBuilder';\nimport { estimateTokensForString } from '../llm/usage';\nimport type { MappedMCPTool } from '../mcp/convertMCPServers';\nimport { toolResultResponse, type CallToolResponse, type IToolSet } from '../mcp/IMCPServer';\nimport { defineTool, LocalToolMCP } from '../mcp/LocalToolMCP';\nimport {\n DEFERRED_TOOLS_SERVER_ID,\n GET_TOOL_INFO_NAME,\n GET_TOOL_OUTPUT_SCHEMA_NAME,\n LIST_TOOLS_NAME,\n} from '../runtime/DeferredTool';\nimport type { AgentTracing } from '../tracing/AgentTracing';\nimport { extractErrorLogFields } from '../util/errorLogFields';\nimport { CodeModeDispatcher } from './codeMode/CodeModeDispatcher';\nimport type { CodeModeTransport } from './codeMode/CodeModeTransport';\nimport { SANDBOX_FILE_UPLOADS_DIR } from './constants';\nimport { ensureExecSuccess, shellEscape, type SandboxProvider } from './provider/Provider';\nimport { validateNoPathTraversal, validateSandboxOwnedByTenant } from './SandboxErrors';\nimport { sandboxScripts } from './sandboxScripts.gen';\n// Import submodules, not the ./skills barrel, to avoid a cycle (the mounters import from Sandbox).\nimport { SKILLS_DIR } from './skills/constants';\nimport type { ISkillMounter } from './skills/ISkillMounter';\n\nexport interface SandboxInfo {\n sandbox_id: string;\n}\n\n// Downloader/setup scripts can run longer than a normal exec.\nexport const SKILL_DOWNLOAD_TIMEOUT_SECONDS = 180;\n\n// Keeps the sandbox-side NATS wait longer than the host's own MCP deadline, so the host\n// surfaces the real MCP error instead of the bridge client giving up first.\nconst NATS_REQUEST_TIMEOUT_BUFFER_SECONDS = 30;\n\n// Write a script (base64-encoded, never interpolated raw) to a path and run it. Skill mounters reuse it.\nexport function buildWriteAndRunScriptCommand(params: { scriptPath: string; scriptContent: string }): string {\n const b64 = Buffer.from(params.scriptContent, 'utf-8').toString('base64');\n return [\n `mkdir -p \"$(dirname ${params.scriptPath})\"`,\n `rm -f ${params.scriptPath}`,\n `echo ${shellEscape(b64)} | base64 -d > ${params.scriptPath}`,\n `chmod a-w ${params.scriptPath}`,\n `python3 ${params.scriptPath}`,\n ].join(' && ');\n}\n\n/** Write or clear the git credential-store file at an absolute path (no global git config mutation). */\nfunction buildSyncGitCredentialsCommand(credentialsContent: string | null, credentialsPath: string): string {\n if (credentialsContent === null) {\n return `rm -f ${credentialsPath}`;\n }\n // Base64 avoids interpolating credentials directly into the shell command.\n const b64 = Buffer.from(credentialsContent, 'utf-8').toString('base64');\n return (\n `mkdir -p \"$(dirname ${credentialsPath})\" && ` +\n `echo '${b64}' | base64 -d > ${credentialsPath} && ` +\n `chmod 600 ${credentialsPath}`\n );\n}\n\n/**\n * Process-scoped credential.helper via GIT_CONFIG_* (avoids shared ~/.gitconfig).\n * Required for TFY sandbox where many logical sandboxes share one pod/HOME.\n */\nfunction buildGitCredentialHelperEnv(credentialsPath: string): Record<string, string> {\n return {\n GIT_CONFIG_COUNT: '1',\n GIT_CONFIG_KEY_0: 'credential.helper',\n GIT_CONFIG_VALUE_0: `store --file ${credentialsPath}`,\n };\n}\n\nexport interface SandboxStoredFile {\n filePath: string;\n sandboxCreated?: SandboxInfo | undefined;\n}\n\nexport interface SandboxOptions {\n provider: SandboxProvider;\n existingSandboxId?: string | undefined;\n skillMounter?: ISkillMounter | undefined;\n fileDownloadEnabled?: boolean | undefined;\n /** Pre-resolved credential-store file content (null = clear / no git auth). */\n resolvedGitCredentialsContent?: string | null | undefined;\n /**\n * Blocks destructive tools in code mode so they go through the approval flow\n * instead. Must be `true` — approvals are always enabled (the kill switch is gone).\n */\n blockDestructiveToolsInCodeMode: true;\n /** Used to derive the Code Mode NATS wait as request + connect. */\n mcpRequestTimeoutMs: number;\n mcpConnectTimeoutMs: number;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n tracing: AgentTracing;\n logger: Logger;\n}\n\nexport const SANDBOX_EXEC_TOOL_NAME = 'exec';\nconst SANDBOX_MCP_REMINDER_TAG = 'sandbox-mcp-code-mode';\nconst SANDBOX_FILE_OUTPUT_TAG = 'sandbox-file-output';\nexport const SANDBOX_SCHEMA_INFER_TAG = 'sandbox-schema-infer';\nconst SANDBOX_FILE_UPLOADS_TAG = 'sandbox-file-uploads';\n\n// Stable directory inside the sandbox where the MCP client library is uploaded.\n// MCP client upload dir. Two lookup channels:\n// - Python imports → PYTHONPATH points here (set by injectMCPClientEnv).\n// - Shell `mcp-client` → symlinked into MCP_CLIENT_BIN_SYMLINK (on default PATH);\n// basename drops `.py` so the agent doesn't reflexively prefix with `python`.\nconst MCP_CLIENT_DIR = '/opt/tfy/mcp-client';\nconst MCP_CLIENT_PATH = `${MCP_CLIENT_DIR}/mcp_client.py`;\nconst MCP_CLIENT_BIN_SYMLINK = '/usr/local/bin/mcp-client';\n\n// Per-server config sent to mcp_client.py via TFY_MCP_SERVERS.\ninterface SandboxMcpServerConfig {\n allowed_tools: string[];\n}\n\n// Snapshots the active W3C trace context (`traceparent` + optional `tracestate`) into env\n// vars consumed by mcp_client.py. Read with TFY_TRACEPARENT / TFY_TRACESTATE on the sandbox\n// side and attached as NATS message headers; the bridge extracts them on each message so the\n// resulting MCP spans nest under the originating Sandbox: exec span.\nfunction injectTraceContextEnv(): Record<string, string> {\n const carrier: Record<string, string> = {};\n propagation.inject(context.active(), carrier);\n const env: Record<string, string> = {};\n if (carrier['traceparent']) env['TFY_TRACEPARENT'] = carrier['traceparent'];\n if (carrier['tracestate']) env['TFY_TRACESTATE'] = carrier['tracestate'];\n return env;\n}\n\n/**\n * Injects environment variables required for mcp_client.py into sandbox_exec arguments.\n * Transport-specific vars (NATS URL/prefix/timeout) come from `codeModeEnv` via transport.start().\n */\nfunction injectMCPClientEnv(params: {\n env?: Record<string, string> | undefined;\n mcpServers: Record<string, SandboxMcpServerConfig>;\n execExtraEnv?: Readonly<Record<string, string>> | undefined;\n codeModeEnv?: Record<string, string> | undefined;\n}): Record<string, string> {\n const codeModeEnv = params.codeModeEnv ?? {};\n const hasCodeModeTransport = Object.keys(codeModeEnv).length > 0;\n return {\n ...(params.execExtraEnv ?? {}),\n ...(params.env ?? {}),\n PYTHONPATH: MCP_CLIENT_DIR,\n ...(Object.keys(params.mcpServers).length > 0 && {\n TFY_MCP_SERVERS: Buffer.from(JSON.stringify(params.mcpServers)).toString('base64'),\n }),\n ...codeModeEnv,\n ...(hasCodeModeTransport && {\n // W3C trace context captured per-exec so each Code Mode request carries the originating\n // Sandbox: exec span as parent.\n ...injectTraceContextEnv(),\n }),\n // Approvals are always enabled: destructive tools cannot run in code mode.\n TFY_ENABLE_AGENT_APPROVALS: 'true',\n };\n}\n\nconst sandboxExecSchema = z.object({\n intent: z\n .string()\n .describe(\n 'A brief, user-facing explanation of the purpose of this command. Avoid technical jargon and code details.',\n ),\n command: z.string().describe('The shell command to execute in the sandbox.'),\n cwd: z.string().optional().describe('Working directory for command execution.'),\n env: z.record(z.string(), z.string()).optional().describe('Additional environment variables to set.'),\n});\n\nconst SANDBOX_EXEC_DESCRIPTION = `Execute shell commands in a persistent sandbox environment.\nThe sandbox persists across calls within the same session, allowing stateful operations.\nPre-installed: Python 3.13, pydantic, git, curl, helm, jq, ripgrep, genson.\nIMPORTANT: The Agent must never list, print, or expose environment variables as they contain sensitive credentials.\nIMPORTANT: Never read or expose any git credential files or ~/.git-credentials.\n`;\n\nexport function createSandboxLargeToolResponseGuidance(): string {\n return `For use cases where the Agent needs a subset of fields from the MCP response or needs deterministic processing, write code with the provided MCP client and use <${SANDBOX_MCP_REMINDER_TAG}>.`;\n}\n\nexport const SANDBOX_MCP_SERVER_ID = 'sandbox';\ntype SandboxExecInput = z.infer<typeof sandboxExecSchema>;\n\nexport class Sandbox extends LocalToolMCP {\n readonly name = SANDBOX_MCP_SERVER_ID;\n readonly displayName = 'Sandbox';\n override readonly description = 'Persistent sandbox environment for code execution';\n static readonly FILE_UPLOADS_DIR = SANDBOX_FILE_UPLOADS_DIR;\n\n private readonly provider: SandboxProvider;\n private readonly existingSandboxId?: string | undefined;\n private readonly tenantName: string;\n private existingSandboxInfo: SandboxInfo | undefined;\n // Cached promise to prevent concurrent sub-agents from creating duplicate sandboxes.\n private sandboxCreationPromise?: Promise<SandboxInfo> | undefined;\n private sandboxInitPromise?: Promise<void> | undefined;\n private codeExecToolSets: readonly IToolSet[] = [];\n private readonly fileDownloadEnabled: boolean;\n private readonly requestTimeoutSeconds: number;\n private readonly execExtraEnv?: Readonly<Record<string, string>> | undefined;\n private readonly skillMounter?: ISkillMounter | undefined;\n private cachedSkillsSection?: string | undefined;\n private readonly mcpClientScriptBase64: string;\n private readonly logger: Logger;\n // Pre-resolved credential-store file content (null = clear / no git auth).\n private readonly resolvedGitCredentialsContent: string | null;\n private codeModeDispatcher: CodeModeDispatcher | undefined;\n private codeModeTransport: CodeModeTransport | undefined;\n\n private tools = [\n defineTool({\n name: SANDBOX_EXEC_TOOL_NAME,\n description: SANDBOX_EXEC_DESCRIPTION,\n schema: sandboxExecSchema,\n handler: input => this.handleExec(input),\n }),\n ];\n\n constructor(options: SandboxOptions) {\n super({ tracing: options.tracing });\n this.provider = options.provider;\n this.existingSandboxId = options.existingSandboxId;\n this.tenantName = options.execExtraEnv?.['TFY_TENANT_NAME'] ?? '';\n this.skillMounter = options.skillMounter;\n this.fileDownloadEnabled = options.fileDownloadEnabled ?? false;\n const mcpBoundTimeoutMs = options.mcpRequestTimeoutMs + options.mcpConnectTimeoutMs;\n this.requestTimeoutSeconds = Math.ceil(mcpBoundTimeoutMs / 1000) + NATS_REQUEST_TIMEOUT_BUFFER_SECONDS;\n this.execExtraEnv = options.execExtraEnv;\n // Scripts are internal to Sandbox: the upload paths, env contract, and prompt\n // text are all hardcoded here, so injecting different content was never a\n // real extension point. Consumers don't see or provide them.\n this.mcpClientScriptBase64 = Buffer.from(sandboxScripts.mcpClient, 'utf-8').toString('base64');\n this.logger = options.logger.child({ module: 'Sandbox' });\n this.resolvedGitCredentialsContent = options.resolvedGitCredentialsContent ?? null;\n\n if (this.existingSandboxId) {\n validateSandboxOwnedByTenant(this.existingSandboxId, this.tenantName);\n this.existingSandboxInfo = { sandbox_id: this.existingSandboxId };\n }\n }\n\n private buildMcpServersEnvelope(): Record<string, SandboxMcpServerConfig> {\n const out: Record<string, SandboxMcpServerConfig> = {};\n for (const server of this.codeExecToolSets) {\n const allowedTools = server.getAllowedToolNamesForSandbox?.() ?? [];\n out[server.name] = { allowed_tools: allowedTools };\n }\n return out;\n }\n\n protected getTools() {\n return this.tools;\n }\n\n configureCodeMode(servers: readonly IToolSet[]): void {\n if (!servers.length) {\n return;\n }\n if (this.codeModeDispatcher !== undefined || this.codeModeTransport !== undefined) {\n throw new Error('Code Mode is already configured for this Sandbox');\n }\n this.codeExecToolSets = servers;\n this.codeModeDispatcher = new CodeModeDispatcher({ toolSets: servers, logger: this.logger });\n this.codeModeTransport = this.provider.createCodeModeTransport();\n }\n\n estimateSkillsTokens(): number {\n return Math.round(estimateTokensForString(this.renderSkillsSection()));\n }\n\n // Single source for the <skills> section, so the prompt and the token estimate can't drift.\n private renderSkillsSection(): string {\n if (this.cachedSkillsSection === undefined) {\n const skills = new InstructionBuilder('skills');\n this.skillMounter?.instruction(skills);\n this.cachedSkillsSection = skills.build();\n }\n return this.cachedSkillsSection;\n }\n\n buildInstruction(builder: InstructionBuilder): void {\n const providerInstructions = this.provider.getAdditionalInstructions();\n\n const sandboxInstructions = builder.beginSection('sandbox');\n sandboxInstructions.addContent('The Agent has access to a persistent sandbox environment for executing code.');\n sandboxInstructions.addContent('The Agent must NOT read or modify any git credential files.');\n\n this.buildSchemaSection(sandboxInstructions);\n this.buildSkillsSection(sandboxInstructions);\n this.buildMCPSection(sandboxInstructions);\n this.buildFileUploadsSection(sandboxInstructions);\n\n if (this.fileDownloadEnabled) {\n this.buildFileOutputSection(sandboxInstructions);\n }\n\n if (providerInstructions) {\n sandboxInstructions.addContent(providerInstructions);\n }\n }\n\n private buildFileUploadsSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_UPLOADS_TAG,\n `User-uploaded files are placed in ${Sandbox.FILE_UPLOADS_DIR}/. The Agent must not modify files in this directory. Always create a copy at a different location and work on the copy.`,\n );\n }\n\n private buildSchemaSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_SCHEMA_INFER_TAG,\n dedent`\n To infer the schema of a file, the Agent MUST execute:\n head -c 200 file_path && echo \"==\" && genson file_path\n This serves two purpose:\n 1. The first 200 characters of the file is printed. Do NOT print more than 200 characters.\n 2. genson infers and returns JSON schema if the file is JSON.\n Even if the file is not JSON, (1) helps to take the next step in less number of turns\n `,\n );\n }\n\n private buildFileOutputSection(builder: InstructionBuilder): void {\n builder.addSection(\n SANDBOX_FILE_OUTPUT_TAG,\n dedent`\n ## File outputs\n\n The user does not have direct access to the sandbox filesystem. When the Agent wants to output a file to the user, it MUST:\n 1. Ensure that the file is present in the sandbox.\n 2. Then emit a fenced sandbox_artifacts block referencing the file.\n\n \\`\\`\\`sandbox_artifacts\n [Human-readable label](/absolute/path/to/file.ext)\n \\`\\`\\`\n\n For large outputs or content that can be generated through code, prefer writing to a file and emitting the sandbox_artifacts block.\n\n Rules:\n - Use absolute paths, not ~/ or relative paths.\n - One link per line. Markdown link syntax: [label](path).`,\n );\n }\n\n private buildSkillsSection(builder: InstructionBuilder): void {\n builder.addContent(this.renderSkillsSection());\n }\n\n private buildMCPSection(builder: InstructionBuilder): void {\n const toolSetNames = this.codeExecToolSets.map(m => m.name).join(',');\n if (!toolSetNames) return;\n\n builder.addSection(\n SANDBOX_MCP_REMINDER_TAG,\n dedent`\n The following MCP servers are accessible for Code Mode from ${SANDBOX_EXEC_TOOL_NAME} via the pre-installed mcp_client module: ${toolSetNames}\n Tool and functions are same.\n Tool and MCP tools are same.\n\n Use \\`from mcp_client import call_tool\\` in code, or \\`mcp-client\\` in shell — both work from any directory with no setup.\n\n For deferred MCP servers, the Agent MUST:\n 1. Discover tools via ${LIST_TOOLS_NAME} from ${DEFERRED_TOOLS_SERVER_ID}. Never call ${GET_TOOL_OUTPUT_SCHEMA_NAME}.\n 2. Discover inputSchema, outputSchema via ${GET_TOOL_INFO_NAME} for the selected tools.\n\n For non-deferred MCP servers, the Agent MUST:\n 1. Discover outputSchema via ${GET_TOOL_OUTPUT_SCHEMA_NAME} for the selected tools.\n\n For both deferred and non-deferred if outputSchema is absent,\n the Agent MUST say: \"Given the MCP Server does not define an outputSchema let me figure it out first before entering Code Mode.\"\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > file && ${SANDBOX_SCHEMA_INFER_TAG}.\n the Agent MUST execute a single command for above.\n If the Agent does not get the outputSchema, the Agent will encounters scenarios like:\n Agent: \"Let me fix the code - it seems the x might be a string rather than an object in some cases:\"\n The Agent MUST not face the above scenario.\n \n Only after the Agent has the above details, it can enter Code Mode.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n CLI: mcp-client call-tool {server} {tool_a} '{\"param\":\"value\"}'\n # The output of the above command MUST be passed through tools like jq or head -c\n # or should be written to a file. Goal is to reduce content that the Agent prints.\n\n Code: from mcp_client import call_tool\n async def main():\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n # In most cases, it is safe to assume response is a dict.\n # To know the dict keys, the Agent still needs to figure out the outputSchema.\n # The Agent MUST write the code to a file first before executing it.\n\n Code Mode MUST be used in following scenarios:\n\n 1. Reduce structured tool output by executing logic on top of it. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool_a, let me enter Code Mode.\n\n from mcp_client import call_tool\n from collections import Counter\n result = await call_tool(\"{server}\", \"{tool_a}\", body={\"param1\": \"value1\"})\n print(Counter(r[\"a\"] for r in result[\"b\"] if r[\"c\"])) # Here we need outputSchema to know a, b and c exists.\n\n 2. Piping result of one tool call to another tool request. Never present raw output.\n Agent: Now I know the inputSchema and outputSchema of tool1, let me enter Code Mode.\n\n from mcp_client import call_tool\n result1 = await call_tool(\"{server}\", \"{tool1}\", body={\"param1\": \"value1\"})\n result2 = await call_tool(\"{server}\", \"{tool2}\", body={\"param2\": result1[\"value1\"]}) # Here we need outputSchema to know value1 exists.\n print(result2) # Note in this case, we do not need outputSchema of result2 as we are not using any fields.\n\n The Agent MUST NOT use Code Mode just to print the full raw response of a tool. The Agent MUST call the tool directly in this case, there are other mechanisms in place to ensure\n large tool call outputs are not loaded in context.\n \n The Agent must not use Code Mode in the following patterns:\n from mcp_client import call_tool\n import json\n result = await call_tool(\"{server}\", \"{tool}\", body={\"param1\": \"value1\"})\n print(result) # Never do this. Call tool without Code Mode.\n print(json.dumps(result, ...)) # Never do this. Call tool without Code Mode.\n print(result[\"a\"][:100]) # Never do this. Call tool without Code Mode.\n\n mcp-client call-tool {server} {tool} '{\"param\":\"value\"}' > data && cat data # Never do this. Call tool without Code Mode.\n `,\n );\n }\n\n private get requiredSandboxInfo(): SandboxInfo {\n if (!this.existingSandboxInfo) {\n throw new Error('Sandbox info not available');\n }\n return this.existingSandboxInfo;\n }\n\n private async ensureSandboxCreated(): Promise<{\n sandboxInfo: SandboxInfo;\n sandboxCreated: SandboxInfo | undefined;\n }> {\n if (this.existingSandboxInfo) {\n return { sandboxInfo: this.existingSandboxInfo, sandboxCreated: undefined };\n }\n // Provider returns camelCase `{ sandboxId }`; rename to the snake_case\n // `SandboxInfo` shape used everywhere downstream (Redis JSON, wire event,\n // in-memory state).\n this.sandboxCreationPromise ??= this.provider\n .createSandbox()\n .then(({ sandboxId }) => {\n validateSandboxOwnedByTenant(sandboxId, this.tenantName);\n return { sandbox_id: sandboxId };\n })\n .catch((e: unknown) => {\n this.sandboxCreationPromise = undefined;\n throw e;\n });\n\n const sandboxInfo = await this.sandboxCreationPromise;\n // Concurrent callers may both await the same creation promise; only the first\n // should report sandboxCreated. Read via a local to avoid control-flow narrowing\n // that treats existingSandboxInfo as always undefined after the early return above.\n const prior = this.existingSandboxInfo as SandboxInfo | undefined;\n const isNew = prior === undefined;\n this.existingSandboxInfo = sandboxInfo;\n return { sandboxInfo, sandboxCreated: isNew ? sandboxInfo : undefined };\n }\n\n private async handleExec(input: SandboxExecInput): Promise<CallToolResponse> {\n const { sandboxInfo, sandboxCreated } = await this.ensureSandboxCreated();\n const sandboxCreatedFlag = Boolean(sandboxCreated);\n try {\n await this.ensureSandboxInitialized();\n } catch (e) {\n this.logger.error('Sandbox initialization failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Sandbox initialization failed';\n return toolResultResponse({\n text: `Sandbox initialization failed: ${message}`,\n isError: true,\n overrides: { sandboxCreated: sandboxCreatedFlag, sandboxInfo },\n });\n }\n const codeModeEnv = await this.ensureCodeModeStarted(sandboxInfo.sandbox_id);\n const mcpClientEnv = injectMCPClientEnv({\n env: input.env,\n mcpServers: this.buildMcpServersEnvelope(),\n execExtraEnv: this.execExtraEnv,\n codeModeEnv,\n });\n const gitAuthEnv =\n this.resolvedGitCredentialsContent !== null\n ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(sandboxInfo.sandbox_id))\n : {};\n const env = { ...mcpClientEnv, ...gitAuthEnv };\n\n const result = await this.provider.exec({\n sandboxId: sandboxInfo.sandbox_id,\n command: input.command,\n cwd: input.cwd,\n env,\n });\n\n return {\n result: {\n content: [{ type: 'text', text: JSON.stringify(result) }],\n isError: !result.success,\n },\n wasInitialized: undefined,\n sandboxCreated: sandboxCreatedFlag,\n sandboxInfo,\n };\n }\n\n async getToolResultDumpDir(): Promise<{ dir: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n const dir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id).replace(/\\/+$/, '');\n return { dir, sandboxCreated };\n }\n\n async uploadFile(params: {\n targetDir: string;\n fileName: string;\n content: Buffer;\n }): Promise<{ filePath: string; sandboxCreated: SandboxInfo | undefined }> {\n const { sandboxCreated } = await this.ensureSandboxCreated();\n await this.ensureSandboxInitialized();\n const { sandbox_id: sandboxId } = this.requiredSandboxInfo;\n const fileName = params.fileName.replace(/^\\/+/, '');\n const targetDir = params.targetDir.replace(/\\/+$/, '');\n const filePath = `${targetDir}/${fileName}`;\n await this.provider.uploadFile({ sandboxId, remotePath: filePath, content: params.content });\n return { filePath, sandboxCreated };\n }\n\n async uploadUserFile(input: { fileName: string; content: Buffer; mime: string }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const result = await this.uploadFile({\n targetDir: Sandbox.FILE_UPLOADS_DIR,\n fileName: input.fileName,\n content: input.content,\n });\n return {\n filePath: result.filePath,\n ...(result.sandboxCreated && { sandboxCreated: result.sandboxCreated }),\n };\n }\n\n async writeArtifact(input: {\n fileName: string;\n content: Buffer;\n sourceTool?: MappedMCPTool | undefined;\n }): Promise<SandboxStoredFile> {\n validateNoPathTraversal(input.fileName);\n const { dir, sandboxCreated: dumpDirCreated } = await this.getToolResultDumpDir();\n const result = await this.uploadFile({\n targetDir: dir,\n fileName: input.fileName,\n content: input.content,\n });\n const sandboxCreated = dumpDirCreated ?? result.sandboxCreated;\n return {\n filePath: result.filePath,\n ...(sandboxCreated && { sandboxCreated }),\n };\n }\n\n /**\n * Upload the MCP client Python script to the sandbox on the first tool call.\n * The sandbox filesystem is persistent within a session, so this only needs to run once.\n */\n private async ensureSandboxInitialized(): Promise<void> {\n this.sandboxInitPromise ??= this.initSandboxEnvironment().catch((e: unknown) => {\n this.sandboxInitPromise = undefined;\n throw e;\n });\n await this.sandboxInitPromise;\n }\n\n private async writeGitCredentials(): Promise<void> {\n const sandboxId = this.requiredSandboxInfo.sandbox_id;\n const credentialsPath = this.provider.getGitCredentialsPath(sandboxId);\n const result = await this.provider.exec({\n sandboxId,\n command: buildSyncGitCredentialsCommand(this.resolvedGitCredentialsContent, credentialsPath),\n });\n ensureExecSuccess(result);\n }\n\n private async initSandboxEnvironment(): Promise<void> {\n const toolResultDumpDir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id);\n\n this.logger.info('Uploading MCP client script and preparing skills directory in sandbox');\n\n const initSteps = [\n `mkdir -p ${MCP_CLIENT_DIR} ${Sandbox.FILE_UPLOADS_DIR} ${toolResultDumpDir} ${SKILLS_DIR}`,\n `rm -f ${MCP_CLIENT_PATH}`,\n `echo '${this.mcpClientScriptBase64}' | base64 -d > ${MCP_CLIENT_PATH}`,\n // Make executable + symlink onto PATH so `mcp-client` runs by name.\n `chmod 0555 ${MCP_CLIENT_PATH}`,\n `ln -sf ${MCP_CLIENT_PATH} ${MCP_CLIENT_BIN_SYMLINK}`,\n ];\n\n // Fold skill installation into this single init exec. The mounter returns a declarative\n // command + env + timeout; runs even when empty so its downloader can prune a reused sandbox.\n const skillInit = this.skillMounter?.getSandboxInit();\n if (skillInit) {\n initSteps.push(skillInit.command);\n }\n\n const script = initSteps.join(' && ');\n const result = await this.provider.exec({\n sandboxId: this.requiredSandboxInfo.sandbox_id,\n command: script,\n env: skillInit?.env,\n timeoutSeconds: skillInit?.timeoutSeconds,\n });\n ensureExecSuccess(result);\n this.logger.info(\n `Sandbox initialized: MCP client at ${MCP_CLIENT_PATH} (symlinked to ${MCP_CLIENT_BIN_SYMLINK}); skills dir ${SKILLS_DIR}`,\n );\n\n await this.writeGitCredentials();\n }\n\n /**\n * Lazily start Code Mode transport. Single-flight and idempotent inside the transport.\n * Skipped when code mode was never configured. Connect failures resolve to `undefined`\n * (no Code Mode env for that exec); mcp_client then errors at call time — no HTTPS fallback.\n */\n private async ensureCodeModeStarted(sandboxId: string): Promise<Record<string, string> | undefined> {\n const dispatcher = this.codeModeDispatcher;\n const transport = this.codeModeTransport;\n if (dispatcher === undefined || transport === undefined) {\n return undefined;\n }\n try {\n const { env } = await transport.start({\n codeModeDispatcher: dispatcher,\n sandboxId,\n requestTimeoutSeconds: this.requestTimeoutSeconds,\n });\n return env;\n } catch (e) {\n // Connect-level errors are not sticky: transport clears its session so a later exec may retry.\n this.logger.error('Code Mode transport start failed', {\n ...extractErrorLogFields(e),\n sandboxId,\n });\n return undefined;\n }\n }\n\n // Tears down per-Sandbox Code Mode resources. Dispatcher close first (in-flight requests get\n // source: transport), then transport.stop() — always safe whether start succeeded, failed, or never ran.\n async close(): Promise<void> {\n this.codeModeDispatcher?.close();\n this.codeModeDispatcher = undefined;\n const transport = this.codeModeTransport;\n this.codeModeTransport = undefined;\n if (transport === undefined) return;\n try {\n await transport.stop();\n } catch (e) {\n this.logger.warn('Error stopping Code Mode transport', extractErrorLogFields(e));\n }\n }\n}\n"],"mappings":";AAAA,SAAS,SAAS,mBAAmB;AACrC,OAAO,YAAY;AAEnB,SAAS,SAAS;AAClB,SAAS,0BAA0B;AACnC,SAAS,+BAA+B;AAExC,SAAS,0BAAgE;AACzE,SAAS,YAAY,oBAAoB;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,6BAA6B;AACtC,SAAS,0BAA0B;AAEnC,SAAS,gCAAgC;AACzC,SAAS,mBAAmB,mBAAyC;AACrE,SAAS,yBAAyB,oCAAoC;AACtE,SAAS,sBAAsB;AAE/B,SAAS,kBAAkB;AAQpB,IAAM,iCAAiC;AAI9C,IAAM,sCAAsC;AAGrC,SAAS,8BAA8B,QAA+D;AAC3G,QAAM,MAAM,OAAO,KAAK,OAAO,eAAe,OAAO,EAAE,SAAS,QAAQ;AACxE,SAAO;AAAA,IACL,uBAAuB,OAAO,UAAU;AAAA,IACxC,SAAS,OAAO,UAAU;AAAA,IAC1B,QAAQ,YAAY,GAAG,CAAC,kBAAkB,OAAO,UAAU;AAAA,IAC3D,aAAa,OAAO,UAAU;AAAA,IAC9B,WAAW,OAAO,UAAU;AAAA,EAC9B,EAAE,KAAK,MAAM;AACf;AAGA,SAAS,+BAA+B,oBAAmC,iBAAiC;AAC1G,MAAI,uBAAuB,MAAM;AAC/B,WAAO,SAAS,eAAe;AAAA,EACjC;AAEA,QAAM,MAAM,OAAO,KAAK,oBAAoB,OAAO,EAAE,SAAS,QAAQ;AACtE,SACE,uBAAuB,eAAe,eAC7B,GAAG,mBAAmB,eAAe,iBACjC,eAAe;AAEhC;AAMA,SAAS,4BAA4B,iBAAiD;AACpF,SAAO;AAAA,IACL,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB,oBAAoB,gBAAgB,eAAe;AAAA,EACrD;AACF;AA2BO,IAAM,yBAAyB;AACtC,IAAM,2BAA2B;AACjC,IAAM,0BAA0B;AACzB,IAAM,2BAA2B;AACxC,IAAM,2BAA2B;AAOjC,IAAM,iBAAiB;AACvB,IAAM,kBAAkB,GAAG,cAAc;AACzC,IAAM,yBAAyB;AAW/B,SAAS,wBAAgD;AACvD,QAAM,UAAkC,CAAC;AACzC,cAAY,OAAO,QAAQ,OAAO,GAAG,OAAO;AAC5C,QAAM,MAA8B,CAAC;AACrC,MAAI,QAAQ,aAAa,EAAG,KAAI,iBAAiB,IAAI,QAAQ,aAAa;AAC1E,MAAI,QAAQ,YAAY,EAAG,KAAI,gBAAgB,IAAI,QAAQ,YAAY;AACvE,SAAO;AACT;AAMA,SAAS,mBAAmB,QAKD;AACzB,QAAM,cAAc,OAAO,eAAe,CAAC;AAC3C,QAAM,uBAAuB,OAAO,KAAK,WAAW,EAAE,SAAS;AAC/D,SAAO;AAAA,IACL,GAAI,OAAO,gBAAgB,CAAC;AAAA,IAC5B,GAAI,OAAO,OAAO,CAAC;AAAA,IACnB,YAAY;AAAA,IACZ,GAAI,OAAO,KAAK,OAAO,UAAU,EAAE,SAAS,KAAK;AAAA,MAC/C,iBAAiB,OAAO,KAAK,KAAK,UAAU,OAAO,UAAU,CAAC,EAAE,SAAS,QAAQ;AAAA,IACnF;AAAA,IACA,GAAG;AAAA,IACH,GAAI,wBAAwB;AAAA;AAAA;AAAA,MAG1B,GAAG,sBAAsB;AAAA,IAC3B;AAAA;AAAA,IAEA,4BAA4B;AAAA,EAC9B;AACF;AAEA,IAAM,oBAAoB,EAAE,OAAO;AAAA,EACjC,QAAQ,EACL,OAAO,EACP;AAAA,IACC;AAAA,EACF;AAAA,EACF,SAAS,EAAE,OAAO,EAAE,SAAS,8CAA8C;AAAA,EAC3E,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,0CAA0C;AAAA,EAC9E,KAAK,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,SAAS,0CAA0C;AACtG,CAAC;AAED,IAAM,2BAA2B;AAAA;AAAA;AAAA;AAAA;AAAA;AAO1B,SAAS,yCAAiD;AAC/D,SAAO,oKAAoK,wBAAwB;AACrM;AAEO,IAAM,wBAAwB;AAG9B,IAAM,UAAN,MAAM,iBAAgB,aAAa;AAAA,EAC/B,OAAO;AAAA,EACP,cAAc;AAAA,EACL,cAAc;AAAA,EAChC,OAAgB,mBAAmB;AAAA,EAElB;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA,mBAAwC,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACT;AAAA,EACS;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACT;AAAA,EACA;AAAA,EAEA,QAAQ;AAAA,IACd,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa;AAAA,MACb,QAAQ;AAAA,MACR,SAAS,WAAS,KAAK,WAAW,KAAK;AAAA,IACzC,CAAC;AAAA,EACH;AAAA,EAEA,YAAY,SAAyB;AACnC,UAAM,EAAE,SAAS,QAAQ,QAAQ,CAAC;AAClC,SAAK,WAAW,QAAQ;AACxB,SAAK,oBAAoB,QAAQ;AACjC,SAAK,aAAa,QAAQ,eAAe,iBAAiB,KAAK;AAC/D,SAAK,eAAe,QAAQ;AAC5B,SAAK,sBAAsB,QAAQ,uBAAuB;AAC1D,UAAM,oBAAoB,QAAQ,sBAAsB,QAAQ;AAChE,SAAK,wBAAwB,KAAK,KAAK,oBAAoB,GAAI,IAAI;AACnE,SAAK,eAAe,QAAQ;AAI5B,SAAK,wBAAwB,OAAO,KAAK,eAAe,WAAW,OAAO,EAAE,SAAS,QAAQ;AAC7F,SAAK,SAAS,QAAQ,OAAO,MAAM,EAAE,QAAQ,UAAU,CAAC;AACxD,SAAK,gCAAgC,QAAQ,iCAAiC;AAE9E,QAAI,KAAK,mBAAmB;AAC1B,mCAA6B,KAAK,mBAAmB,KAAK,UAAU;AACpE,WAAK,sBAAsB,EAAE,YAAY,KAAK,kBAAkB;AAAA,IAClE;AAAA,EACF;AAAA,EAEQ,0BAAkE;AACxE,UAAM,MAA8C,CAAC;AACrD,eAAW,UAAU,KAAK,kBAAkB;AAC1C,YAAM,eAAe,OAAO,gCAAgC,KAAK,CAAC;AAClE,UAAI,OAAO,IAAI,IAAI,EAAE,eAAe,aAAa;AAAA,IACnD;AACA,WAAO;AAAA,EACT;AAAA,EAEU,WAAW;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,kBAAkB,SAAoC;AACpD,QAAI,CAAC,QAAQ,QAAQ;AACnB;AAAA,IACF;AACA,QAAI,KAAK,uBAAuB,UAAa,KAAK,sBAAsB,QAAW;AACjF,YAAM,IAAI,MAAM,kDAAkD;AAAA,IACpE;AACA,SAAK,mBAAmB;AACxB,SAAK,qBAAqB,IAAI,mBAAmB,EAAE,UAAU,SAAS,QAAQ,KAAK,OAAO,CAAC;AAC3F,SAAK,oBAAoB,KAAK,SAAS,wBAAwB;AAAA,EACjE;AAAA,EAEA,uBAA+B;AAC7B,WAAO,KAAK,MAAM,wBAAwB,KAAK,oBAAoB,CAAC,CAAC;AAAA,EACvE;AAAA;AAAA,EAGQ,sBAA8B;AACpC,QAAI,KAAK,wBAAwB,QAAW;AAC1C,YAAM,SAAS,IAAI,mBAAmB,QAAQ;AAC9C,WAAK,cAAc,YAAY,MAAM;AACrC,WAAK,sBAAsB,OAAO,MAAM;AAAA,IAC1C;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,iBAAiB,SAAmC;AAClD,UAAM,uBAAuB,KAAK,SAAS,0BAA0B;AAErE,UAAM,sBAAsB,QAAQ,aAAa,SAAS;AAC1D,wBAAoB,WAAW,8EAA8E;AAC7G,wBAAoB,WAAW,6DAA6D;AAE5F,SAAK,mBAAmB,mBAAmB;AAC3C,SAAK,mBAAmB,mBAAmB;AAC3C,SAAK,gBAAgB,mBAAmB;AACxC,SAAK,wBAAwB,mBAAmB;AAEhD,QAAI,KAAK,qBAAqB;AAC5B,WAAK,uBAAuB,mBAAmB;AAAA,IACjD;AAEA,QAAI,sBAAsB;AACxB,0BAAoB,WAAW,oBAAoB;AAAA,IACrD;AAAA,EACF;AAAA,EAEQ,wBAAwB,SAAmC;AACjE,YAAQ;AAAA,MACN;AAAA,MACA,qCAAqC,SAAQ,gBAAgB;AAAA,IAC/D;AAAA,EACF;AAAA,EAEQ,mBAAmB,SAAmC;AAC5D,YAAQ;AAAA,MACN;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQF;AAAA,EACF;AAAA,EAEQ,uBAAuB,SAAmC;AAChE,YAAQ;AAAA,MACN;AAAA,MACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBF;AAAA,EACF;AAAA,EAEQ,mBAAmB,SAAmC;AAC5D,YAAQ,WAAW,KAAK,oBAAoB,CAAC;AAAA,EAC/C;AAAA,EAEQ,gBAAgB,SAAmC;AACzD,UAAM,eAAe,KAAK,iBAAiB,IAAI,OAAK,EAAE,IAAI,EAAE,KAAK,GAAG;AACpE,QAAI,CAAC,aAAc;AAEnB,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,sEACgE,sBAAsB,6CAA6C,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gCAOrH,eAAe,SAAS,wBAAwB,gBAAgB,2BAA2B;AAAA,oDACvE,kBAAkB;AAAA;AAAA;AAAA,uCAG/B,2BAA2B;AAAA;AAAA;AAAA;AAAA,6EAIW,wBAAwB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAmDjG;AAAA,EACF;AAAA,EAEA,IAAY,sBAAmC;AAC7C,QAAI,CAAC,KAAK,qBAAqB;AAC7B,YAAM,IAAI,MAAM,4BAA4B;AAAA,IAC9C;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,uBAGX;AACD,QAAI,KAAK,qBAAqB;AAC5B,aAAO,EAAE,aAAa,KAAK,qBAAqB,gBAAgB,OAAU;AAAA,IAC5E;AAIA,SAAK,2BAA2B,KAAK,SAClC,cAAc,EACd,KAAK,CAAC,EAAE,UAAU,MAAM;AACvB,mCAA6B,WAAW,KAAK,UAAU;AACvD,aAAO,EAAE,YAAY,UAAU;AAAA,IACjC,CAAC,EACA,MAAM,CAAC,MAAe;AACrB,WAAK,yBAAyB;AAC9B,YAAM;AAAA,IACR,CAAC;AAEH,UAAM,cAAc,MAAM,KAAK;AAI/B,UAAM,QAAQ,KAAK;AACnB,UAAM,QAAQ,UAAU;AACxB,SAAK,sBAAsB;AAC3B,WAAO,EAAE,aAAa,gBAAgB,QAAQ,cAAc,OAAU;AAAA,EACxE;AAAA,EAEA,MAAc,WAAW,OAAoD;AAC3E,UAAM,EAAE,aAAa,eAAe,IAAI,MAAM,KAAK,qBAAqB;AACxE,UAAM,qBAAqB,QAAQ,cAAc;AACjD,QAAI;AACF,YAAM,KAAK,yBAAyB;AAAA,IACtC,SAAS,GAAG;AACV,WAAK,OAAO,MAAM,iCAAiC,sBAAsB,CAAC,CAAC;AAC3E,YAAM,UAAU,aAAa,QAAQ,EAAE,UAAU;AACjD,aAAO,mBAAmB;AAAA,QACxB,MAAM,kCAAkC,OAAO;AAAA,QAC/C,SAAS;AAAA,QACT,WAAW,EAAE,gBAAgB,oBAAoB,YAAY;AAAA,MAC/D,CAAC;AAAA,IACH;AACA,UAAM,cAAc,MAAM,KAAK,sBAAsB,YAAY,UAAU;AAC3E,UAAM,eAAe,mBAAmB;AAAA,MACtC,KAAK,MAAM;AAAA,MACX,YAAY,KAAK,wBAAwB;AAAA,MACzC,cAAc,KAAK;AAAA,MACnB;AAAA,IACF,CAAC;AACD,UAAM,aACJ,KAAK,kCAAkC,OACnC,4BAA4B,KAAK,SAAS,sBAAsB,YAAY,UAAU,CAAC,IACvF,CAAC;AACP,UAAM,MAAM,EAAE,GAAG,cAAc,GAAG,WAAW;AAE7C,UAAM,SAAS,MAAM,KAAK,SAAS,KAAK;AAAA,MACtC,WAAW,YAAY;AAAA,MACvB,SAAS,MAAM;AAAA,MACf,KAAK,MAAM;AAAA,MACX;AAAA,IACF,CAAC;AAED,WAAO;AAAA,MACL,QAAQ;AAAA,QACN,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,MAAM,EAAE,CAAC;AAAA,QACxD,SAAS,CAAC,OAAO;AAAA,MACnB;AAAA,MACA,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,MAChB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,uBAA0F;AAC9F,UAAM,EAAE,eAAe,IAAI,MAAM,KAAK,qBAAqB;AAC3D,UAAM,MAAM,KAAK,SAAS,qBAAqB,KAAK,oBAAoB,UAAU,EAAE,QAAQ,QAAQ,EAAE;AACtG,WAAO,EAAE,KAAK,eAAe;AAAA,EAC/B;AAAA,EAEA,MAAM,WAAW,QAI0D;AACzE,UAAM,EAAE,eAAe,IAAI,MAAM,KAAK,qBAAqB;AAC3D,UAAM,KAAK,yBAAyB;AACpC,UAAM,EAAE,YAAY,UAAU,IAAI,KAAK;AACvC,UAAM,WAAW,OAAO,SAAS,QAAQ,QAAQ,EAAE;AACnD,UAAM,YAAY,OAAO,UAAU,QAAQ,QAAQ,EAAE;AACrD,UAAM,WAAW,GAAG,SAAS,IAAI,QAAQ;AACzC,UAAM,KAAK,SAAS,WAAW,EAAE,WAAW,YAAY,UAAU,SAAS,OAAO,QAAQ,CAAC;AAC3F,WAAO,EAAE,UAAU,eAAe;AAAA,EACpC;AAAA,EAEA,MAAM,eAAe,OAAwF;AAC3G,4BAAwB,MAAM,QAAQ;AACtC,UAAM,SAAS,MAAM,KAAK,WAAW;AAAA,MACnC,WAAW,SAAQ;AAAA,MACnB,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,WAAO;AAAA,MACL,UAAU,OAAO;AAAA,MACjB,GAAI,OAAO,kBAAkB,EAAE,gBAAgB,OAAO,eAAe;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,OAIW;AAC7B,4BAAwB,MAAM,QAAQ;AACtC,UAAM,EAAE,KAAK,gBAAgB,eAAe,IAAI,MAAM,KAAK,qBAAqB;AAChF,UAAM,SAAS,MAAM,KAAK,WAAW;AAAA,MACnC,WAAW;AAAA,MACX,UAAU,MAAM;AAAA,MAChB,SAAS,MAAM;AAAA,IACjB,CAAC;AACD,UAAM,iBAAiB,kBAAkB,OAAO;AAChD,WAAO;AAAA,MACL,UAAU,OAAO;AAAA,MACjB,GAAI,kBAAkB,EAAE,eAAe;AAAA,IACzC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,2BAA0C;AACtD,SAAK,uBAAuB,KAAK,uBAAuB,EAAE,MAAM,CAAC,MAAe;AAC9E,WAAK,qBAAqB;AAC1B,YAAM;AAAA,IACR,CAAC;AACD,UAAM,KAAK;AAAA,EACb;AAAA,EAEA,MAAc,sBAAqC;AACjD,UAAM,YAAY,KAAK,oBAAoB;AAC3C,UAAM,kBAAkB,KAAK,SAAS,sBAAsB,SAAS;AACrE,UAAM,SAAS,MAAM,KAAK,SAAS,KAAK;AAAA,MACtC;AAAA,MACA,SAAS,+BAA+B,KAAK,+BAA+B,eAAe;AAAA,IAC7F,CAAC;AACD,sBAAkB,MAAM;AAAA,EAC1B;AAAA,EAEA,MAAc,yBAAwC;AACpD,UAAM,oBAAoB,KAAK,SAAS,qBAAqB,KAAK,oBAAoB,UAAU;AAEhG,SAAK,OAAO,KAAK,uEAAuE;AAExF,UAAM,YAAY;AAAA,MAChB,YAAY,cAAc,IAAI,SAAQ,gBAAgB,IAAI,iBAAiB,IAAI,UAAU;AAAA,MACzF,SAAS,eAAe;AAAA,MACxB,SAAS,KAAK,qBAAqB,mBAAmB,eAAe;AAAA;AAAA,MAErE,cAAc,eAAe;AAAA,MAC7B,UAAU,eAAe,IAAI,sBAAsB;AAAA,IACrD;AAIA,UAAM,YAAY,KAAK,cAAc,eAAe;AACpD,QAAI,WAAW;AACb,gBAAU,KAAK,UAAU,OAAO;AAAA,IAClC;AAEA,UAAM,SAAS,UAAU,KAAK,MAAM;AACpC,UAAM,SAAS,MAAM,KAAK,SAAS,KAAK;AAAA,MACtC,WAAW,KAAK,oBAAoB;AAAA,MACpC,SAAS;AAAA,MACT,KAAK,WAAW;AAAA,MAChB,gBAAgB,WAAW;AAAA,IAC7B,CAAC;AACD,sBAAkB,MAAM;AACxB,SAAK,OAAO;AAAA,MACV,sCAAsC,eAAe,kBAAkB,sBAAsB,iBAAiB,UAAU;AAAA,IAC1H;AAEA,UAAM,KAAK,oBAAoB;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,sBAAsB,WAAgE;AAClG,UAAM,aAAa,KAAK;AACxB,UAAM,YAAY,KAAK;AACvB,QAAI,eAAe,UAAa,cAAc,QAAW;AACvD,aAAO;AAAA,IACT;AACA,QAAI;AACF,YAAM,EAAE,IAAI,IAAI,MAAM,UAAU,MAAM;AAAA,QACpC,oBAAoB;AAAA,QACpB;AAAA,QACA,uBAAuB,KAAK;AAAA,MAC9B,CAAC;AACD,aAAO;AAAA,IACT,SAAS,GAAG;AAEV,WAAK,OAAO,MAAM,oCAAoC;AAAA,QACpD,GAAG,sBAAsB,CAAC;AAAA,QAC1B;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA,EAIA,MAAM,QAAuB;AAC3B,SAAK,oBAAoB,MAAM;AAC/B,SAAK,qBAAqB;AAC1B,UAAM,YAAY,KAAK;AACvB,SAAK,oBAAoB;AACzB,QAAI,cAAc,OAAW;AAC7B,QAAI;AACF,YAAM,UAAU,KAAK;AAAA,IACvB,SAAS,GAAG;AACV,WAAK,OAAO,KAAK,sCAAsC,sBAAsB,CAAC,CAAC;AAAA,IACjF;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,29 @@
1
+ import { type IToolSet } from '../../mcp/IMCPServer';
2
+ import type { CodeModeReply, CodeModeRequest } from './types';
3
+ /** Minimal logger surface used by Code Mode (avoids a hard winston dep for PoC binders). */
4
+ export interface CodeModeLogger {
5
+ child(bindings: Record<string, unknown>): CodeModeLogger;
6
+ error(message: string, meta?: unknown): void;
7
+ }
8
+ /**
9
+ * Shared Code Mode policy leaf: tool routing, authz, error `source`. No NATS/UDS.
10
+ */
11
+ export declare class CodeModeDispatcher {
12
+ private readonly toolSets;
13
+ private readonly logger;
14
+ private closed;
15
+ constructor(params: {
16
+ toolSets: readonly IToolSet[];
17
+ logger: CodeModeLogger;
18
+ });
19
+ close(): void;
20
+ dispatch(params: {
21
+ request: CodeModeRequest;
22
+ traceCarrier: Readonly<Record<string, string>>;
23
+ }): Promise<CodeModeReply>;
24
+ private route;
25
+ private getToolSet;
26
+ private handleListTools;
27
+ private handleCallTool;
28
+ }
29
+ //# sourceMappingURL=CodeModeDispatcher.d.ts.map