@truefoundry/trueforge-core 0.1.2 → 0.1.4-rc.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 (117) hide show
  1. package/dist/agent-session/TurnResourceResolver.d.ts.map +1 -1
  2. package/dist/agent-session/TurnResourceResolver.js +1 -4
  3. package/dist/agent-session/TurnResourceResolver.js.map +1 -1
  4. package/dist/agent-session/TurnResourceResolver.mjs +1 -4
  5. package/dist/agent-session/TurnResourceResolver.mjs.map +1 -1
  6. package/dist/agent-session/index.d.ts +1 -1
  7. package/dist/agent-session/index.d.ts.map +1 -1
  8. package/dist/agent-session/index.js.map +1 -1
  9. package/dist/agent-session/index.mjs.map +1 -1
  10. package/dist/agent-session/schemas/agentSpec.d.ts +7 -28
  11. package/dist/agent-session/schemas/agentSpec.d.ts.map +1 -1
  12. package/dist/agent-session/schemas/agentSpec.js +15 -43
  13. package/dist/agent-session/schemas/agentSpec.js.map +1 -1
  14. package/dist/agent-session/schemas/agentSpec.mjs +15 -43
  15. package/dist/agent-session/schemas/agentSpec.mjs.map +1 -1
  16. package/dist/agent-session/schemas/session.d.ts +0 -39
  17. package/dist/agent-session/schemas/session.d.ts.map +1 -1
  18. package/dist/core/index.d.ts +4 -1
  19. package/dist/core/index.d.ts.map +1 -1
  20. package/dist/core/index.js +12 -0
  21. package/dist/core/index.js.map +1 -1
  22. package/dist/core/index.mjs +7 -0
  23. package/dist/core/index.mjs.map +1 -1
  24. package/dist/core/runtime/AgentThread.d.ts.map +1 -1
  25. package/dist/core/runtime/AgentThread.js +25 -11
  26. package/dist/core/runtime/AgentThread.js.map +1 -1
  27. package/dist/core/runtime/AgentThread.mjs +25 -11
  28. package/dist/core/runtime/AgentThread.mjs.map +1 -1
  29. package/dist/core/sandbox/Sandbox.d.ts +15 -5
  30. package/dist/core/sandbox/Sandbox.d.ts.map +1 -1
  31. package/dist/core/sandbox/Sandbox.js +200 -84
  32. package/dist/core/sandbox/Sandbox.js.map +1 -1
  33. package/dist/core/sandbox/Sandbox.mjs +201 -85
  34. package/dist/core/sandbox/Sandbox.mjs.map +1 -1
  35. package/dist/core/sandbox/SandboxErrors.d.ts +7 -3
  36. package/dist/core/sandbox/SandboxErrors.d.ts.map +1 -1
  37. package/dist/core/sandbox/SandboxErrors.js +6 -6
  38. package/dist/core/sandbox/SandboxErrors.js.map +1 -1
  39. package/dist/core/sandbox/SandboxErrors.mjs +6 -6
  40. package/dist/core/sandbox/SandboxErrors.mjs.map +1 -1
  41. package/dist/core/sandbox/codeMode/CodeModeTransport.d.ts +22 -0
  42. package/dist/core/sandbox/codeMode/CodeModeTransport.d.ts.map +1 -1
  43. package/dist/core/sandbox/codeMode/CodeModeTransport.js.map +1 -1
  44. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.d.ts +9 -1
  45. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.d.ts.map +1 -1
  46. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.js +12 -0
  47. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.js.map +1 -1
  48. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.mjs +12 -0
  49. package/dist/core/sandbox/codeMode/nats/CodeModeNatsTransport.mjs.map +1 -1
  50. package/dist/core/sandbox/constants.d.ts +0 -2
  51. package/dist/core/sandbox/constants.d.ts.map +1 -1
  52. package/dist/core/sandbox/constants.js +2 -5
  53. package/dist/core/sandbox/constants.js.map +1 -1
  54. package/dist/core/sandbox/constants.mjs +1 -3
  55. package/dist/core/sandbox/constants.mjs.map +1 -1
  56. package/dist/core/sandbox/provider/DaytonaProvider.d.ts +4 -0
  57. package/dist/core/sandbox/provider/DaytonaProvider.d.ts.map +1 -1
  58. package/dist/core/sandbox/provider/DaytonaProvider.js +27 -5
  59. package/dist/core/sandbox/provider/DaytonaProvider.js.map +1 -1
  60. package/dist/core/sandbox/provider/DaytonaProvider.mjs +27 -4
  61. package/dist/core/sandbox/provider/DaytonaProvider.mjs.map +1 -1
  62. package/dist/core/sandbox/provider/Provider.d.ts +13 -1
  63. package/dist/core/sandbox/provider/Provider.d.ts.map +1 -1
  64. package/dist/core/sandbox/provider/Provider.js.map +1 -1
  65. package/dist/core/sandbox/provider/Provider.mjs.map +1 -1
  66. package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts +17 -2
  67. package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts.map +1 -1
  68. package/dist/core/sandbox/provider/TFYSandboxProvider.js +81 -17
  69. package/dist/core/sandbox/provider/TFYSandboxProvider.js.map +1 -1
  70. package/dist/core/sandbox/provider/TFYSandboxProvider.mjs +82 -13
  71. package/dist/core/sandbox/provider/TFYSandboxProvider.mjs.map +1 -1
  72. package/dist/core/sandbox/provider/execEnv.d.ts +6 -0
  73. package/dist/core/sandbox/provider/execEnv.d.ts.map +1 -0
  74. package/dist/core/sandbox/provider/execEnv.js +55 -0
  75. package/dist/core/sandbox/provider/execEnv.js.map +1 -0
  76. package/dist/core/sandbox/provider/execEnv.mjs +30 -0
  77. package/dist/core/sandbox/provider/execEnv.mjs.map +1 -0
  78. package/dist/core/sandbox/sandboxImage.d.ts +2 -5
  79. package/dist/core/sandbox/sandboxImage.d.ts.map +1 -1
  80. package/dist/core/sandbox/sandboxImage.js +8 -1
  81. package/dist/core/sandbox/sandboxImage.js.map +1 -1
  82. package/dist/core/sandbox/sandboxImage.mjs +6 -1
  83. package/dist/core/sandbox/sandboxImage.mjs.map +1 -1
  84. package/dist/core/sandbox/sandboxRef.d.ts +26 -0
  85. package/dist/core/sandbox/sandboxRef.d.ts.map +1 -0
  86. package/dist/core/sandbox/sandboxRef.js +69 -0
  87. package/dist/core/sandbox/sandboxRef.js.map +1 -0
  88. package/dist/core/sandbox/sandboxRef.mjs +41 -0
  89. package/dist/core/sandbox/sandboxRef.mjs.map +1 -0
  90. package/dist/core/sandbox/sandboxScripts.gen.d.ts +1 -1
  91. package/dist/core/sandbox/sandboxScripts.gen.d.ts.map +1 -1
  92. package/dist/core/sandbox/sandboxScripts.gen.js +1 -1
  93. package/dist/core/sandbox/sandboxScripts.gen.js.map +1 -1
  94. package/dist/core/sandbox/sandboxScripts.gen.mjs +1 -1
  95. package/dist/core/sandbox/sandboxScripts.gen.mjs.map +1 -1
  96. package/dist/core/sandbox/skills/ISkillMounter.d.ts +7 -2
  97. package/dist/core/sandbox/skills/ISkillMounter.d.ts.map +1 -1
  98. package/dist/core/sandbox/skills/ISkillMounter.js.map +1 -1
  99. package/dist/core/sandbox/skills/SkillMounter.d.ts +7 -2
  100. package/dist/core/sandbox/skills/SkillMounter.d.ts.map +1 -1
  101. package/dist/core/sandbox/skills/SkillMounter.js +5 -6
  102. package/dist/core/sandbox/skills/SkillMounter.js.map +1 -1
  103. package/dist/core/sandbox/skills/SkillMounter.mjs +5 -6
  104. package/dist/core/sandbox/skills/SkillMounter.mjs.map +1 -1
  105. package/dist/core/sandbox/skills/constants.d.ts +4 -2
  106. package/dist/core/sandbox/skills/constants.d.ts.map +1 -1
  107. package/dist/core/sandbox/skills/constants.js +2 -5
  108. package/dist/core/sandbox/skills/constants.js.map +1 -1
  109. package/dist/core/sandbox/skills/constants.mjs +2 -4
  110. package/dist/core/sandbox/skills/constants.mjs.map +1 -1
  111. package/dist/core/sandbox/skills/index.d.ts +1 -1
  112. package/dist/core/sandbox/skills/index.d.ts.map +1 -1
  113. package/dist/core/sandbox/skills/index.js +0 -2
  114. package/dist/core/sandbox/skills/index.js.map +1 -1
  115. package/dist/core/sandbox/skills/index.mjs +1 -2
  116. package/dist/core/sandbox/skills/index.mjs.map +1 -1
  117. package/package.json +1 -1
