@truefoundry/trueforge-core 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) 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 -4
  30. package/dist/core/sandbox/Sandbox.d.ts.map +1 -1
  31. package/dist/core/sandbox/Sandbox.js +200 -80
  32. package/dist/core/sandbox/Sandbox.js.map +1 -1
  33. package/dist/core/sandbox/Sandbox.mjs +201 -81
  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/skills/ISkillMounter.d.ts +7 -2
  91. package/dist/core/sandbox/skills/ISkillMounter.d.ts.map +1 -1
  92. package/dist/core/sandbox/skills/ISkillMounter.js.map +1 -1
  93. package/dist/core/sandbox/skills/SkillMounter.d.ts +7 -2
  94. package/dist/core/sandbox/skills/SkillMounter.d.ts.map +1 -1
  95. package/dist/core/sandbox/skills/SkillMounter.js +5 -6
  96. package/dist/core/sandbox/skills/SkillMounter.js.map +1 -1
  97. package/dist/core/sandbox/skills/SkillMounter.mjs +5 -6
  98. package/dist/core/sandbox/skills/SkillMounter.mjs.map +1 -1
  99. package/dist/core/sandbox/skills/constants.d.ts +4 -2
  100. package/dist/core/sandbox/skills/constants.d.ts.map +1 -1
  101. package/dist/core/sandbox/skills/constants.js +2 -5
  102. package/dist/core/sandbox/skills/constants.js.map +1 -1
  103. package/dist/core/sandbox/skills/constants.mjs +2 -4
  104. package/dist/core/sandbox/skills/constants.mjs.map +1 -1
  105. package/dist/core/sandbox/skills/index.d.ts +1 -1
  106. package/dist/core/sandbox/skills/index.d.ts.map +1 -1
  107. package/dist/core/sandbox/skills/index.js +0 -2
  108. package/dist/core/sandbox/skills/index.js.map +1 -1
  109. package/dist/core/sandbox/skills/index.mjs +1 -2
  110. package/dist/core/sandbox/skills/index.mjs.map +1 -1
  111. package/package.json +1 -1
@@ -46,6 +46,8 @@ export interface SandboxBuild {
46
46
  metadata: SandboxBuildMetadata | null;
47
47
  }
48
48
  export interface SandboxProvider {
49
+ /** Stable provider kind used in fancy sandbox ids and carry-forward (plain string). */
50
+ readonly type: string;
49
51
  /**
50
52
  * Ensures the release image is being built into the provider's backing store and
51
53
  * returns its current status. Idempotent: an already-built image reports `ready`;
@@ -62,8 +64,18 @@ export interface SandboxProvider {
62
64
  getAdditionalInstructions(): string | undefined;
63
65
  /** Directory inside the sandbox where large tool responses are dumped. */
64
66
  getToolResultDumpDir(sandboxId: string): string;
65
- /** Absolute path for the git credential-store file (per logical sandbox when sharing a pod). */
67
+ /**
68
+ * Git credential-store file (absolute, or cwd-relative when exec cwd is the jail).
69
+ * `GIT_CONFIG` `store --file` is relative to the git process cwd — providers that
70
+ * return a relative path must make it absolute in their own `exec`.
71
+ */
66
72
  getGitCredentialsPath(sandboxId: string): string;
73
+ /** Directory for user-uploaded files (absolute, or cwd-relative when the provider has no global FS). */
74
+ getFileUploadsDir(sandboxId: string): string;
75
+ /** Directory where git skills are materialized. */
76
+ getSkillsDir(sandboxId: string): string;
77
+ /** Path the git skill downloader script is written to before it runs. */
78
+ getGitDownloaderPath(sandboxId: string): string;
67
79
  /** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */
68
80
  downloadFile(params: {
69
81
  sandboxId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"Provider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/Provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAEvE,2FAA2F;AAC3F,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,IAAI,CAAC;IACd,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAChD;AAED,6FAA6F;AAC7F,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,UAAU,GAAG,iBAAiB,GAAG,eAAe,CAAC;AAE7D;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAO1D;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;IACzC,qFAAqF;IACrF,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACrC;AAGD,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC,iBAAiB,EAAE,WAAW,CAAC,CAAC;AAE/D,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEhE,4DAA4D;AAC5D,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1D,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,yFAAyF;IACzF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,wEAAwE;IACxE,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;CACvC;AAED,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IACpC,qFAAqF;IACrF,mBAAmB,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7C,aAAa,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACrD,0EAA0E;IAC1E,yBAAyB,IAAI,MAAM,GAAG,SAAS,CAAC;IAChD,0EAA0E;IAC1E,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAChD,gGAAgG;IAChG,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IACjD,0KAA0K;IAC1K,YAAY,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC3E,qCAAqC;IACrC,UAAU,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9F;;;OAGG;IACH,uBAAuB,IAAI,iBAAiB,CAAC;CAC9C"}
1
+ {"version":3,"file":"Provider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/Provider.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n// An init command a producer (e.g. a skill mounter) hands to the Sandbox, which supplies `sandboxId`.\nexport type SandboxInit = Omit<SandboxExecParams, 'sandboxId'>;\n\nexport type SandboxBuildStatus = 'pending' | 'ready' | 'failed';\n\n/** Provider-specific opaque build metadata (string map). */\nexport type SandboxBuildMetadata = Record<string, string>;\n\nexport interface SandboxBuild {\n status: SandboxBuildStatus;\n /** Human-readable detail for the current status (shown to the user); null when ready. */\n reason: string | null;\n /** Provider-specific build details; null when the provider has none. */\n metadata: SandboxBuildMetadata | null;\n}\n\nexport interface SandboxProvider {\n /**\n * Ensures the release image is being built into the provider's backing store and\n * returns its current status. Idempotent: an already-built image reports `ready`;\n * a fresh build starts in the background and reports `pending`.\n */\n buildImage(): Promise<SandboxBuild>;\n /** Current build status of the release image. Read-only: never kicks off a build. */\n getImageBuildStatus(): Promise<SandboxBuild>;\n createSandbox(): Promise<{ sandboxId: string }>;\n exec(params: SandboxExecParams): Promise<ExecResult>;\n /** Provider-specific instructions appended to the agent system prompt. */\n getAdditionalInstructions(): string | undefined;\n /** Directory inside the sandbox where large tool responses are dumped. */\n getToolResultDumpDir(sandboxId: string): string;\n /** Absolute path for the git credential-store file (per logical sandbox when sharing a pod). */\n getGitCredentialsPath(sandboxId: string): string;\n /** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */\n downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer>;\n /** Uploads a file to the sandbox. */\n uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void>;\n\n /**\n * Construct a Code Mode transport for this provider (no connect/listen yet).\n * Throws if the provider does not support Code Mode.\n */\n createCodeModeTransport(): CodeModeTransport;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBO,SAAS,YAAY,OAAuB;AACjD,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAGO,SAAS,kBAAkB,QAA0B;AAC1D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,OAAO,KAAK;AAAA,EAC9B;AACA,MAAI,OAAO,SAAS,aAAa,GAAG;AAClC,UAAM,IAAI,MAAM,cAAc,OAAO,OAAO,SAAS,QAAQ,CAAC,MAAM,OAAO,SAAS,MAAM,EAAE;AAAA,EAC9F;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n// An init command a producer (e.g. a skill mounter) hands to the Sandbox, which supplies `sandboxId`.\nexport type SandboxInit = Omit<SandboxExecParams, 'sandboxId'>;\n\nexport type SandboxBuildStatus = 'pending' | 'ready' | 'failed';\n\n/** Provider-specific opaque build metadata (string map). */\nexport type SandboxBuildMetadata = Record<string, string>;\n\nexport interface SandboxBuild {\n status: SandboxBuildStatus;\n /** Human-readable detail for the current status (shown to the user); null when ready. */\n reason: string | null;\n /** Provider-specific build details; null when the provider has none. */\n metadata: SandboxBuildMetadata | null;\n}\n\nexport interface SandboxProvider {\n /** Stable provider kind used in fancy sandbox ids and carry-forward (plain string). */\n readonly type: string;\n /**\n * Ensures the release image is being built into the provider's backing store and\n * returns its current status. Idempotent: an already-built image reports `ready`;\n * a fresh build starts in the background and reports `pending`.\n */\n buildImage(): Promise<SandboxBuild>;\n /** Current build status of the release image. Read-only: never kicks off a build. */\n getImageBuildStatus(): Promise<SandboxBuild>;\n createSandbox(): Promise<{ sandboxId: string }>;\n exec(params: SandboxExecParams): Promise<ExecResult>;\n /** Provider-specific instructions appended to the agent system prompt. */\n getAdditionalInstructions(): string | undefined;\n /** Directory inside the sandbox where large tool responses are dumped. */\n getToolResultDumpDir(sandboxId: string): string;\n /**\n * Git credential-store file (absolute, or cwd-relative when exec cwd is the jail).\n * `GIT_CONFIG` `store --file` is relative to the git process cwd — providers that\n * return a relative path must make it absolute in their own `exec`.\n */\n getGitCredentialsPath(sandboxId: string): string;\n /** Directory for user-uploaded files (absolute, or cwd-relative when the provider has no global FS). */\n getFileUploadsDir(sandboxId: string): string;\n /** Directory where git skills are materialized. */\n getSkillsDir(sandboxId: string): string;\n /** Path the git skill downloader script is written to before it runs. */\n getGitDownloaderPath(sandboxId: string): string;\n /** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */\n downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer>;\n /** Uploads a file to the sandbox. */\n uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void>;\n\n /**\n * Construct a Code Mode transport for this provider (no connect/listen yet).\n * Throws if the provider does not support Code Mode.\n */\n createCodeModeTransport(): CodeModeTransport;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAqBO,SAAS,YAAY,OAAuB;AACjD,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAGO,SAAS,kBAAkB,QAA0B;AAC1D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,OAAO,KAAK;AAAA,EAC9B;AACA,MAAI,OAAO,SAAS,aAAa,GAAG;AAClC,UAAM,IAAI,MAAM,cAAc,OAAO,OAAO,SAAS,QAAQ,CAAC,MAAM,OAAO,SAAS,MAAM,EAAE;AAAA,EAC9F;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n// An init command a producer (e.g. a skill mounter) hands to the Sandbox, which supplies `sandboxId`.\nexport type SandboxInit = Omit<SandboxExecParams, 'sandboxId'>;\n\nexport type SandboxBuildStatus = 'pending' | 'ready' | 'failed';\n\n/** Provider-specific opaque build metadata (string map). */\nexport type SandboxBuildMetadata = Record<string, string>;\n\nexport interface SandboxBuild {\n status: SandboxBuildStatus;\n /** Human-readable detail for the current status (shown to the user); null when ready. */\n reason: string | null;\n /** Provider-specific build details; null when the provider has none. */\n metadata: SandboxBuildMetadata | null;\n}\n\nexport interface SandboxProvider {\n /**\n * Ensures the release image is being built into the provider's backing store and\n * returns its current status. Idempotent: an already-built image reports `ready`;\n * a fresh build starts in the background and reports `pending`.\n */\n buildImage(): Promise<SandboxBuild>;\n /** Current build status of the release image. Read-only: never kicks off a build. */\n getImageBuildStatus(): Promise<SandboxBuild>;\n createSandbox(): Promise<{ sandboxId: string }>;\n exec(params: SandboxExecParams): Promise<ExecResult>;\n /** Provider-specific instructions appended to the agent system prompt. */\n getAdditionalInstructions(): string | undefined;\n /** Directory inside the sandbox where large tool responses are dumped. */\n getToolResultDumpDir(sandboxId: string): string;\n /** Absolute path for the git credential-store file (per logical sandbox when sharing a pod). */\n getGitCredentialsPath(sandboxId: string): string;\n /** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */\n downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer>;\n /** Uploads a file to the sandbox. */\n uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void>;\n\n /**\n * Construct a Code Mode transport for this provider (no connect/listen yet).\n * Throws if the provider does not support Code Mode.\n */\n createCodeModeTransport(): CodeModeTransport;\n}\n"],"mappings":";AAqBO,SAAS,YAAY,OAAuB;AACjD,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAGO,SAAS,kBAAkB,QAA0B;AAC1D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,OAAO,KAAK;AAAA,EAC9B;AACA,MAAI,OAAO,SAAS,aAAa,GAAG;AAClC,UAAM,IAAI,MAAM,cAAc,OAAO,OAAO,SAAS,QAAQ,CAAC,MAAM,OAAO,SAAS,MAAM,EAAE;AAAA,EAC9F;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/core/sandbox/provider/Provider.ts"],"sourcesContent":["import type { CodeModeTransport } from '../codeMode/CodeModeTransport';\n\n/** Command executed in sandbox — exitCode may be non-zero but the infra call succeeded. */\nexport interface ExecSuccessResult {\n success: true;\n response: { exitCode: number; result: string };\n}\n\n/** Sandbox infrastructure failure — command never ran (e.g. network error, auth failure). */\nexport interface ExecErrorResult {\n success: false;\n error: string;\n}\n\nexport type ExecResult = ExecSuccessResult | ExecErrorResult;\n\n/**\n * Wraps a value in single quotes for safe use in a shell command. Inner single quotes are\n * escaped via the standard shell idiom: ' -> '\\''. Use for any user-controlled value that is\n * interpolated into an exec command string.\n */\nexport function shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\n/** Throws if the exec result indicates failure (infra error or non-zero exit code). */\nexport function ensureExecSuccess(result: ExecResult): void {\n if (!result.success) {\n throw new Error(result.error);\n }\n if (result.response.exitCode !== 0) {\n throw new Error(`(exit code ${String(result.response.exitCode)}): ${result.response.result}`);\n }\n}\n\nexport interface SandboxFileInfo {\n size: number;\n isDir: boolean;\n}\n\n/** Parameters for a single sandbox command. */\nexport interface SandboxExecParams {\n sandboxId: string;\n command: string;\n cwd?: string | undefined;\n env?: Record<string, string> | undefined;\n /** Overrides the provider's default exec timeout (e.g. for long skill downloads). */\n timeoutSeconds?: number | undefined;\n}\n\n// An init command a producer (e.g. a skill mounter) hands to the Sandbox, which supplies `sandboxId`.\nexport type SandboxInit = Omit<SandboxExecParams, 'sandboxId'>;\n\nexport type SandboxBuildStatus = 'pending' | 'ready' | 'failed';\n\n/** Provider-specific opaque build metadata (string map). */\nexport type SandboxBuildMetadata = Record<string, string>;\n\nexport interface SandboxBuild {\n status: SandboxBuildStatus;\n /** Human-readable detail for the current status (shown to the user); null when ready. */\n reason: string | null;\n /** Provider-specific build details; null when the provider has none. */\n metadata: SandboxBuildMetadata | null;\n}\n\nexport interface SandboxProvider {\n /** Stable provider kind used in fancy sandbox ids and carry-forward (plain string). */\n readonly type: string;\n /**\n * Ensures the release image is being built into the provider's backing store and\n * returns its current status. Idempotent: an already-built image reports `ready`;\n * a fresh build starts in the background and reports `pending`.\n */\n buildImage(): Promise<SandboxBuild>;\n /** Current build status of the release image. Read-only: never kicks off a build. */\n getImageBuildStatus(): Promise<SandboxBuild>;\n createSandbox(): Promise<{ sandboxId: string }>;\n exec(params: SandboxExecParams): Promise<ExecResult>;\n /** Provider-specific instructions appended to the agent system prompt. */\n getAdditionalInstructions(): string | undefined;\n /** Directory inside the sandbox where large tool responses are dumped. */\n getToolResultDumpDir(sandboxId: string): string;\n /**\n * Git credential-store file (absolute, or cwd-relative when exec cwd is the jail).\n * `GIT_CONFIG` `store --file` is relative to the git process cwd — providers that\n * return a relative path must make it absolute in their own `exec`.\n */\n getGitCredentialsPath(sandboxId: string): string;\n /** Directory for user-uploaded files (absolute, or cwd-relative when the provider has no global FS). */\n getFileUploadsDir(sandboxId: string): string;\n /** Directory where git skills are materialized. */\n getSkillsDir(sandboxId: string): string;\n /** Path the git skill downloader script is written to before it runs. */\n getGitDownloaderPath(sandboxId: string): string;\n /** Downloads a file from the sandbox as a Buffer. Throws SandboxFileNotFoundError / SandboxNotAvailableError / SandboxPathIsDirectoryError / SandboxFileTooLargeError. */\n downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer>;\n /** Uploads a file to the sandbox. */\n uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void>;\n\n /**\n * Construct a Code Mode transport for this provider (no connect/listen yet).\n * Throws if the provider does not support Code Mode.\n */\n createCodeModeTransport(): CodeModeTransport;\n}\n"],"mappings":";AAqBO,SAAS,YAAY,OAAuB;AACjD,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAGO,SAAS,kBAAkB,QAA0B;AAC1D,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,IAAI,MAAM,OAAO,KAAK;AAAA,EAC9B;AACA,MAAI,OAAO,SAAS,aAAa,GAAG;AAClC,UAAM,IAAI,MAAM,cAAc,OAAO,OAAO,SAAS,QAAQ,CAAC,MAAM,OAAO,SAAS,MAAM,EAAE;AAAA,EAC9F;AACF;","names":[]}
@@ -1,6 +1,8 @@
1
1
  import type { Logger } from 'winston';