@@ -14,29 +14,36 @@ import {
14
14
  } from "../runtime/DeferredTool.mjs";
15
15
  import { extractErrorLogFields } from "../util/errorLogFields.mjs";
16
16
  import { CodeModeDispatcher } from "./codeMode/CodeModeDispatcher.mjs";
17
- import { SANDBOX_FILE_UPLOADS_DIR } from "./constants.mjs";
17
+ import "./codeMode/CodeModeTransport.mjs";
18
18
  import { ensureExecSuccess, shellEscape } from "./provider/Provider.mjs";
19
- import { validateNoPathTraversal, validateSandboxOwnedByTenant } from "./SandboxErrors.mjs";
20
- import { sandboxScripts } from "./sandboxScripts.gen.mjs";
21
- import { SKILLS_DIR } from "./skills/constants.mjs";
19
+ import { SandboxNotAvailableError, validateNoPathTraversal } from "./SandboxErrors.mjs";
20
+ import { formatSandboxId, rawSandboxId } from "./sandboxRef.mjs";
21
+ import { dirname, join, relative } from "path";
22
+ function mcpClientLayout(remotePath) {
23
+ const pythonPath = dirname(remotePath);
24
+ const binDir = join(pythonPath, "bin");
25
+ return { pythonPath, binDir, binLink: join(binDir, "mcp-client") };
26
+ }
22
27
  var SKILL_DOWNLOAD_TIMEOUT_SECONDS = 180;