2
2
  import type { CodeModeTransport } from '../codeMode/CodeModeTransport';
3
3
  import { type ExecResult, type SandboxBuild, type SandboxExecParams, type SandboxProvider } from './Provider';
4
+ /** Put the layout CLI first on PATH (cwd-relative; {@link absolutizeRelativeExecEnv} makes it absolute). */
5
+ export declare function withMcpClientOnPath(path: string): string;
4
6
  export interface TFYSandboxProviderOptions {
5
7
  serverUrl: string;
6
8
  natsBridgeUrl: string;
@@ -10,12 +12,15 @@ export interface TFYSandboxProviderOptions {
10
12
  logger: Logger;
11
13
  }
12
14
  export declare class TFYSandboxProvider implements SandboxProvider {
15
+ readonly type = "tfy";
13
16
  private readonly serverUrl;
14
17
  private readonly natsBridgeUrl;
15
18
  private readonly tenantName;
16
19
  private readonly fileMaxBytesForDownload;
17
20
  private readonly defaultExecTimeoutSeconds;
18
21
  private readonly logger;
22
+ /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */
23
+ private readonly sandboxLayouts;
19
24
  constructor(options: TFYSandboxProviderOptions);
20
25
  private static readonly readyBuild;
21
26
  buildImage(): Promise<SandboxBuild>;
@@ -23,7 +28,14 @@ export declare class TFYSandboxProvider implements SandboxProvider {
23
28
  createSandbox(): Promise<{
24
29
  sandboxId: string;
25
30
  }>;
31
+ /**
32
+ * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative
33
+ * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so
34
+ * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.
35
+ */
26
36
  exec(params: SandboxExecParams): Promise<ExecResult>;
37
+ private sandboxLayout;
38
+ private postExec;
27
39
  private getFileInfo;
28
40
  downloadFile(params: {
29
41
  sandboxId: string;
@@ -36,7 +48,10 @@ export declare class TFYSandboxProvider implements SandboxProvider {
36
48
  }): Promise<void>;
37
49
  createCodeModeTransport(): CodeModeTransport;
38
50
  getAdditionalInstructions(): string;
39
- getToolResultDumpDir(sandboxId: string): string;
40
- getGitCredentialsPath(sandboxId: string): string;
51
+ getToolResultDumpDir(): string;
52
+ getGitCredentialsPath(): string;
53
+ getFileUploadsDir(): string;
54
+ getSkillsDir(): string;
55
+ getGitDownloaderPath(): string;
41
56
  }
42
57
  //# sourceMappingURL=TFYSandboxProvider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TFYSandboxProvider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAIvE,OAAO,EAEL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAYpB,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD,qBAAa,kBAAmB,YAAW,eAAe;IACxD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,YAAY,OAAO,EAAE,yBAAyB,EAO7C;IAGD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAIhC;IAEF,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAElC;IAED,mBAAmB,IAAI,OAAO,CAAC,YAAY,CAAC,CAE3C;IAED,aAAa,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAI9C;IAEK,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CA+CzD;YAEa,WAAW;IAiBnB,YAAY,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB/E;IAEK,UAAU,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASlG;IAGD,uBAAuB,IAAI,iBAAiB,CAM3C;IAED,yBAAyB,IAAI,MAAM,CAOlC;IAED,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE9C;IAED,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE/C;CACF"}
1
+ {"version":3,"file":"TFYSandboxProvider.d.ts","sourceRoot":"","sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAUvE,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,iBAAiB,EAEtB,KAAK,eAAe,EACrB,MAAM,YAAY,CAAC;AAQpB,4GAA4G;AAC5G,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGxD;AAWD,MAAM,WAAW,yBAAyB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,MAAM,CAAC;IAChC,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAOD,qBAAa,kBAAmB,YAAW,eAAe;IACxD,QAAQ,CAAC,IAAI,SAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,sEAAsE;IACtE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA8D;IAE7F,YAAY,OAAO,EAAE,yBAAyB,EAO7C;IAGD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,UAAU,CAIhC;IAEF,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAElC;IAED,mBAAmB,IAAI,OAAO,CAAC,YAAY,CAAC,CAE3C;IAED,aAAa,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAI9C;IAED;;;;OAIG;IACG,IAAI,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC,CASzD;YAEa,aAAa;YAoBb,QAAQ;YAiDR,WAAW;IAiBnB,YAAY,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsB/E;IAEK,UAAU,CAAC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CASlG;IAGD,uBAAuB,IAAI,iBAAiB,CAO3C;IAED,yBAAyB,IAAI,MAAM,CAOlC;IAKD,oBAAoB,IAAI,MAAM,CAE7B;IAED,qBAAqB,IAAI,MAAM,CAE9B;IAED,iBAAiB,IAAI,MAAM,CAE1B;IAED,YAAY,IAAI,MAAM,CAErB;IAED,oBAAoB,IAAI,MAAM,CAE7B;CACF"}
@@ -30,30 +30,46 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/core/sandbox/provider/TFYSandboxProvider.ts
31
31
  var TFYSandboxProvider_exports = {};
32
32
  __export(TFYSandboxProvider_exports, {
33
- TFYSandboxProvider: () => TFYSandboxProvider
33
+ TFYSandboxProvider: () => TFYSandboxProvider,
34
+ withMcpClientOnPath: () => withMcpClientOnPath
34
35
  });
35
36
  module.exports = __toCommonJS(TFYSandboxProvider_exports);
36
37
  var import_api = require("@opentelemetry/api");
37
38
  var import_core = require("@opentelemetry/core");
38
- var import_crypto = require("crypto");
39
39
  var import_dedent = __toESM(require("dedent"), 1);
40
+ var import_node_crypto = require("crypto");
41
+ var import_posix = require("path/posix");
40
42
  var import_errorLogFields = require("../../util/errorLogFields.js");
41
43
  var import_CodeModeNatsTransport = require("../codeMode/nats/CodeModeNatsTransport.js");
42
44
  var import_constants = require("../constants.js");
43
45
  var import_SandboxErrors = require("../SandboxErrors.js");
46
+ var import_execEnv = require("./execEnv.js");
44
47
  var import_Provider = require("./Provider.js");
45
48
  var DEFAULT_TIMEOUT_SECONDS = 60;
46
49
  var CLIENT_TIMEOUT_BUFFER_SECONDS = 5;
47
- function shellEscape(value) {
48
- return `'${value.replace(/'/g, "'\\''")}'`;
50
+ var TFY_MCP_CLIENT_BIN = "mcp-client/bin";
51
+ function withMcpClientOnPath(path) {
52
+ const rest = path.split(":").filter((part) => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);
53
+ return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(":")}`;
54
+ }
55
+ function parsePwdAndPath(text) {
56
+ const lines = text.replace(/\r\n/g, "\n").split("\n");
57
+ const root = lines[0];
58
+ if (!root?.startsWith("/")) {
59
+ return void 0;
60
+ }
61
+ return { root, inheritedPath: lines[1] ?? "" };
49
62
  }
50
63
  var TFYSandboxProvider = class _TFYSandboxProvider {
64
+ type = "tfy";
51
65
  serverUrl;
52
66
  natsBridgeUrl;
53
67
  tenantName;
54
68
  fileMaxBytesForDownload;
55
69
  defaultExecTimeoutSeconds;
56
70
  logger;
71
+ /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */
72
+ sandboxLayouts = /* @__PURE__ */ new Map();
57
73
  constructor(options) {
58
74
  this.serverUrl = options.serverUrl.replace(/\/+$/, "");
59
75
  this.natsBridgeUrl = options.natsBridgeUrl;
@@ -75,11 +91,45 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
75
91
  return Promise.resolve(_TFYSandboxProvider.readyBuild);
76
92
  }
77
93
  createSandbox() {
78
- const sandboxId = `${this.tenantName}.${(0, import_crypto.randomUUID)()}`;
94
+ const sandboxId = `${this.tenantName}.${(0, import_node_crypto.randomUUID)()}`;
79
95
  this.logger.debug(`Sandbox created: id=${sandboxId}`);
80
96
  return Promise.resolve({ sandboxId });
81
97
  }
98
+ /**
99
+ * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative
100
+ * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so
101
+ * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.
102
+ */
82
103
  async exec(params) {
104
+ (0, import_SandboxErrors.validateSandboxOwnedByTenant)({ sandboxId: params.sandboxId, tenantName: this.tenantName });
105
+ const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);
106
+ const incoming = params.env ?? {};
107
+ const env = (0, import_execEnv.absolutizeRelativeExecEnv)({
108
+ root,
109
+ env: { ...incoming, PATH: withMcpClientOnPath(incoming["PATH"] ?? inheritedPath) }
110
+ });
111
+ return this.postExec({ ...params, env });
112
+ }
113
+ async sandboxLayout(sandboxId) {
114
+ const cached = this.sandboxLayouts.get(sandboxId);
115
+ if (cached !== void 0) {
116
+ return cached;
117
+ }
118
+ const result = await this.postExec({
119
+ sandboxId,
120
+ command: `printf '%s\\n%s\\n' "$(pwd)" "$PATH"`
121
+ });
122
+ if (!result.success) {
123
+ throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);
124
+ }
125
+ const parsed = parsePwdAndPath(result.response.result);
126
+ if (parsed === void 0) {
127
+ throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);
128
+ }
129
+ this.sandboxLayouts.set(sandboxId, parsed);
130
+ return parsed;
131
+ }
132
+ async postExec(params) {
83
133
  return import_api.context.with((0, import_core.suppressTracing)(import_api.context.active()), async () => {
84
134
  const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;
85
135
  const body = {
@@ -124,7 +174,7 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
124
174
  async getFileInfo(params) {
125
175
  const result = await this.exec({
126
176
  sandboxId: params.sandboxId,
127
- command: `stat -L --printf='{"size":%s,"type":"%F"}' ${shellEscape(params.path)}`
177
+ command: `stat -L --printf='{"size":%s,"type":"%F"}' ${(0, import_Provider.shellEscape)(params.path)}`
128
178
  });
129
179
  if (!result.success) {
130
180
  throw new Error(`Failed to stat file: ${result.error}`);
@@ -145,7 +195,7 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
145
195
  }
146
196
  const result = await this.exec({
147
197
  sandboxId: params.sandboxId,
148
- command: `base64 -w0 ${shellEscape(params.path)}`
198
+ command: `base64 -w0 ${(0, import_Provider.shellEscape)(params.path)}`
149
199
  });
150
200
  if (!result.success) {
151
201
  throw new Error(`Failed to download file: ${result.error}`);
@@ -157,10 +207,10 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
157
207
  }
158
208
  async uploadFile(params) {
159
209
  const encoded = params.content.toString("base64");
160
- const escapedPath = shellEscape(params.remotePath);
210
+ const escapedPath = (0, import_Provider.shellEscape)(params.remotePath);
161
211
  const result = await this.exec({
162
212
  sandboxId: params.sandboxId,
163
- command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`
213
+ command: `echo ${(0, import_Provider.shellEscape)(encoded)} | base64 -d > ${escapedPath}`
164
214
  });
165
215
  (0, import_Provider.ensureExecSuccess)(result);
166
216
  }
@@ -169,26 +219,40 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
169
219
  return new import_CodeModeNatsTransport.CodeModeNatsTransport({
170
220
  resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),
171
221
  sandboxClientNatsUrl: `ws://localhost:${String(import_constants.DEFAULT_SANDBOX_NATS_WS_PORT)}`,
172
- logger: this.logger
222
+ logger: this.logger,
223
+ mcpClientInstall: { remotePath: (0, import_posix.join)("mcp-client", "mcp_client.py") }
173
224
  });
174
225
  }
175
226
  getAdditionalInstructions() {
176
227
  return import_dedent.default`
177
228
  SANDBOX RULES:
178
- - The Agent's first sandbox command should be \`pwd\` to discover the working directory.
179
- - ALL file creation and writes MUST stay within that working directory.
229
+ - uploads, skills, and tool-results live in the sandbox working directory (not /tmp or /opt).
230
+ - ALL file creation and writes MUST stay within the sandbox working directory.
180
231
  - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.
181
232
  `;
182
233
  }
183
- getToolResultDumpDir(sandboxId) {
184
- return `/tmp/${sandboxId}/tool-results`;
234
+ // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.
235
+ // uploads, skills, tool-results, git_downloader.py, .git-credentials
236
+ // mcp-client/mcp_client.py (no /usr/local/bin symlink)
237
+ getToolResultDumpDir() {
238
+ return "tool-results";
239
+ }
240
+ getGitCredentialsPath() {
241
+ return ".git-credentials";
242
+ }
243
+ getFileUploadsDir() {
244
+ return "uploads";
245
+ }
246
+ getSkillsDir() {
247
+ return "skills";
185
248
  }
186
- getGitCredentialsPath(sandboxId) {
187
- return `/tmp/${sandboxId}/.git-credentials`;
249
+ getGitDownloaderPath() {
250
+ return "git_downloader.py";
188
251
  }
189
252
  };
190
253
  // Annotate the CommonJS export names for ESM import in node:
191
254
  0 && (module.exports = {
192
- TFYSandboxProvider
255
+ TFYSandboxProvider,
256
+ withMcpClientOnPath
193
257
  });
194
258
  //# sourceMappingURL=TFYSandboxProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'crypto';\nimport dedent from 'dedent';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport { SandboxFileNotFoundError, SandboxFileTooLargeError, SandboxPathIsDirectoryError } from '../SandboxErrors';\nimport {\n ensureExecSuccess,\n type ExecResult,\n type SandboxBuild,\n type SandboxExecParams,\n type SandboxFileInfo,\n type SandboxProvider,\n} from './Provider';\n\nconst DEFAULT_TIMEOUT_SECONDS = 60;\n// Buffer for network latency + response processing on top of the server-side timeout.\nconst CLIENT_TIMEOUT_BUFFER_SECONDS = 5;\n\n// Wraps a value in single quotes for safe use in shell commands.\n// Inner single quotes are escaped via the standard shell idiom: ' → '\\''\nfunction shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const controller = new AbortController();\n const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1000;\n const timer = setTimeout(() => {\n controller.abort();\n }, clientTimeoutMs);\n\n try {\n const response = await fetch(`${this.serverUrl}/exec`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);\n return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };\n }\n\n const result = (await response.json()) as ExecResult;\n return result;\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'AbortError') {\n this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));\n return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };\n }\n this.logger.error('Sandbox exec failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n } finally {\n clearTimeout(timer);\n }\n });\n }\n\n private async getFileInfo(params: { sandboxId: string; path: string }): Promise<SandboxFileInfo> {\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `stat -L --printf='{\"size\":%s,\"type\":\"%F\"}' ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to stat file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n const parsed = JSON.parse(result.response.result) as StatResult;\n return { size: parsed.size, isDir: parsed.type === 'directory' };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n const info = await this.getFileInfo(params);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `base64 -w0 ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to download file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n return Buffer.from(result.response.result.trim(), 'base64');\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n const encoded = params.content.toString('base64');\n const escapedPath = shellEscape(params.remotePath);\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`,\n });\n ensureExecSuccess(result);\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - The Agent's first sandbox command should be \\`pwd\\` to discover the working directory.\n - ALL file creation and writes MUST stay within that working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n getToolResultDumpDir(sandboxId: string): string {\n return `/tmp/${sandboxId}/tool-results`;\n }\n\n getGitCredentialsPath(sandboxId: string): string {\n return `/tmp/${sandboxId}/.git-credentials`;\n }\n}\n"],"mappings":"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","names":["dedent"]}
1
+ {"version":3,"sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport dedent from 'dedent';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport { absolutizeRelativeExecEnv } from './execEnv';\nimport {\n ensureExecSuccess,\n shellEscape,\n type ExecResult,\n type SandboxBuild,\n type SandboxExecParams,\n type SandboxFileInfo,\n type SandboxProvider,\n} from './Provider';\n\nconst DEFAULT_TIMEOUT_SECONDS = 60;\n// Buffer for network latency + response processing on top of the server-side timeout.\nconst CLIENT_TIMEOUT_BUFFER_SECONDS = 5;\n\nconst TFY_MCP_CLIENT_BIN = 'mcp-client/bin';\n\n/** Put the layout CLI first on PATH (cwd-relative; {@link absolutizeRelativeExecEnv} makes it absolute). */\nexport function withMcpClientOnPath(path: string): string {\n const rest = path.split(':').filter(part => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);\n return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(':')}`;\n}\n\nfunction parsePwdAndPath(text: string): { root: string; inheritedPath: string } | undefined {\n const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\n const root = lines[0];\n if (!root?.startsWith('/')) {\n return undefined;\n }\n return { root, inheritedPath: lines[1] ?? '' };\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n readonly type = 'tfy';\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */\n private readonly sandboxLayouts = new Map<string, { root: string; inheritedPath: string }>();\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n /**\n * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative\n * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so\n * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.\n */\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });\n const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);\n const incoming = params.env ?? {};\n const env = absolutizeRelativeExecEnv({\n root,\n env: { ...incoming, PATH: withMcpClientOnPath(incoming['PATH'] ?? inheritedPath) },\n });\n return this.postExec({ ...params, env });\n }\n\n private async sandboxLayout(sandboxId: string): Promise<{ root: string; inheritedPath: string }> {\n const cached = this.sandboxLayouts.get(sandboxId);\n if (cached !== undefined) {\n return cached;\n }\n const result = await this.postExec({\n sandboxId,\n command: 'printf \\'%s\\\\n%s\\\\n\\' \"$(pwd)\" \"$PATH\"',\n });\n if (!result.success) {\n throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);\n }\n const parsed = parsePwdAndPath(result.response.result);\n if (parsed === undefined) {\n throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);\n }\n this.sandboxLayouts.set(sandboxId, parsed);\n return parsed;\n }\n\n private async postExec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const controller = new AbortController();\n const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1000;\n const timer = setTimeout(() => {\n controller.abort();\n }, clientTimeoutMs);\n\n try {\n const response = await fetch(`${this.serverUrl}/exec`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);\n return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };\n }\n\n const result = (await response.json()) as ExecResult;\n return result;\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'AbortError') {\n this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));\n return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };\n }\n this.logger.error('Sandbox exec failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n } finally {\n clearTimeout(timer);\n }\n });\n }\n\n private async getFileInfo(params: { sandboxId: string; path: string }): Promise<SandboxFileInfo> {\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `stat -L --printf='{\"size\":%s,\"type\":\"%F\"}' ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to stat file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n const parsed = JSON.parse(result.response.result) as StatResult;\n return { size: parsed.size, isDir: parsed.type === 'directory' };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n const info = await this.getFileInfo(params);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `base64 -w0 ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to download file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n return Buffer.from(result.response.result.trim(), 'base64');\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n const encoded = params.content.toString('base64');\n const escapedPath = shellEscape(params.remotePath);\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`,\n });\n ensureExecSuccess(result);\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n mcpClientInstall: { remotePath: join('mcp-client', 'mcp_client.py') },\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - uploads, skills, and tool-results live in the sandbox working directory (not /tmp or /opt).\n - ALL file creation and writes MUST stay within the sandbox working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.\n // uploads, skills, tool-results, git_downloader.py, .git-credentials\n // mcp-client/mcp_client.py (no /usr/local/bin symlink)\n getToolResultDumpDir(): string {\n return 'tool-results';\n }\n\n getGitCredentialsPath(): string {\n return '.git-credentials';\n }\n\n getFileUploadsDir(): string {\n return 'uploads';\n }\n\n getSkillsDir(): string {\n return 'skills';\n }\n\n getGitDownloaderPath(): string {\n return 'git_downloader.py';\n }\n}\n"],"mappings":"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","names":["dedent"]}
@@ -1,27 +1,48 @@
1
1
  // src/core/sandbox/provider/TFYSandboxProvider.ts