23
28
  var NATS_REQUEST_TIMEOUT_BUFFER_SECONDS = 30;
24
29
  function buildWriteAndRunScriptCommand(params) {
25
30
  const b64 = Buffer.from(params.scriptContent, "utf-8").toString("base64");
31
+ const scriptPath = shellEscape(params.scriptPath);
26
32
  return [
27
- `mkdir -p "$(dirname ${params.scriptPath})"`,
28
- `rm -f ${params.scriptPath}`,
29
- `echo ${shellEscape(b64)} | base64 -d > ${params.scriptPath}`,
30
- `chmod a-w ${params.scriptPath}`,
31
- `python3 ${params.scriptPath}`
33
+ `mkdir -p "$(dirname ${scriptPath})"`,
34
+ `rm -f ${scriptPath}`,
35
+ `echo ${shellEscape(b64)} | base64 -d > ${scriptPath}`,
36
+ `chmod a-w ${scriptPath}`,
37
+ `python3 ${scriptPath}`
32
38
  ].join(" && ");
33
39
  }
34
40
  function buildSyncGitCredentialsCommand(credentialsContent, credentialsPath) {
41
+ const path = shellEscape(credentialsPath);
35
42
  if (credentialsContent === null) {
36
- return `rm -f ${credentialsPath}`;
43
+ return `rm -f ${path}`;
37
44
  }
38
45
  const b64 = Buffer.from(credentialsContent, "utf-8").toString("base64");
39
- return `mkdir -p "$(dirname ${credentialsPath})" && echo '${b64}' | base64 -d > ${credentialsPath} && chmod 600 ${credentialsPath}`;
46
+ return `mkdir -p "$(dirname ${path})" && echo '${b64}' | base64 -d > ${path} && chmod 600 ${path}`;
40
47
  }
41
48
  function buildGitCredentialHelperEnv(credentialsPath) {
42
49
  return {
@@ -50,9 +57,6 @@ var SANDBOX_MCP_REMINDER_TAG = "sandbox-mcp-code-mode";
50
57
  var SANDBOX_FILE_OUTPUT_TAG = "sandbox-file-output";
51
58
  var SANDBOX_SCHEMA_INFER_TAG = "sandbox-schema-infer";
52
59
  var SANDBOX_FILE_UPLOADS_TAG = "sandbox-file-uploads";
53
- var MCP_CLIENT_DIR = "/opt/tfy/mcp-client";
54
- var MCP_CLIENT_PATH = `${MCP_CLIENT_DIR}/mcp_client.py`;
55
- var MCP_CLIENT_BIN_SYMLINK = "/usr/local/bin/mcp-client";
56
60
  function injectTraceContextEnv() {
57
61
  const carrier = {};
58
62
  propagation.inject(context.active(), carrier);
@@ -68,10 +72,13 @@ function injectTraceContextEnv() {
68
72
  function injectMCPClientEnv(params) {
69
73
  const codeModeEnv = params.codeModeEnv ?? {};
70
74
  const hasCodeModeTransport = Object.keys(codeModeEnv).length > 0;
75
+ const install = params.mcpClientInstall;
76
+ const layout = install === void 0 ? void 0 : mcpClientLayout(install.remotePath);
71
77
  return {
72
- ...params.execExtraEnv ?? {},
73
78
  ...params.env ?? {},
74
- PYTHONPATH: MCP_CLIENT_DIR,
79
+ ...layout !== void 0 && {
80
+ PYTHONPATH: layout.pythonPath
81
+ },
75
82
  ...Object.keys(params.mcpServers).length > 0 && {
76
83
  TFY_MCP_SERVERS: Buffer.from(JSON.stringify(params.mcpServers)).toString("base64")
77
84
  },
@@ -103,14 +110,12 @@ function createSandboxLargeToolResponseGuidance() {
103
110
  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}>.`;
104
111
  }
105
112
  var SANDBOX_MCP_SERVER_ID = "sandbox";
106
- var Sandbox = class _Sandbox extends LocalToolMCP {
113
+ var Sandbox = class extends LocalToolMCP {
107
114
  name = SANDBOX_MCP_SERVER_ID;
108
115
  displayName = "Sandbox";
109
116
  description = "Persistent sandbox environment for code execution";
110
- static FILE_UPLOADS_DIR = SANDBOX_FILE_UPLOADS_DIR;
111
117
  provider;
112
118
  existingSandboxId;
113
- tenantName;
114
119
  existingSandboxInfo;
115
120
  // Cached promise to prevent concurrent sub-agents from creating duplicate sandboxes.
116
121
  sandboxCreationPromise;
@@ -118,15 +123,15 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
118
123
  codeExecToolSets = [];
119
124
  fileDownloadEnabled;
120
125
  requestTimeoutSeconds;
121
- execExtraEnv;
122
126
  skillMounter;
123
127
  cachedSkillsSection;
124
- mcpClientScriptBase64;
125
128
  logger;
126
129
  // Pre-resolved credential-store file content (null = clear / no git auth).
127
130
  resolvedGitCredentialsContent;
128
131
  codeModeDispatcher;
129
132
  codeModeTransport;
133
+ /** Cached from transport.getClientInstall after sandbox init (when Code Mode is configured). */
134
+ mcpClientInstall;
130
135
  tools = [
131
136
  defineTool({
132
137
  name: SANDBOX_EXEC_TOOL_NAME,
@@ -139,20 +144,24 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
139
144
  super({ tracing: options.tracing });
140
145
  this.provider = options.provider;
141
146
  this.existingSandboxId = options.existingSandboxId;
142
- this.tenantName = options.execExtraEnv?.["TFY_TENANT_NAME"] ?? "";
143
147
  this.skillMounter = options.skillMounter;
144
148
  this.fileDownloadEnabled = options.fileDownloadEnabled ?? false;
145
149
  const mcpBoundTimeoutMs = options.mcpRequestTimeoutMs + options.mcpConnectTimeoutMs;
146
150
  this.requestTimeoutSeconds = Math.ceil(mcpBoundTimeoutMs / 1e3) + NATS_REQUEST_TIMEOUT_BUFFER_SECONDS;
147
- this.execExtraEnv = options.execExtraEnv;
148
- this.mcpClientScriptBase64 = Buffer.from(sandboxScripts.mcpClient, "utf-8").toString("base64");
149
151
  this.logger = options.logger.child({ module: "Sandbox" });
150
152
  this.resolvedGitCredentialsContent = options.resolvedGitCredentialsContent ?? null;
151
153
  if (this.existingSandboxId) {
152
- validateSandboxOwnedByTenant(this.existingSandboxId, this.tenantName);
153
154
  this.existingSandboxInfo = { sandbox_id: this.existingSandboxId };
154
155
  }
155
156
  }
157
+ /** Provider-facing id (unwraps `v1:type:raw`; legacy ids pass through). */
158
+ providerSandboxId(sessionId) {
159
+ return rawSandboxId(sessionId);
160
+ }
161
+ /** Raw id for prompt layout paths before create (empty when this turn has no existing sandbox). */
162
+ promptSandboxId() {
163
+ return this.existingSandboxId === void 0 ? "" : rawSandboxId(this.existingSandboxId);
164
+ }
156
165
  buildMcpServersEnvelope() {
157
166
  const out = {};
158
167
  for (const server of this.codeExecToolSets) {
@@ -182,7 +191,7 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
182
191
  renderSkillsSection() {
183
192
  if (this.cachedSkillsSection === void 0) {
184
193
  const skills = new InstructionBuilder("skills");
185
- this.skillMounter?.instruction(skills);
194
+ this.skillMounter?.instruction(skills, { skillsDir: this.provider.getSkillsDir(this.promptSandboxId()) });
186
195
  this.cachedSkillsSection = skills.build();
187
196
  }
188
197
  return this.cachedSkillsSection;
@@ -206,7 +215,7 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
206
215
  buildFileUploadsSection(builder) {
207
216
  builder.addSection(
208
217
  SANDBOX_FILE_UPLOADS_TAG,
209
- `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.`
218
+ `User-uploaded files are placed in ${this.provider.getFileUploadsDir(this.promptSandboxId())}/. The Agent must not modify files in this directory. Always create a copy at a different location and work on the copy.`
210
219
  );
211
220
  }
212
221
  buildSchemaSection(builder) {
@@ -328,87 +337,168 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
328
337
  }
329
338
  return this.existingSandboxInfo;
330
339
  }
340
+ clearExistingSandbox() {
341
+ this.existingSandboxInfo = void 0;
342
+ this.sandboxCreationPromise = void 0;
343
+ this.sandboxInitPromise = void 0;
344
+ this.mcpClientInstall = void 0;
345
+ }
331
346
  async ensureSandboxCreated() {
332
347
  if (this.existingSandboxInfo) {
333
348
  return { sandboxInfo: this.existingSandboxInfo, sandboxCreated: void 0 };
334
349
  }
335
- this.sandboxCreationPromise ??= this.provider.createSandbox().then(({ sandboxId }) => {
336
- validateSandboxOwnedByTenant(sandboxId, this.tenantName);
337
- return { sandbox_id: sandboxId };
338
- }).catch((e) => {
350
+ this.sandboxCreationPromise ??= this.provider.createSandbox().then(({ sandboxId }) => ({
351
+ sandbox_id: formatSandboxId({ providerType: this.provider.type, rawId: sandboxId })
352
+ })).catch((e) => {
339
353
  this.sandboxCreationPromise = void 0;
340
354
  throw e;
341
355
  });
342
356
  const sandboxInfo = await this.sandboxCreationPromise;
343
- const prior = this.existingSandboxInfo;
344
- const isNew = prior === void 0;
357
+ const raced = this.peekExistingSandboxInfo();
358
+ if (raced !== void 0) {
359
+ return { sandboxInfo: raced, sandboxCreated: void 0 };
360
+ }
345
361
  this.existingSandboxInfo = sandboxInfo;
346
- return { sandboxInfo, sandboxCreated: isNew ? sandboxInfo : void 0 };
362
+ return { sandboxInfo, sandboxCreated: sandboxInfo };
347
363
  }
348
- async handleExec(input) {
349
- const { sandboxInfo, sandboxCreated } = await this.ensureSandboxCreated();
350
- const sandboxCreatedFlag = Boolean(sandboxCreated);
364
+ peekExistingSandboxInfo() {
365
+ return this.existingSandboxInfo;
366
+ }
367
+ /** Same-type missing remote/local root: drop the stale id and create a new fancy id. */
368
+ async recreateAfterUnavailable() {
369
+ this.clearExistingSandbox();
370
+ const created = await this.ensureSandboxCreated();
371
+ if (created.sandboxCreated === void 0) {
372
+ throw new Error("Sandbox recreate did not create a new sandbox");
373
+ }
374
+ return { sandboxInfo: created.sandboxInfo, sandboxCreated: created.sandboxCreated };
375
+ }
376
+ /**
377
+ * Create or reattach, then init. A missing same-type sandbox throws
378
+ * `SandboxNotAvailableError` from the provider; reattach then recreates.
379
+ */
380
+ async ensureReadySandbox() {
381
+ const first = await this.ensureSandboxCreated();
351
382
  try {
352
383
  await this.ensureSandboxInitialized();
384
+ return first;
385
+ } catch (error) {
386
+ if (!(error instanceof SandboxNotAvailableError) || first.sandboxCreated !== void 0) {
387
+ throw error;
388
+ }
389
+ const recreated = await this.recreateAfterUnavailable();
390
+ await this.ensureSandboxInitialized();
391
+ return recreated;
392
+ }
393
+ }
394
+ async handleExec(input) {
395
+ let sandboxInfo;
396
+ let sandboxCreated;
397
+ try {
398
+ ({ sandboxInfo, sandboxCreated } = await this.ensureReadySandbox());
353
399
  } catch (e) {
354
400
  this.logger.error("Sandbox initialization failed", extractErrorLogFields(e));
355
401
  const message = e instanceof Error ? e.message : "Sandbox initialization failed";
402
+ const fallback = this.existingSandboxInfo;
356
403
  return toolResultResponse({
357
404
  text: `Sandbox initialization failed: ${message}`,
358
405
  isError: true,
359
- overrides: { sandboxCreated: sandboxCreatedFlag, sandboxInfo }
406
+ overrides: {
407
+ sandboxCreated: Boolean(fallback),
408
+ ...fallback !== void 0 ? { sandboxInfo: fallback } : {}
409
+ }
360
410
  });
361
411
  }
362
- const codeModeEnv = await this.ensureCodeModeStarted(sandboxInfo.sandbox_id);
412
+ const sandboxCreatedFlag = Boolean(sandboxCreated);
413
+ const rawId = this.providerSandboxId(sandboxInfo.sandbox_id);
414
+ const codeModeEnv = await this.ensureCodeModeStarted(rawId);
363
415
  const mcpClientEnv = injectMCPClientEnv({
364
416
  env: input.env,
365
417
  mcpServers: this.buildMcpServersEnvelope(),
366
- execExtraEnv: this.execExtraEnv,
367
- codeModeEnv
418
+ codeModeEnv,
419
+ mcpClientInstall: this.mcpClientInstall
368
420
  });
369
- const gitAuthEnv = this.resolvedGitCredentialsContent !== null ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(sandboxInfo.sandbox_id)) : {};
421
+ const gitAuthEnv = this.resolvedGitCredentialsContent !== null ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(rawId)) : {};
370
422
  const env = { ...mcpClientEnv, ...gitAuthEnv };
371
- const result = await this.provider.exec({
372
- sandboxId: sandboxInfo.sandbox_id,
373
- command: input.command,
374
- cwd: input.cwd,
375
- env
376
- });
377
- return {
378
- result: {
379
- content: [{ type: "text", text: JSON.stringify(result) }],
380
- isError: !result.success
381
- },
382
- wasInitialized: void 0,
383
- sandboxCreated: sandboxCreatedFlag,
384
- sandboxInfo
385
- };
423
+ try {
424
+ const result = await this.provider.exec({
425
+ sandboxId: rawId,
426
+ command: input.command,
427
+ cwd: input.cwd,
428
+ env
429
+ });
430
+ return {
431
+ result: {
432
+ content: [{ type: "text", text: JSON.stringify(result) }],
433
+ isError: !result.success
434
+ },
435
+ wasInitialized: void 0,
436
+ sandboxCreated: sandboxCreatedFlag,
437
+ sandboxInfo
438
+ };
439
+ } catch (error) {
440
+ if (!(error instanceof SandboxNotAvailableError) || sandboxCreated !== void 0) {
441
+ throw error;
442
+ }
443
+ const recreated = await this.recreateAfterUnavailable();
444
+ await this.ensureSandboxInitialized();
445
+ const recreatedRawId = this.providerSandboxId(recreated.sandboxInfo.sandbox_id);
446
+ const retryCodeModeEnv = await this.ensureCodeModeStarted(recreatedRawId);
447
+ const retryEnv = {
448
+ ...injectMCPClientEnv({
449
+ env: input.env,
450
+ mcpServers: this.buildMcpServersEnvelope(),
451
+ codeModeEnv: retryCodeModeEnv,
452
+ mcpClientInstall: this.mcpClientInstall
453
+ }),
454
+ ...this.resolvedGitCredentialsContent !== null ? buildGitCredentialHelperEnv(this.provider.getGitCredentialsPath(recreatedRawId)) : {}
455
+ };
456
+ const retryResult = await this.provider.exec({
457
+ sandboxId: recreatedRawId,
458
+ command: input.command,
459
+ cwd: input.cwd,
460
+ env: retryEnv
461
+ });
462
+ return {
463
+ result: {
464
+ content: [{ type: "text", text: JSON.stringify(retryResult) }],
465
+ isError: !retryResult.success
466
+ },
467
+ wasInitialized: void 0,
468
+ sandboxCreated: true,
469
+ sandboxInfo: recreated.sandboxInfo
470
+ };
471
+ }
386
472
  }
387
473
  async getToolResultDumpDir() {
388
- const { sandboxCreated } = await this.ensureSandboxCreated();
389
- const dir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id).replace(/\/+$/, "");
474
+ const { sandboxCreated, sandboxInfo } = await this.ensureSandboxCreated();
475
+ const dir = this.provider.getToolResultDumpDir(this.providerSandboxId(sandboxInfo.sandbox_id)).replace(/\/+$/, "");
390
476
  return { dir, sandboxCreated };
391
477
  }
392
478
  async uploadFile(params) {
393
- const { sandboxCreated } = await this.ensureSandboxCreated();
394
- await this.ensureSandboxInitialized();
395
- const { sandbox_id: sandboxId } = this.requiredSandboxInfo;
479
+ const { sandboxCreated, sandboxInfo } = await this.ensureReadySandbox();
396
480
  const fileName = params.fileName.replace(/^\/+/, "");
397
481
  const targetDir = params.targetDir.replace(/\/+$/, "");
398
482
  const filePath = `${targetDir}/${fileName}`;
399
- await this.provider.uploadFile({ sandboxId, remotePath: filePath, content: params.content });
483
+ await this.provider.uploadFile({
484
+ sandboxId: this.providerSandboxId(sandboxInfo.sandbox_id),
485
+ remotePath: filePath,
486
+ content: params.content
487
+ });
400
488
  return { filePath, sandboxCreated };
401
489
  }
402
490
  async uploadUserFile(input) {
403
491
  validateNoPathTraversal(input.fileName);
492
+ const { sandboxCreated, sandboxInfo } = await this.ensureReadySandbox();
404
493
  const result = await this.uploadFile({
405
- targetDir: _Sandbox.FILE_UPLOADS_DIR,
494
+ targetDir: this.provider.getFileUploadsDir(this.providerSandboxId(sandboxInfo.sandbox_id)),
406
495
  fileName: input.fileName,
407
496
  content: input.content
408
497
  });
498
+ const created = sandboxCreated ?? result.sandboxCreated;
409
499
  return {
410
500
  filePath: result.filePath,
411
- ...result.sandboxCreated && { sandboxCreated: result.sandboxCreated }
501
+ ...created && { sandboxCreated: created }
412
502
  };
413
503
  }
414
504
  async writeArtifact(input) {
@@ -437,7 +527,7 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
437
527
  await this.sandboxInitPromise;
438
528
  }
439
529
  async writeGitCredentials() {
440
- const sandboxId = this.requiredSandboxInfo.sandbox_id;
530
+ const sandboxId = this.providerSandboxId(this.requiredSandboxInfo.sandbox_id);
441
531
  const credentialsPath = this.provider.getGitCredentialsPath(sandboxId);
442
532
  const result = await this.provider.exec({
443
533
  sandboxId,
@@ -446,30 +536,56 @@ var Sandbox = class _Sandbox extends LocalToolMCP {
446
536
  ensureExecSuccess(result);
447
537
  }
448
538
  async initSandboxEnvironment() {
449
- const toolResultDumpDir = this.provider.getToolResultDumpDir(this.requiredSandboxInfo.sandbox_id);
539
+ const sandboxId = this.providerSandboxId(this.requiredSandboxInfo.sandbox_id);
540
+ const fileUploadsDir = this.provider.getFileUploadsDir(sandboxId);
541
+ const skillsDir = this.provider.getSkillsDir(sandboxId);
542
+ const toolResultDumpDir = this.provider.getToolResultDumpDir(sandboxId);
450
543
  this.logger.info("Uploading MCP client script and preparing skills directory in sandbox");
451
- const initSteps = [
452
- `mkdir -p ${MCP_CLIENT_DIR} ${_Sandbox.FILE_UPLOADS_DIR} ${toolResultDumpDir} ${SKILLS_DIR}`,
453
- `rm -f ${MCP_CLIENT_PATH}`,
454
- `echo '${this.mcpClientScriptBase64}' | base64 -d > ${MCP_CLIENT_PATH}`,
455
- // Make executable + symlink onto PATH so `mcp-client` runs by name.
456
- `chmod 0555 ${MCP_CLIENT_PATH}`,
457
- `ln -sf ${MCP_CLIENT_PATH} ${MCP_CLIENT_BIN_SYMLINK}`
458
- ];
459
- const skillInit = this.skillMounter?.getSandboxInit();
544
+ this.mcpClientInstall = this.codeModeTransport?.getClientInstall({ sandboxId });
545
+ const install = this.mcpClientInstall;
546
+ const dirs = [shellEscape(fileUploadsDir), shellEscape(toolResultDumpDir), shellEscape(skillsDir)];
547
+ if (install !== void 0) {
548
+ const { pythonPath, binDir } = mcpClientLayout(install.remotePath);
549
+ dirs.push(shellEscape(pythonPath));
550
+ if (install.pathBinSymlink === void 0) {
551
+ dirs.push(shellEscape(binDir));
552
+ }
553
+ }
554
+ ensureExecSuccess(await this.provider.exec({ sandboxId, command: `mkdir -p ${dirs.join(" ")}` }));
555
+ const initSteps = [];
556
+ if (install !== void 0) {
557
+ await this.provider.uploadFile({
558
+ sandboxId,
559
+ remotePath: install.remotePath,
560
+ content: Buffer.from(install.content, "utf-8")
561
+ });
562
+ initSteps.push(`chmod 0555 ${shellEscape(install.remotePath)}`);
563
+ if (install.pathBinSymlink !== void 0) {
564
+ initSteps.push(`ln -sf ${shellEscape(install.remotePath)} ${shellEscape(install.pathBinSymlink)}`);
565
+ } else {
566
+ const { binDir, binLink } = mcpClientLayout(install.remotePath);
567
+ initSteps.push(`ln -sf ${shellEscape(relative(binDir, install.remotePath))} ${shellEscape(binLink)}`);
568
+ }
569
+ }
570
+ const skillInit = this.skillMounter?.getSandboxInit({
571
+ skillsDir,
572
+ gitDownloaderPath: this.provider.getGitDownloaderPath(sandboxId)
573
+ });
460
574
  if (skillInit) {
461
575
  initSteps.push(skillInit.command);
462
576
  }
463
- const script = initSteps.join(" && ");
464
- const result = await this.provider.exec({
465
- sandboxId: this.requiredSandboxInfo.sandbox_id,
466
- command: script,
467
- env: skillInit?.env,
468
- timeoutSeconds: skillInit?.timeoutSeconds
469
- });
470
- ensureExecSuccess(result);
577
+ if (initSteps.length > 0) {
578
+ ensureExecSuccess(
579
+ await this.provider.exec({
580
+ sandboxId,
581
+ command: initSteps.join(" && "),
582
+ env: skillInit?.env,
583
+ timeoutSeconds: skillInit?.timeoutSeconds
584
+ })
585
+ );
586
+ }
471
587
  this.logger.info(
472
- `Sandbox initialized: MCP client at ${MCP_CLIENT_PATH} (symlinked to ${MCP_CLIENT_BIN_SYMLINK}); skills dir ${SKILLS_DIR}`
588
+ install === void 0 ? `Sandbox initialized: skills dir ${skillsDir}` : `Sandbox initialized: MCP client at ${install.remotePath}; skills dir ${skillsDir}`
473
589
  );
474
590
  await this.writeGitCredentials();
475
591
  }