2
2
  import { context } from "@opentelemetry/api";
3
3
  import { suppressTracing } from "@opentelemetry/core";
4
- import { randomUUID } from "crypto";
5
4
  import dedent from "dedent";
5
+ import { randomUUID } from "crypto";
6
+ import { join } from "path/posix";
6
7
  import { extractErrorLogFields } from "../../util/errorLogFields.mjs";
7
8
  import { CodeModeNatsTransport } from "../codeMode/nats/CodeModeNatsTransport.mjs";
8
9
  import { DEFAULT_SANDBOX_NATS_WS_PORT } from "../constants.mjs";
9
- import { SandboxFileNotFoundError, SandboxFileTooLargeError, SandboxPathIsDirectoryError } from "../SandboxErrors.mjs";
10
10
  import {
11
- ensureExecSuccess
11
+ SandboxFileNotFoundError,
12
+ SandboxFileTooLargeError,
13
+ SandboxPathIsDirectoryError,
14
+ validateSandboxOwnedByTenant
15
+ } from "../SandboxErrors.mjs";
16
+ import { absolutizeRelativeExecEnv } from "./execEnv.mjs";
17
+ import {
18
+ ensureExecSuccess,
19
+ shellEscape
12
20
  } from "./Provider.mjs";
13
21
  var DEFAULT_TIMEOUT_SECONDS = 60;
14
22
  var CLIENT_TIMEOUT_BUFFER_SECONDS = 5;
15
- function shellEscape(value) {
16
- return `'${value.replace(/'/g, "'\\''")}'`;
23
+ var TFY_MCP_CLIENT_BIN = "mcp-client/bin";
24
+ function withMcpClientOnPath(path) {
25
+ const rest = path.split(":").filter((part) => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);
26
+ return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(":")}`;
27
+ }
28
+ function parsePwdAndPath(text) {
29
+ const lines = text.replace(/\r\n/g, "\n").split("\n");
30
+ const root = lines[0];
31
+ if (!root?.startsWith("/")) {
32
+ return void 0;
33
+ }
34
+ return { root, inheritedPath: lines[1] ?? "" };
17
35
  }
18
36
  var TFYSandboxProvider = class _TFYSandboxProvider {
37
+ type = "tfy";
19
38
  serverUrl;
20
39
  natsBridgeUrl;
21
40
  tenantName;
22
41
  fileMaxBytesForDownload;
23
42
  defaultExecTimeoutSeconds;
24
43
  logger;
44
+ /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */
45
+ sandboxLayouts = /* @__PURE__ */ new Map();
25
46
  constructor(options) {
26
47
  this.serverUrl = options.serverUrl.replace(/\/+$/, "");
27
48
  this.natsBridgeUrl = options.natsBridgeUrl;
@@ -47,7 +68,41 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
47
68
  this.logger.debug(`Sandbox created: id=${sandboxId}`);
48
69
  return Promise.resolve({ sandboxId });
49
70
  }
71
+ /**
72
+ * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative
73
+ * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so
74
+ * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.
75
+ */
50
76
  async exec(params) {
77
+ validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });
78
+ const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);
79
+ const incoming = params.env ?? {};
80
+ const env = absolutizeRelativeExecEnv({
81
+ root,
82
+ env: { ...incoming, PATH: withMcpClientOnPath(incoming["PATH"] ?? inheritedPath) }
83
+ });
84
+ return this.postExec({ ...params, env });
85
+ }
86
+ async sandboxLayout(sandboxId) {
87
+ const cached = this.sandboxLayouts.get(sandboxId);
88
+ if (cached !== void 0) {
89
+ return cached;
90
+ }
91
+ const result = await this.postExec({
92
+ sandboxId,
93
+ command: `printf '%s\\n%s\\n' "$(pwd)" "$PATH"`
94
+ });
95
+ if (!result.success) {
96
+ throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);
97
+ }
98
+ const parsed = parsePwdAndPath(result.response.result);
99
+ if (parsed === void 0) {
100
+ throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);
101
+ }
102
+ this.sandboxLayouts.set(sandboxId, parsed);
103
+ return parsed;
104
+ }
105
+ async postExec(params) {
51
106
  return context.with(suppressTracing(context.active()), async () => {
52
107
  const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;
53
108
  const body = {
@@ -137,25 +192,39 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
137
192
  return new CodeModeNatsTransport({
138
193
  resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),
139
194
  sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,
140
- logger: this.logger
195
+ logger: this.logger,
196
+ mcpClientInstall: { remotePath: join("mcp-client", "mcp_client.py") }
141
197
  });
142
198
  }
143
199
  getAdditionalInstructions() {
144
200
  return dedent`
145
201
  SANDBOX RULES:
146
- - The Agent's first sandbox command should be \`pwd\` to discover the working directory.
147
- - ALL file creation and writes MUST stay within that working directory.
202
+ - uploads, skills, and tool-results live in the sandbox working directory (not /tmp or /opt).
203
+ - ALL file creation and writes MUST stay within the sandbox working directory.
148
204
  - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.
149
205
  `;
150
206
  }
151
- getToolResultDumpDir(sandboxId) {
152
- return `/tmp/${sandboxId}/tool-results`;
207
+ // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.
208
+ // uploads, skills, tool-results, git_downloader.py, .git-credentials
209
+ // mcp-client/mcp_client.py (no /usr/local/bin symlink)
210
+ getToolResultDumpDir() {
211
+ return "tool-results";
212
+ }
213
+ getGitCredentialsPath() {
214
+ return ".git-credentials";
215
+ }
216
+ getFileUploadsDir() {
217
+ return "uploads";
218
+ }
219
+ getSkillsDir() {
220
+ return "skills";
153
221
  }
154
- getGitCredentialsPath(sandboxId) {
155
- return `/tmp/${sandboxId}/.git-credentials`;
222
+ getGitDownloaderPath() {
223
+ return "git_downloader.py";
156
224
  }
157
225
  };
158
226
  export {
159
- TFYSandboxProvider
227
+ TFYSandboxProvider,
228
+ withMcpClientOnPath
160
229
  };
161
230
  //# sourceMappingURL=TFYSandboxProvider.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport { randomUUID } from 'crypto';\nimport dedent from 'dedent';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport { SandboxFileNotFoundError, SandboxFileTooLargeError, SandboxPathIsDirectoryError } from '../SandboxErrors';\nimport {\n ensureExecSuccess,\n type ExecResult,\n type SandboxBuild,\n type SandboxExecParams,\n type SandboxFileInfo,\n type SandboxProvider,\n} from './Provider';\n\nconst DEFAULT_TIMEOUT_SECONDS = 60;\n// Buffer for network latency + response processing on top of the server-side timeout.\nconst CLIENT_TIMEOUT_BUFFER_SECONDS = 5;\n\n// Wraps a value in single quotes for safe use in shell commands.\n// Inner single quotes are escaped via the standard shell idiom: ' → '\\''\nfunction shellEscape(value: string): string {\n return `'${value.replace(/'/g, \"'\\\\''\")}'`;\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const controller = new AbortController();\n const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1000;\n const timer = setTimeout(() => {\n controller.abort();\n }, clientTimeoutMs);\n\n try {\n const response = await fetch(`${this.serverUrl}/exec`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);\n return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };\n }\n\n const result = (await response.json()) as ExecResult;\n return result;\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'AbortError') {\n this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));\n return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };\n }\n this.logger.error('Sandbox exec failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n } finally {\n clearTimeout(timer);\n }\n });\n }\n\n private async getFileInfo(params: { sandboxId: string; path: string }): Promise<SandboxFileInfo> {\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `stat -L --printf='{\"size\":%s,\"type\":\"%F\"}' ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to stat file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n const parsed = JSON.parse(result.response.result) as StatResult;\n return { size: parsed.size, isDir: parsed.type === 'directory' };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n const info = await this.getFileInfo(params);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `base64 -w0 ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to download file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n return Buffer.from(result.response.result.trim(), 'base64');\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n const encoded = params.content.toString('base64');\n const escapedPath = shellEscape(params.remotePath);\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`,\n });\n ensureExecSuccess(result);\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - The Agent's first sandbox command should be \\`pwd\\` to discover the working directory.\n - ALL file creation and writes MUST stay within that working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n getToolResultDumpDir(sandboxId: string): string {\n return `/tmp/${sandboxId}/tool-results`;\n }\n\n getGitCredentialsPath(sandboxId: string): string {\n return `/tmp/${sandboxId}/.git-credentials`;\n }\n}\n"],"mappings":";AAAA,SAAS,eAAe;AACxB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAC3B,OAAO,YAAY;AAEnB,SAAS,6BAA6B;AAEtC,SAAS,6BAA6B;AACtC,SAAS,oCAAoC;AAC7C,SAAS,0BAA0B,0BAA0B,mCAAmC;AAChG;AAAA,EACE;AAAA,OAMK;AAEP,IAAM,0BAA0B;AAEhC,IAAM,gCAAgC;AAItC,SAAS,YAAY,OAAuB;AAC1C,SAAO,IAAI,MAAM,QAAQ,MAAM,OAAO,CAAC;AACzC;AAgBO,IAAM,qBAAN,MAAM,oBAA8C;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEjB,YAAY,SAAoC;AAC9C,SAAK,YAAY,QAAQ,UAAU,QAAQ,QAAQ,EAAE;AACrD,SAAK,gBAAgB,QAAQ;AAC7B,SAAK,aAAa,QAAQ;AAC1B,SAAK,0BAA0B,QAAQ;AACvC,SAAK,4BAA4B,KAAK,MAAM,QAAQ,wBAAwB,0BAA0B,OAAQ,GAAI;AAClH,SAAK,SAAS,QAAQ,OAAO,MAAM,EAAE,QAAQ,qBAAqB,CAAC;AAAA,EACrE;AAAA;AAAA,EAGA,OAAwB,aAA2B;AAAA,IACjD,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,UAAU;AAAA,EACZ;AAAA,EAEA,aAAoC;AAClC,WAAO,QAAQ,QAAQ,oBAAmB,UAAU;AAAA,EACtD;AAAA,EAEA,sBAA6C;AAC3C,WAAO,QAAQ,QAAQ,oBAAmB,UAAU;AAAA,EACtD;AAAA,EAEA,gBAAgD;AAC9C,UAAM,YAAY,GAAG,KAAK,UAAU,IAAI,WAAW,CAAC;AACpD,SAAK,OAAO,MAAM,uBAAuB,SAAS,EAAE;AACpD,WAAO,QAAQ,QAAQ,EAAE,UAAU,CAAC;AAAA,EACtC;AAAA,EAEA,MAAM,KAAK,QAAgD;AACzD,WAAO,QAAQ,KAAK,gBAAgB,QAAQ,OAAO,CAAC,GAAG,YAAiC;AAGtF,YAAM,iBAAiB,OAAO,kBAAkB,KAAK;AACrD,YAAM,OAAO;AAAA,QACX,YAAY,OAAO;AAAA,QACnB,SAAS,OAAO;AAAA,QAChB,KAAK,OAAO;AAAA,QACZ,KAAK,OAAO;AAAA,QACZ,SAAS;AAAA,MACX;AAEA,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,mBAAmB,iBAAiB,iCAAiC;AAC3E,YAAM,QAAQ,WAAW,MAAM;AAC7B,mBAAW,MAAM;AAAA,MACnB,GAAG,eAAe;AAElB,UAAI;AACF,cAAM,WAAW,MAAM,MAAM,GAAG,KAAK,SAAS,SAAS;AAAA,UACrD,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,IAAI;AAAA,UACzB,QAAQ,WAAW;AAAA,QACrB,CAAC;AAED,YAAI,CAAC,SAAS,IAAI;AAChB,gBAAM,OAAO,MAAM,SAAS,KAAK;AACjC,eAAK,OAAO,MAAM,2BAA2B,OAAO,SAAS,MAAM,CAAC,KAAK,IAAI,EAAE;AAC/E,iBAAO,EAAE,SAAS,OAAO,OAAO,2BAA2B,OAAO,SAAS,MAAM,CAAC,KAAK,IAAI,GAAG;AAAA,QAChG;AAEA,cAAM,SAAU,MAAM,SAAS,KAAK;AACpC,eAAO;AAAA,MACT,SAAS,GAAY;AACnB,YAAI,aAAa,SAAS,EAAE,SAAS,cAAc;AACjD,eAAK,OAAO,MAAM,gCAAgC,OAAO,cAAc,CAAC,KAAK,sBAAsB,CAAC,CAAC;AACrG,iBAAO,EAAE,SAAS,OAAO,OAAO,gCAAgC,OAAO,cAAc,CAAC,IAAI;AAAA,QAC5F;AACA,aAAK,OAAO,MAAM,uBAAuB,sBAAsB,CAAC,CAAC;AACjE,cAAM,UAAU,aAAa,QAAQ,EAAE,UAAU;AACjD,eAAO,EAAE,SAAS,OAAO,OAAO,QAAQ;AAAA,MAC1C,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAc,YAAY,QAAuE;AAC/F,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,WAAW,OAAO;AAAA,MAClB,SAAS,8CAA8C,YAAY,OAAO,IAAI,CAAC;AAAA,IACjF,CAAC;AAED,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,wBAAwB,OAAO,KAAK,EAAE;AAAA,IACxD;AACA,QAAI,OAAO,SAAS,aAAa,GAAG;AAClC,YAAM,IAAI,yBAAyB,OAAO,IAAI;AAAA,IAChD;AAEA,UAAM,SAAS,KAAK,MAAM,OAAO,SAAS,MAAM;AAChD,WAAO,EAAE,MAAM,OAAO,MAAM,OAAO,OAAO,SAAS,YAAY;AAAA,EACjE;AAAA,EAEA,MAAM,aAAa,QAA8D;AAC/E,UAAM,OAAO,MAAM,KAAK,YAAY,MAAM;AAC1C,QAAI,KAAK,OAAO;AACd,YAAM,IAAI,4BAA4B,OAAO,IAAI;AAAA,IACnD;AACA,QAAI,KAAK,OAAO,KAAK,yBAAyB;AAC5C,YAAM,IAAI,yBAAyB,OAAO,MAAM,KAAK,MAAM,KAAK,uBAAuB;AAAA,IACzF;AAEA,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,WAAW,OAAO;AAAA,MAClB,SAAS,cAAc,YAAY,OAAO,IAAI,CAAC;AAAA,IACjD,CAAC;AAED,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,MAAM,4BAA4B,OAAO,KAAK,EAAE;AAAA,IAC5D;AACA,QAAI,OAAO,SAAS,aAAa,GAAG;AAClC,YAAM,IAAI,yBAAyB,OAAO,IAAI;AAAA,IAChD;AAEA,WAAO,OAAO,KAAK,OAAO,SAAS,OAAO,KAAK,GAAG,QAAQ;AAAA,EAC5D;AAAA,EAEA,MAAM,WAAW,QAAmF;AAClG,UAAM,UAAU,OAAO,QAAQ,SAAS,QAAQ;AAChD,UAAM,cAAc,YAAY,OAAO,UAAU;AAEjD,UAAM,SAAS,MAAM,KAAK,KAAK;AAAA,MAC7B,WAAW,OAAO;AAAA,MAClB,SAAS,QAAQ,YAAY,OAAO,CAAC,kBAAkB,WAAW;AAAA,IACpE,CAAC;AACD,sBAAkB,MAAM;AAAA,EAC1B;AAAA;AAAA,EAGA,0BAA6C;AAC3C,WAAO,IAAI,sBAAsB;AAAA,MAC/B,gBAAgB,MAAM,QAAQ,QAAQ,KAAK,aAAa;AAAA,MACxD,sBAAsB,kBAAkB,OAAO,4BAA4B,CAAC;AAAA,MAC5E,QAAQ,KAAK;AAAA,IACf,CAAC;AAAA,EACH;AAAA,EAEA,4BAAoC;AAClC,WAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMT;AAAA,EAEA,qBAAqB,WAA2B;AAC9C,WAAO,QAAQ,SAAS;AAAA,EAC1B;AAAA,EAEA,sBAAsB,WAA2B;AAC/C,WAAO,QAAQ,SAAS;AAAA,EAC1B;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport dedent from 'dedent';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport { absolutizeRelativeExecEnv } from './execEnv';\nimport {\n ensureExecSuccess,\n shellEscape,\n type ExecResult,\n type SandboxBuild,\n type SandboxExecParams,\n type SandboxFileInfo,\n type SandboxProvider,\n} from './Provider';\n\nconst DEFAULT_TIMEOUT_SECONDS = 60;\n// Buffer for network latency + response processing on top of the server-side timeout.\nconst CLIENT_TIMEOUT_BUFFER_SECONDS = 5;\n\nconst TFY_MCP_CLIENT_BIN = 'mcp-client/bin';\n\n/** Put the layout CLI first on PATH (cwd-relative; {@link absolutizeRelativeExecEnv} makes it absolute). */\nexport function withMcpClientOnPath(path: string): string {\n const rest = path.split(':').filter(part => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);\n return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(':')}`;\n}\n\nfunction parsePwdAndPath(text: string): { root: string; inheritedPath: string } | undefined {\n const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\n const root = lines[0];\n if (!root?.startsWith('/')) {\n return undefined;\n }\n return { root, inheritedPath: lines[1] ?? '' };\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n readonly type = 'tfy';\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */\n private readonly sandboxLayouts = new Map<string, { root: string; inheritedPath: string }>();\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n /**\n * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative\n * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so\n * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.\n */\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });\n const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);\n const incoming = params.env ?? {};\n const env = absolutizeRelativeExecEnv({\n root,\n env: { ...incoming, PATH: withMcpClientOnPath(incoming['PATH'] ?? inheritedPath) },\n });\n return this.postExec({ ...params, env });\n }\n\n private async sandboxLayout(sandboxId: string): Promise<{ root: string; inheritedPath: string }> {\n const cached = this.sandboxLayouts.get(sandboxId);\n if (cached !== undefined) {\n return cached;\n }\n const result = await this.postExec({\n sandboxId,\n command: 'printf \\'%s\\\\n%s\\\\n\\' \"$(pwd)\" \"$PATH\"',\n });\n if (!result.success) {\n throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);\n }\n const parsed = parsePwdAndPath(result.response.result);\n if (parsed === undefined) {\n throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);\n }\n this.sandboxLayouts.set(sandboxId, parsed);\n return parsed;\n }\n\n private async postExec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const controller = new AbortController();\n const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1000;\n const timer = setTimeout(() => {\n controller.abort();\n }, clientTimeoutMs);\n\n try {\n const response = await fetch(`${this.serverUrl}/exec`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);\n return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };\n }\n\n const result = (await response.json()) as ExecResult;\n return result;\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'AbortError') {\n this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));\n return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };\n }\n this.logger.error('Sandbox exec failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n } finally {\n clearTimeout(timer);\n }\n });\n }\n\n private async getFileInfo(params: { sandboxId: string; path: string }): Promise<SandboxFileInfo> {\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `stat -L --printf='{\"size\":%s,\"type\":\"%F\"}' ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to stat file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n const parsed = JSON.parse(result.response.result) as StatResult;\n return { size: parsed.size, isDir: parsed.type === 'directory' };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n const info = await this.getFileInfo(params);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `base64 -w0 ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to download file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n return Buffer.from(result.response.result.trim(), 'base64');\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n const encoded = params.content.toString('base64');\n const escapedPath = shellEscape(params.remotePath);\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`,\n });\n ensureExecSuccess(result);\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n mcpClientInstall: { remotePath: join('mcp-client', 'mcp_client.py') },\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - uploads, skills, and tool-results live in the sandbox working directory (not /tmp or /opt).\n - ALL file creation and writes MUST stay within the sandbox working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.\n // uploads, skills, tool-results, git_downloader.py, .git-credentials\n // mcp-client/mcp_client.py (no /usr/local/bin symlink)\n getToolResultDumpDir(): string {\n return 'tool-results';\n }\n\n getGitCredentialsPath(): string {\n return '.git-credentials';\n }\n\n getFileUploadsDir(): string {\n return 'uploads';\n }\n\n getSkillsDir(): string {\n return 'skills';\n }\n\n getGitDownloaderPath(): string {\n return 'git_downloader.py';\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,6 @@
1
+ /** Make cwd-relative PATH / PYTHONPATH / GIT_CONFIG store --file / TFY_SKILLS_DIR usable from any cwd. */
2
+ export declare function absolutizeRelativeExecEnv(params: {
3
+ root: string;
4
+ env: Record<string, string>;
5
+ }): Record<string, string>;
6
+ //# sourceMappingURL=execEnv.d.ts.map