@truefoundry/trueforge-core 0.2.0 → 0.3.0-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 (78) hide show
  1. package/dist/core/index.d.ts +1 -0
  2. package/dist/core/index.d.ts.map +1 -1
  3. package/dist/core/index.js +7 -0
  4. package/dist/core/index.js.map +1 -1
  5. package/dist/core/index.mjs +4 -0
  6. package/dist/core/index.mjs.map +1 -1
  7. package/dist/core/llm/LLMTypes.js +2 -2
  8. package/dist/core/llm/LLMTypes.js.map +1 -1
  9. package/dist/core/llm/LLMTypes.mjs +2 -2
  10. package/dist/core/llm/LLMTypes.mjs.map +1 -1
  11. package/dist/core/llm/VercelAILLM.d.ts.map +1 -1
  12. package/dist/core/llm/VercelAILLM.js +10 -0
  13. package/dist/core/llm/VercelAILLM.js.map +1 -1
  14. package/dist/core/llm/VercelAILLM.mjs +10 -0
  15. package/dist/core/llm/VercelAILLM.mjs.map +1 -1
  16. package/dist/core/llm/toOpenAIChatMessage.d.ts +4 -3
  17. package/dist/core/llm/toOpenAIChatMessage.d.ts.map +1 -1
  18. package/dist/core/llm/toOpenAIChatMessage.js.map +1 -1
  19. package/dist/core/llm/toOpenAIChatMessage.mjs.map +1 -1
  20. package/dist/core/mcp/RemoteMCP.d.ts +2 -1
  21. package/dist/core/mcp/RemoteMCP.d.ts.map +1 -1
  22. package/dist/core/mcp/RemoteMCP.js +65 -20
  23. package/dist/core/mcp/RemoteMCP.js.map +1 -1
  24. package/dist/core/mcp/RemoteMCP.mjs +65 -20
  25. package/dist/core/mcp/RemoteMCP.mjs.map +1 -1
  26. package/dist/core/mcp/remoteMcpClient.d.ts.map +1 -1
  27. package/dist/core/mcp/remoteMcpClient.js +2 -5
  28. package/dist/core/mcp/remoteMcpClient.js.map +1 -1
  29. package/dist/core/mcp/remoteMcpClient.mjs +2 -5
  30. package/dist/core/mcp/remoteMcpClient.mjs.map +1 -1
  31. package/dist/core/runtime/AgentThread.d.ts.map +1 -1
  32. package/dist/core/runtime/AgentThread.js +7 -1
  33. package/dist/core/runtime/AgentThread.js.map +1 -1
  34. package/dist/core/runtime/AgentThread.mjs +7 -1
  35. package/dist/core/runtime/AgentThread.mjs.map +1 -1
  36. package/dist/core/sandbox/provider/TFYSandboxProvider.d.ts.map +1 -1
  37. package/dist/core/sandbox/provider/TFYSandboxProvider.js +56 -14
  38. package/dist/core/sandbox/provider/TFYSandboxProvider.js.map +1 -1
  39. package/dist/core/sandbox/provider/TFYSandboxProvider.mjs +57 -16
  40. package/dist/core/sandbox/provider/TFYSandboxProvider.mjs.map +1 -1
  41. package/dist/core/util/errorLogFields.d.ts +1 -0
  42. package/dist/core/util/errorLogFields.d.ts.map +1 -1
  43. package/dist/core/util/errorLogFields.js +33 -6
  44. package/dist/core/util/errorLogFields.js.map +1 -1
  45. package/dist/core/util/errorLogFields.mjs +33 -6
  46. package/dist/core/util/errorLogFields.mjs.map +1 -1
  47. package/dist/core/util/ssrfGuard.d.ts +10 -0
  48. package/dist/core/util/ssrfGuard.d.ts.map +1 -0
  49. package/dist/core/util/ssrfGuard.js +333 -0
  50. package/dist/core/util/ssrfGuard.js.map +1 -0
  51. package/dist/core/util/ssrfGuard.mjs +305 -0
  52. package/dist/core/util/ssrfGuard.mjs.map +1 -0
  53. package/dist/core/web-search/ParallelWebSearchProvider.d.ts.map +1 -1
  54. package/dist/core/web-search/ParallelWebSearchProvider.js +5 -1
  55. package/dist/core/web-search/ParallelWebSearchProvider.js.map +1 -1
  56. package/dist/core/web-search/ParallelWebSearchProvider.mjs +5 -1
  57. package/dist/core/web-search/ParallelWebSearchProvider.mjs.map +1 -1
  58. package/dist/request-reply/client.d.ts +2 -2
  59. package/dist/request-reply/client.d.ts.map +1 -1
  60. package/dist/request-reply/client.js.map +1 -1
  61. package/dist/request-reply/client.mjs.map +1 -1
  62. package/dist/request-reply/executor.d.ts +3 -3
  63. package/dist/request-reply/executor.d.ts.map +1 -1
  64. package/dist/request-reply/executor.js +0 -1
  65. package/dist/request-reply/executor.js.map +1 -1
  66. package/dist/request-reply/executor.mjs +0 -1
  67. package/dist/request-reply/executor.mjs.map +1 -1
  68. package/dist/request-reply/index.d.ts +1 -0
  69. package/dist/request-reply/index.d.ts.map +1 -1
  70. package/dist/request-reply/index.js.map +1 -1
  71. package/dist/request-reply/index.mjs.map +1 -1
  72. package/dist/request-reply/redisClient.d.ts +4 -0
  73. package/dist/request-reply/redisClient.d.ts.map +1 -0
  74. package/dist/request-reply/redisClient.js +19 -0
  75. package/dist/request-reply/redisClient.js.map +1 -0
  76. package/dist/request-reply/redisClient.mjs +1 -0
  77. package/dist/request-reply/redisClient.mjs.map +1 -0
  78. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { suppressTracing } from "@opentelemetry/core";
4
4
  import dedent from "dedent";
5
5
  import { randomUUID } from "crypto";
6
6
  import { join } from "path/posix";
7
- import { extractErrorLogFields } from "../../util/errorLogFields.mjs";
7
+ import { describeUnknownError, extractErrorLogFields } from "../../util/errorLogFields.mjs";
8
8
  import { CodeModeNatsTransport } from "../codeMode/nats/CodeModeNatsTransport.mjs";
9
9
  import { DEFAULT_SANDBOX_NATS_WS_PORT } from "../constants.mjs";
10
10
  import {
@@ -15,11 +15,11 @@ import {
15
15
  } from "../SandboxErrors.mjs";
16
16
  import { absolutizeRelativeExecEnv } from "./execEnv.mjs";
17
17
  import {
18
- ensureExecSuccess,
19
18
  shellEscape
20
19
  } from "./Provider.mjs";
21
20
  var DEFAULT_TIMEOUT_SECONDS = 60;
22
21
  var CLIENT_TIMEOUT_BUFFER_SECONDS = 5;
22
+ var FILE_UPLOAD_TIMEOUT_MS = 30 * 60 * 1e3;
23
23
  var TFY_MCP_CLIENT_BIN = "mcp-client/bin";
24
24
  function withMcpClientOnPath(path) {
25
25
  const rest = path.split(":").filter((part) => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);
@@ -112,13 +112,14 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
112
112
  env: params.env,
113
113
  timeout: timeoutSeconds
114
114
  };
115
+ const execUrl = `${this.serverUrl}/exec`;
115
116
  const controller = new AbortController();
116
117
  const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1e3;
117
118
  const timer = setTimeout(() => {
118
119
  controller.abort();
119
120
  }, clientTimeoutMs);
120
121
  try {
121
- const response = await fetch(`${this.serverUrl}/exec`, {
122
+ const response = await fetch(execUrl, {
122
123
  method: "POST",
123
124
  headers: { "Content-Type": "application/json" },
124
125
  body: JSON.stringify(body),
@@ -126,19 +127,30 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
126
127
  });
127
128
  if (!response.ok) {
128
129
  const text = await response.text();
129
- this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);
130
- return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };
130
+ this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`, { url: execUrl });
131
+ return {
132
+ success: false,
133
+ error: `Sandbox server returned ${String(response.status)} from ${execUrl}: ${text}`
134
+ };
131
135
  }
132
136
  const result = await response.json();
133
137
  return result;
134
138
  } catch (e) {
135
139
  if (e instanceof Error && e.name === "AbortError") {
136
- this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));
137
- return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };
140
+ this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, {
141
+ url: execUrl,
142
+ ...extractErrorLogFields(e)
143
+ });
144
+ return {
145
+ success: false,
146
+ error: `Sandbox exec to ${execUrl} timed out after ${String(timeoutSeconds)}s`
147
+ };
138
148
  }
139
- this.logger.error("Sandbox exec failed", extractErrorLogFields(e));
140
- const message = e instanceof Error ? e.message : "Unknown error";
141
- return { success: false, error: message };
149
+ this.logger.error("Sandbox exec failed", { url: execUrl, ...extractErrorLogFields(e) });
150
+ return {
151
+ success: false,
152
+ error: `Sandbox exec to ${execUrl} failed: ${describeUnknownError(e)}`
153
+ };
142
154
  } finally {
143
155
  clearTimeout(timer);
144
156
  }
@@ -179,13 +191,42 @@ var TFYSandboxProvider = class _TFYSandboxProvider {
179
191
  return Buffer.from(result.response.result.trim(), "base64");
180
192
  }
181
193
  async uploadFile(params) {
182
- const encoded = params.content.toString("base64");
183
- const escapedPath = shellEscape(params.remotePath);
184
- const result = await this.exec({
185
- sandboxId: params.sandboxId,
186
- command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`
194
+ validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });
195
+ return context.with(suppressTracing(context.active()), async () => {
196
+ const query = new URLSearchParams({ sandbox_id: params.sandboxId, path: params.remotePath });
197
+ const uploadUrl = `${this.serverUrl}/files/upload?${query.toString()}`;
198
+ const bytes = params.content.byteLength;
199
+ this.logger.info("Uploading file to sandbox", {
200
+ sandboxId: params.sandboxId,
201
+ remotePath: params.remotePath,
202
+ bytes
203
+ });
204
+ let response;
205
+ try {
206
+ response = await fetch(uploadUrl, {
207
+ method: "POST",
208
+ headers: { "Content-Type": "application/octet-stream" },
209
+ body: params.content,
210
+ signal: AbortSignal.timeout(FILE_UPLOAD_TIMEOUT_MS)
211
+ });
212
+ } catch (e) {
213
+ throw new Error(`File upload to ${uploadUrl} failed: ${describeUnknownError(e)}`, { cause: e });
214
+ }
215
+ if (!response.ok) {
216
+ throw new Error(
217
+ `File upload to sandbox failed: Sandbox server returned ${String(response.status)} from ${uploadUrl}: ${await response.text()}`
218
+ );
219
+ }
220
+ const result = await response.json();
221
+ if (!result.success) {
222
+ throw new Error(`File upload to sandbox failed: ${result.error}`);
223
+ }
224
+ this.logger.info("Uploaded file to sandbox", {
225
+ sandboxId: params.sandboxId,
226
+ remotePath: params.remotePath,
227
+ bytes
228
+ });
187
229
  });
188
- ensureExecSuccess(result);
189
230
  }
190
231
  // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).
191
232
  createCodeModeTransport() {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/core/sandbox/provider/TFYSandboxProvider.ts"],"sourcesContent":["import { context } from '@opentelemetry/api';\nimport { suppressTracing } from '@opentelemetry/core';\nimport dedent from 'dedent';\nimport { randomUUID } from 'node:crypto';\nimport { join } from 'node:path/posix';\nimport type { Logger } from 'winston';\nimport { extractErrorLogFields } from '../../util/errorLogFields';\nimport type { CodeModeTransport } from '../codeMode/CodeModeTransport';\nimport { CodeModeNatsTransport } from '../codeMode/nats/CodeModeNatsTransport';\nimport { DEFAULT_SANDBOX_NATS_WS_PORT } from '../constants';\nimport {\n SandboxFileNotFoundError,\n SandboxFileTooLargeError,\n SandboxPathIsDirectoryError,\n validateSandboxOwnedByTenant,\n} from '../SandboxErrors';\nimport { absolutizeRelativeExecEnv } from './execEnv';\nimport {\n ensureExecSuccess,\n shellEscape,\n type ExecResult,\n type SandboxBuild,\n type SandboxExecParams,\n type SandboxFileInfo,\n type SandboxProvider,\n} from './Provider';\n\nconst DEFAULT_TIMEOUT_SECONDS = 60;\n// Buffer for network latency + response processing on top of the server-side timeout.\nconst CLIENT_TIMEOUT_BUFFER_SECONDS = 5;\n\nconst TFY_MCP_CLIENT_BIN = 'mcp-client/bin';\n\n/** Put the layout CLI first on PATH (cwd-relative; {@link absolutizeRelativeExecEnv} makes it absolute). */\nexport function withMcpClientOnPath(path: string): string {\n const rest = path.split(':').filter(part => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);\n return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(':')}`;\n}\n\nfunction parsePwdAndPath(text: string): { root: string; inheritedPath: string } | undefined {\n const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\n const root = lines[0];\n if (!root?.startsWith('/')) {\n return undefined;\n }\n return { root, inheritedPath: lines[1] ?? '' };\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n readonly type = 'truefoundry';\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */\n private readonly sandboxLayouts = new Map<string, { root: string; inheritedPath: string }>();\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n /**\n * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative\n * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so\n * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.\n */\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });\n const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);\n const incoming = params.env ?? {};\n const env = absolutizeRelativeExecEnv({\n root,\n env: { ...incoming, PATH: withMcpClientOnPath(incoming['PATH'] ?? inheritedPath) },\n });\n return this.postExec({ ...params, env });\n }\n\n private async sandboxLayout(sandboxId: string): Promise<{ root: string; inheritedPath: string }> {\n const cached = this.sandboxLayouts.get(sandboxId);\n if (cached !== undefined) {\n return cached;\n }\n const result = await this.postExec({\n sandboxId,\n command: 'printf \\'%s\\\\n%s\\\\n\\' \"$(pwd)\" \"$PATH\"',\n });\n if (!result.success) {\n throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);\n }\n const parsed = parsePwdAndPath(result.response.result);\n if (parsed === undefined) {\n throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);\n }\n this.sandboxLayouts.set(sandboxId, parsed);\n return parsed;\n }\n\n private async postExec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const controller = new AbortController();\n const clientTimeoutMs = (timeoutSeconds + CLIENT_TIMEOUT_BUFFER_SECONDS) * 1000;\n const timer = setTimeout(() => {\n controller.abort();\n }, clientTimeoutMs);\n\n try {\n const response = await fetch(`${this.serverUrl}/exec`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n const text = await response.text();\n this.logger.error(`Sandbox server returned ${String(response.status)}: ${text}`);\n return { success: false, error: `Sandbox server returned ${String(response.status)}: ${text}` };\n }\n\n const result = (await response.json()) as ExecResult;\n return result;\n } catch (e: unknown) {\n if (e instanceof Error && e.name === 'AbortError') {\n this.logger.error(`Sandbox exec timed out after ${String(timeoutSeconds)}s`, extractErrorLogFields(e));\n return { success: false, error: `Sandbox exec timed out after ${String(timeoutSeconds)}s` };\n }\n this.logger.error('Sandbox exec failed', extractErrorLogFields(e));\n const message = e instanceof Error ? e.message : 'Unknown error';\n return { success: false, error: message };\n } finally {\n clearTimeout(timer);\n }\n });\n }\n\n private async getFileInfo(params: { sandboxId: string; path: string }): Promise<SandboxFileInfo> {\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `stat -L --printf='{\"size\":%s,\"type\":\"%F\"}' ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to stat file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n const parsed = JSON.parse(result.response.result) as StatResult;\n return { size: parsed.size, isDir: parsed.type === 'directory' };\n }\n\n async downloadFile(params: { sandboxId: string; path: string }): Promise<Buffer> {\n const info = await this.getFileInfo(params);\n if (info.isDir) {\n throw new SandboxPathIsDirectoryError(params.path);\n }\n if (info.size > this.fileMaxBytesForDownload) {\n throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);\n }\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `base64 -w0 ${shellEscape(params.path)}`,\n });\n\n if (!result.success) {\n throw new Error(`Failed to download file: ${result.error}`);\n }\n if (result.response.exitCode !== 0) {\n throw new SandboxFileNotFoundError(params.path);\n }\n\n return Buffer.from(result.response.result.trim(), 'base64');\n }\n\n async uploadFile(params: { sandboxId: string; remotePath: string; content: Buffer }): Promise<void> {\n const encoded = params.content.toString('base64');\n const escapedPath = shellEscape(params.remotePath);\n\n const result = await this.exec({\n sandboxId: params.sandboxId,\n command: `echo ${shellEscape(encoded)} | base64 -d > ${escapedPath}`,\n });\n ensureExecSuccess(result);\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n mcpClientInstall: { remotePath: join('mcp-client', 'mcp_client.py') },\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - The Agent's first sandbox command should be \\`pwd\\` to discover the working directory.\n - uploads, skills, and tool-results live in that working directory (not /tmp or /opt).\n - ALL file creation and writes MUST stay within that working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.\n // uploads, skills, tool-results, skill_downloader.py, .git-credentials\n // mcp-client/mcp_client.py (no /usr/local/bin symlink)\n getToolResultDumpDir(): string {\n return 'tool-results';\n }\n\n getGitCredentialsPath(): string {\n return '.git-credentials';\n }\n\n getFileUploadsDir(): string {\n return 'uploads';\n }\n\n getSkillsDir(): string {\n return 'skills';\n }\n\n getSkillDownloaderPath(): string {\n return 'skill_downloader.py';\n }\n}\n"],"mappings":"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","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 { describeUnknownError, 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 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\n// TFY sandbox file upload timeout (same as Daytona SDK uploadFile default timeout).\nconst FILE_UPLOAD_TIMEOUT_MS = 30 * 60 * 1000;\n\nconst TFY_MCP_CLIENT_BIN = 'mcp-client/bin';\n\n/** Put the layout CLI first on PATH (cwd-relative; {@link absolutizeRelativeExecEnv} makes it absolute). */\nexport function withMcpClientOnPath(path: string): string {\n const rest = path.split(':').filter(part => part.length > 0 && part !== TFY_MCP_CLIENT_BIN);\n return rest.length === 0 ? TFY_MCP_CLIENT_BIN : `${TFY_MCP_CLIENT_BIN}:${rest.join(':')}`;\n}\n\nfunction parsePwdAndPath(text: string): { root: string; inheritedPath: string } | undefined {\n const lines = text.replace(/\\r\\n/g, '\\n').split('\\n');\n const root = lines[0];\n if (!root?.startsWith('/')) {\n return undefined;\n }\n return { root, inheritedPath: lines[1] ?? '' };\n}\n\nexport interface TFYSandboxProviderOptions {\n serverUrl: string;\n natsBridgeUrl: string;\n tenantName: string;\n fileMaxBytesForDownload: number;\n defaultExecTimeoutMs?: number | undefined;\n logger: Logger;\n}\n\ninterface StatResult {\n size: number;\n type: string;\n}\n\nexport class TFYSandboxProvider implements SandboxProvider {\n readonly type = 'truefoundry';\n private readonly serverUrl: string;\n private readonly natsBridgeUrl: string;\n private readonly tenantName: string;\n private readonly fileMaxBytesForDownload: number;\n private readonly defaultExecTimeoutSeconds: number;\n private readonly logger: Logger;\n /** Discovered via `pwd` + `$PATH` — never a hardcoded host layout. */\n private readonly sandboxLayouts = new Map<string, { root: string; inheritedPath: string }>();\n\n constructor(options: TFYSandboxProviderOptions) {\n this.serverUrl = options.serverUrl.replace(/\\/+$/, '');\n this.natsBridgeUrl = options.natsBridgeUrl;\n this.tenantName = options.tenantName;\n this.fileMaxBytesForDownload = options.fileMaxBytesForDownload;\n this.defaultExecTimeoutSeconds = Math.ceil((options.defaultExecTimeoutMs ?? DEFAULT_TIMEOUT_SECONDS * 1000) / 1000);\n this.logger = options.logger.child({ module: 'TFYSandboxProvider' });\n }\n\n // TFY sandboxes run a prebuilt server image with no per-image build step, so the image is always ready.\n private static readonly readyBuild: SandboxBuild = {\n status: 'ready',\n reason: null,\n metadata: null,\n };\n\n buildImage(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n getImageBuildStatus(): Promise<SandboxBuild> {\n return Promise.resolve(TFYSandboxProvider.readyBuild);\n }\n\n createSandbox(): Promise<{ sandboxId: string }> {\n const sandboxId = `${this.tenantName}.${randomUUID()}`;\n this.logger.debug(`Sandbox created: id=${sandboxId}`);\n return Promise.resolve({ sandboxId });\n }\n\n /**\n * No extra FS jail: exec cwd is the logical sandbox root. Layout getters are cwd-relative\n * so writes cannot escape it. `GIT_CONFIG` / PATH / PYTHONPATH must still be absolute so\n * git and `mcp-client` work after `cd` — resolved from `pwd` / `$PATH`, not a hardcoded prefix.\n */\n async exec(params: SandboxExecParams): Promise<ExecResult> {\n validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });\n const { root, inheritedPath } = await this.sandboxLayout(params.sandboxId);\n const incoming = params.env ?? {};\n const env = absolutizeRelativeExecEnv({\n root,\n env: { ...incoming, PATH: withMcpClientOnPath(incoming['PATH'] ?? inheritedPath) },\n });\n return this.postExec({ ...params, env });\n }\n\n private async sandboxLayout(sandboxId: string): Promise<{ root: string; inheritedPath: string }> {\n const cached = this.sandboxLayouts.get(sandboxId);\n if (cached !== undefined) {\n return cached;\n }\n const result = await this.postExec({\n sandboxId,\n command: 'printf \\'%s\\\\n%s\\\\n\\' \"$(pwd)\" \"$PATH\"',\n });\n if (!result.success) {\n throw new Error(`Failed to resolve sandbox working directory: ${result.error}`);\n }\n const parsed = parsePwdAndPath(result.response.result);\n if (parsed === undefined) {\n throw new Error(`sandbox pwd did not return an absolute path: ${result.response.result}`);\n }\n this.sandboxLayouts.set(sandboxId, parsed);\n return parsed;\n }\n\n private async postExec(params: SandboxExecParams): Promise<ExecResult> {\n return context.with(suppressTracing(context.active()), async (): Promise<ExecResult> => {\n // Honor the per-call timeout (e.g. the longer skill-download timeout) for both the server-side\n // request timeout and the client abort timer; fall back to the provider default otherwise.\n const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;\n const body = {\n sandbox_id: params.sandboxId,\n command: params.command,\n cwd: params.cwd,\n env: params.env,\n timeout: timeoutSeconds,\n };\n\n const execUrl = `${this.serverUrl}/exec`;\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(execUrl, {\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}`, { url: execUrl });\n return {\n success: false,\n error: `Sandbox server returned ${String(response.status)} from ${execUrl}: ${text}`,\n };\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`, {\n url: execUrl,\n ...extractErrorLogFields(e),\n });\n return {\n success: false,\n error: `Sandbox exec to ${execUrl} timed out after ${String(timeoutSeconds)}s`,\n };\n }\n this.logger.error('Sandbox exec failed', { url: execUrl, ...extractErrorLogFields(e) });\n return {\n success: false,\n error: `Sandbox exec to ${execUrl} failed: ${describeUnknownError(e)}`,\n };\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 validateSandboxOwnedByTenant({ sandboxId: params.sandboxId, tenantName: this.tenantName });\n\n return context.with(suppressTracing(context.active()), async () => {\n const query = new URLSearchParams({ sandbox_id: params.sandboxId, path: params.remotePath });\n const uploadUrl = `${this.serverUrl}/files/upload?${query.toString()}`;\n const bytes = params.content.byteLength;\n this.logger.info('Uploading file to sandbox', {\n sandboxId: params.sandboxId,\n remotePath: params.remotePath,\n bytes,\n });\n let response: Response;\n try {\n response = await fetch(uploadUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/octet-stream' },\n body: params.content,\n signal: AbortSignal.timeout(FILE_UPLOAD_TIMEOUT_MS),\n });\n } catch (e: unknown) {\n throw new Error(`File upload to ${uploadUrl} failed: ${describeUnknownError(e)}`, { cause: e });\n }\n if (!response.ok) {\n throw new Error(\n `File upload to sandbox failed: Sandbox server returned ${String(response.status)} from ${uploadUrl}: ${await response.text()}`,\n );\n }\n\n const result = (await response.json()) as { success: true } | { success: false; error: string };\n if (!result.success) {\n throw new Error(`File upload to sandbox failed: ${result.error}`);\n }\n this.logger.info('Uploaded file to sandbox', {\n sandboxId: params.sandboxId,\n remotePath: params.remotePath,\n bytes,\n });\n });\n }\n\n // The TFY sandbox exposes a static, cluster-internal NATS WebSocket URL (no signed URLs).\n createCodeModeTransport(): CodeModeTransport {\n return new CodeModeNatsTransport({\n resolveHostUrl: () => Promise.resolve(this.natsBridgeUrl),\n sandboxClientNatsUrl: `ws://localhost:${String(DEFAULT_SANDBOX_NATS_WS_PORT)}`,\n logger: this.logger,\n mcpClientInstall: { remotePath: join('mcp-client', 'mcp_client.py') },\n });\n }\n\n getAdditionalInstructions(): string {\n return dedent`\n SANDBOX RULES:\n - The Agent's first sandbox command should be \\`pwd\\` to discover the working directory.\n - uploads, skills, and tool-results live in that working directory (not /tmp or /opt).\n - ALL file creation and writes MUST stay within that working directory.\n - The Agent must NOT write to /tmp/, ~/, or any absolute path outside the working directory.\n `;\n }\n\n // Cwd-relative (no FS jail). exec() pwd-joins GIT_CONFIG / PATH / PYTHONPATH.\n // uploads, skills, tool-results, skill_downloader.py, .git-credentials\n // mcp-client/mcp_client.py (no /usr/local/bin symlink)\n getToolResultDumpDir(): string {\n return 'tool-results';\n }\n\n getGitCredentialsPath(): string {\n return '.git-credentials';\n }\n\n getFileUploadsDir(): string {\n return 'uploads';\n }\n\n getSkillsDir(): string {\n return 'skills';\n }\n\n getSkillDownloaderPath(): string {\n return 'skill_downloader.py';\n }\n}\n"],"mappings":"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","names":[]}
@@ -5,6 +5,7 @@ export interface ErrorLogFields {
5
5
  /**
6
6
  * User/turn-facing message for any thrown value. Prefer Error.message / `.message`;
7
7
  * never surface developer-only strings (e.g. unserialisable dumps) in Agent Steps.
8
+ * Includes nested `cause` messages when present (e.g. undici "fetch failed").
8
9
  */
9
10
  export declare function describeUnknownError(error: unknown): string;
10
11
  export declare function extractErrorLogFields(error: unknown): ErrorLogFields;
@@ -1 +1 @@
1
- {"version":3,"file":"errorLogFields.d.ts","sourceRoot":"","sources":["../../../src/core/util/errorLogFields.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAUD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAgB3D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAepE"}
1
+ {"version":3,"file":"errorLogFields.d.ts","sourceRoot":"","sources":["../../../src/core/util/errorLogFields.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAoDD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAS3D;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAgBpE"}
@@ -31,9 +31,9 @@ function formatObjectErrorForLog(error) {
31
31
  return `unserialisable error (${Object.keys(error).join(", ")})`;
32
32
  }
33
33
  }
34
- function describeUnknownError(error) {
34
+ function messageOfUnknown(error) {
35
35
  if (error instanceof Error) {
36
- return error.message.length > 0 ? error.message : "An unexpected error occurred";
36
+ return error.message;
37
37
  }
38
38
  if (typeof error !== "object" || error === null) {
39
39
  return String(error);
@@ -45,22 +45,49 @@ function describeUnknownError(error) {
45
45
  try {
46
46
  return JSON.stringify(error);
47
47
  } catch {
48
+ return "";
49
+ }
50
+ }
51
+ function describeErrorChain(error, seen) {
52
+ if (error === void 0 || error === null || seen.has(error)) {
53
+ return "";
54
+ }
55
+ seen.add(error);
56
+ const head = messageOfUnknown(error).trim();
57
+ const nestedCause = typeof error === "object" && "cause" in error ? Reflect.get(error, "cause") : void 0;
58
+ const tail = describeErrorChain(nestedCause, seen).trim();
59
+ if (head.length === 0) {
60
+ return tail;
61
+ }
62
+ if (tail.length === 0 || head.includes(tail)) {
63
+ return head;
64
+ }
65
+ return `${head}: ${tail}`;
66
+ }
67
+ function describeUnknownError(error) {
68
+ const chain = describeErrorChain(error, /* @__PURE__ */ new Set());
69
+ if (chain.length > 0) {
70
+ return chain;
71
+ }
72
+ if (typeof error === "object" && error !== null) {
48
73
  return "An unexpected error occurred";
49
74
  }
75
+ return String(error);
50
76
  }
51
77
  function extractErrorLogFields(error) {
52
78
  if (error instanceof Error) {
79
+ const chain2 = describeErrorChain(error, /* @__PURE__ */ new Set());
53
80
  return {
54
- error: error.message.length > 0 ? error.message : formatObjectErrorForLog(error),
81
+ error: chain2.length > 0 ? chain2 : formatObjectErrorForLog(error),
55
82
  stack: error.stack
56
83
  };
57
84
  }
58
85
  if (typeof error !== "object" || error === null) {
59
86
  return { error: String(error) };
60
87
  }
61
- const message = Reflect.get(error, "message");
62
- if (typeof message === "string" && message.length > 0) {
63
- return { error: message };
88
+ const chain = describeErrorChain(error, /* @__PURE__ */ new Set());
89
+ if (chain.length > 0) {
90
+ return { error: chain };
64
91
  }
65
92
  return { error: formatObjectErrorForLog(error) };
66
93
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/util/errorLogFields.ts"],"sourcesContent":["export interface ErrorLogFields {\n error: string;\n stack?: string | undefined;\n}\n\nfunction formatObjectErrorForLog(error: object): string {\n try {\n return JSON.stringify(error);\n } catch {\n return `unserialisable error (${Object.keys(error).join(', ')})`;\n }\n}\n\n/**\n * User/turn-facing message for any thrown value. Prefer Error.message / `.message`;\n * never surface developer-only strings (e.g. unserialisable dumps) in Agent Steps.\n */\nexport function describeUnknownError(error: unknown): string {\n if (error instanceof Error) {\n return error.message.length > 0 ? error.message : 'An unexpected error occurred';\n }\n if (typeof error !== 'object' || error === null) {\n return String(error);\n }\n const message: unknown = Reflect.get(error, 'message');\n if (typeof message === 'string' && message.length > 0) {\n return message;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return 'An unexpected error occurred';\n }\n}\n\nexport function extractErrorLogFields(error: unknown): ErrorLogFields {\n if (error instanceof Error) {\n return {\n error: error.message.length > 0 ? error.message : formatObjectErrorForLog(error),\n stack: error.stack,\n };\n }\n if (typeof error !== 'object' || error === null) {\n return { error: String(error) };\n }\n const message: unknown = Reflect.get(error, 'message');\n if (typeof message === 'string' && message.length > 0) {\n return { error: message };\n }\n return { error: formatObjectErrorForLog(error) };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,SAAS,wBAAwB,OAAuB;AACtD,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,yBAAyB,OAAO,KAAK,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,EAC/D;AACF;AAMO,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM,QAAQ,SAAS,IAAI,MAAM,UAAU;AAAA,EACpD;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,UAAmB,QAAQ,IAAI,OAAO,SAAS;AACrD,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GAAG;AACrD,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,sBAAsB,OAAgC;AACpE,MAAI,iBAAiB,OAAO;AAC1B,WAAO;AAAA,MACL,OAAO,MAAM,QAAQ,SAAS,IAAI,MAAM,UAAU,wBAAwB,KAAK;AAAA,MAC/E,OAAO,MAAM;AAAA,IACf;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,EAAE,OAAO,OAAO,KAAK,EAAE;AAAA,EAChC;AACA,QAAM,UAAmB,QAAQ,IAAI,OAAO,SAAS;AACrD,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GAAG;AACrD,WAAO,EAAE,OAAO,QAAQ;AAAA,EAC1B;AACA,SAAO,EAAE,OAAO,wBAAwB,KAAK,EAAE;AACjD;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/util/errorLogFields.ts"],"sourcesContent":["export interface ErrorLogFields {\n error: string;\n stack?: string | undefined;\n}\n\nfunction formatObjectErrorForLog(error: object): string {\n try {\n return JSON.stringify(error);\n } catch {\n return `unserialisable error (${Object.keys(error).join(', ')})`;\n }\n}\n\n/** Single-hop message for any thrown value (no cause walk). */\nfunction messageOfUnknown(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error !== 'object' || error === null) {\n return String(error);\n }\n const message: unknown = Reflect.get(error, 'message');\n if (typeof message === 'string' && message.length > 0) {\n return message;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return '';\n }\n}\n\n/**\n * Walk `Error.cause` (and plain `{ cause }` objects) so undici-style\n * `TypeError: fetch failed` surfaces the nested `ECONNREFUSED` / cert reason.\n */\nfunction describeErrorChain(error: unknown, seen: Set<unknown>): string {\n if (error === undefined || error === null || seen.has(error)) {\n return '';\n }\n seen.add(error);\n\n const head = messageOfUnknown(error).trim();\n const nestedCause = typeof error === 'object' && 'cause' in error ? Reflect.get(error, 'cause') : undefined;\n const tail = describeErrorChain(nestedCause, seen).trim();\n\n if (head.length === 0) {\n return tail;\n }\n if (tail.length === 0 || head.includes(tail)) {\n return head;\n }\n return `${head}: ${tail}`;\n}\n\n/**\n * User/turn-facing message for any thrown value. Prefer Error.message / `.message`;\n * never surface developer-only strings (e.g. unserialisable dumps) in Agent Steps.\n * Includes nested `cause` messages when present (e.g. undici \"fetch failed\").\n */\nexport function describeUnknownError(error: unknown): string {\n const chain = describeErrorChain(error, new Set());\n if (chain.length > 0) {\n return chain;\n }\n if (typeof error === 'object' && error !== null) {\n return 'An unexpected error occurred';\n }\n return String(error);\n}\n\nexport function extractErrorLogFields(error: unknown): ErrorLogFields {\n if (error instanceof Error) {\n const chain = describeErrorChain(error, new Set());\n return {\n error: chain.length > 0 ? chain : formatObjectErrorForLog(error),\n stack: error.stack,\n };\n }\n if (typeof error !== 'object' || error === null) {\n return { error: String(error) };\n }\n const chain = describeErrorChain(error, new Set());\n if (chain.length > 0) {\n return { error: chain };\n }\n return { error: formatObjectErrorForLog(error) };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,SAAS,wBAAwB,OAAuB;AACtD,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,yBAAyB,OAAO,KAAK,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,EAC/D;AACF;AAGA,SAAS,iBAAiB,OAAwB;AAChD,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,UAAmB,QAAQ,IAAI,OAAO,SAAS;AACrD,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GAAG;AACrD,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,mBAAmB,OAAgB,MAA4B;AACtE,MAAI,UAAU,UAAa,UAAU,QAAQ,KAAK,IAAI,KAAK,GAAG;AAC5D,WAAO;AAAA,EACT;AACA,OAAK,IAAI,KAAK;AAEd,QAAM,OAAO,iBAAiB,KAAK,EAAE,KAAK;AAC1C,QAAM,cAAc,OAAO,UAAU,YAAY,WAAW,QAAQ,QAAQ,IAAI,OAAO,OAAO,IAAI;AAClG,QAAM,OAAO,mBAAmB,aAAa,IAAI,EAAE,KAAK;AAExD,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,EACT;AACA,MAAI,KAAK,WAAW,KAAK,KAAK,SAAS,IAAI,GAAG;AAC5C,WAAO;AAAA,EACT;AACA,SAAO,GAAG,IAAI,KAAK,IAAI;AACzB;AAOO,SAAS,qBAAqB,OAAwB;AAC3D,QAAM,QAAQ,mBAAmB,OAAO,oBAAI,IAAI,CAAC;AACjD,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,SAAO,OAAO,KAAK;AACrB;AAEO,SAAS,sBAAsB,OAAgC;AACpE,MAAI,iBAAiB,OAAO;AAC1B,UAAMA,SAAQ,mBAAmB,OAAO,oBAAI,IAAI,CAAC;AACjD,WAAO;AAAA,MACL,OAAOA,OAAM,SAAS,IAAIA,SAAQ,wBAAwB,KAAK;AAAA,MAC/D,OAAO,MAAM;AAAA,IACf;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,EAAE,OAAO,OAAO,KAAK,EAAE;AAAA,EAChC;AACA,QAAM,QAAQ,mBAAmB,OAAO,oBAAI,IAAI,CAAC;AACjD,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AACA,SAAO,EAAE,OAAO,wBAAwB,KAAK,EAAE;AACjD;","names":["chain"]}
@@ -6,9 +6,9 @@ function formatObjectErrorForLog(error) {
6
6
  return `unserialisable error (${Object.keys(error).join(", ")})`;
7
7
  }
8
8
  }
9
- function describeUnknownError(error) {
9
+ function messageOfUnknown(error) {
10
10
  if (error instanceof Error) {
11
- return error.message.length > 0 ? error.message : "An unexpected error occurred";
11
+ return error.message;
12
12
  }
13
13
  if (typeof error !== "object" || error === null) {
14
14
  return String(error);
@@ -20,22 +20,49 @@ function describeUnknownError(error) {
20
20
  try {
21
21
  return JSON.stringify(error);
22
22
  } catch {
23
+ return "";
24
+ }
25
+ }
26
+ function describeErrorChain(error, seen) {
27
+ if (error === void 0 || error === null || seen.has(error)) {
28
+ return "";
29
+ }
30
+ seen.add(error);
31
+ const head = messageOfUnknown(error).trim();
32
+ const nestedCause = typeof error === "object" && "cause" in error ? Reflect.get(error, "cause") : void 0;
33
+ const tail = describeErrorChain(nestedCause, seen).trim();
34
+ if (head.length === 0) {
35
+ return tail;
36
+ }
37
+ if (tail.length === 0 || head.includes(tail)) {
38
+ return head;
39
+ }
40
+ return `${head}: ${tail}`;
41
+ }
42
+ function describeUnknownError(error) {
43
+ const chain = describeErrorChain(error, /* @__PURE__ */ new Set());
44
+ if (chain.length > 0) {
45
+ return chain;
46
+ }
47
+ if (typeof error === "object" && error !== null) {
23
48
  return "An unexpected error occurred";
24
49
  }
50
+ return String(error);
25
51
  }
26
52
  function extractErrorLogFields(error) {
27
53
  if (error instanceof Error) {
54
+ const chain2 = describeErrorChain(error, /* @__PURE__ */ new Set());
28
55
  return {
29
- error: error.message.length > 0 ? error.message : formatObjectErrorForLog(error),
56
+ error: chain2.length > 0 ? chain2 : formatObjectErrorForLog(error),
30
57
  stack: error.stack
31
58
  };
32
59
  }
33
60
  if (typeof error !== "object" || error === null) {
34
61
  return { error: String(error) };
35
62
  }
36
- const message = Reflect.get(error, "message");
37
- if (typeof message === "string" && message.length > 0) {
38
- return { error: message };
63
+ const chain = describeErrorChain(error, /* @__PURE__ */ new Set());
64
+ if (chain.length > 0) {
65
+ return { error: chain };
39
66
  }
40
67
  return { error: formatObjectErrorForLog(error) };
41
68
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/core/util/errorLogFields.ts"],"sourcesContent":["export interface ErrorLogFields {\n error: string;\n stack?: string | undefined;\n}\n\nfunction formatObjectErrorForLog(error: object): string {\n try {\n return JSON.stringify(error);\n } catch {\n return `unserialisable error (${Object.keys(error).join(', ')})`;\n }\n}\n\n/**\n * User/turn-facing message for any thrown value. Prefer Error.message / `.message`;\n * never surface developer-only strings (e.g. unserialisable dumps) in Agent Steps.\n */\nexport function describeUnknownError(error: unknown): string {\n if (error instanceof Error) {\n return error.message.length > 0 ? error.message : 'An unexpected error occurred';\n }\n if (typeof error !== 'object' || error === null) {\n return String(error);\n }\n const message: unknown = Reflect.get(error, 'message');\n if (typeof message === 'string' && message.length > 0) {\n return message;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return 'An unexpected error occurred';\n }\n}\n\nexport function extractErrorLogFields(error: unknown): ErrorLogFields {\n if (error instanceof Error) {\n return {\n error: error.message.length > 0 ? error.message : formatObjectErrorForLog(error),\n stack: error.stack,\n };\n }\n if (typeof error !== 'object' || error === null) {\n return { error: String(error) };\n }\n const message: unknown = Reflect.get(error, 'message');\n if (typeof message === 'string' && message.length > 0) {\n return { error: message };\n }\n return { error: formatObjectErrorForLog(error) };\n}\n"],"mappings":";AAKA,SAAS,wBAAwB,OAAuB;AACtD,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,yBAAyB,OAAO,KAAK,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,EAC/D;AACF;AAMO,SAAS,qBAAqB,OAAwB;AAC3D,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM,QAAQ,SAAS,IAAI,MAAM,UAAU;AAAA,EACpD;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,UAAmB,QAAQ,IAAI,OAAO,SAAS;AACrD,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GAAG;AACrD,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,sBAAsB,OAAgC;AACpE,MAAI,iBAAiB,OAAO;AAC1B,WAAO;AAAA,MACL,OAAO,MAAM,QAAQ,SAAS,IAAI,MAAM,UAAU,wBAAwB,KAAK;AAAA,MAC/E,OAAO,MAAM;AAAA,IACf;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,EAAE,OAAO,OAAO,KAAK,EAAE;AAAA,EAChC;AACA,QAAM,UAAmB,QAAQ,IAAI,OAAO,SAAS;AACrD,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GAAG;AACrD,WAAO,EAAE,OAAO,QAAQ;AAAA,EAC1B;AACA,SAAO,EAAE,OAAO,wBAAwB,KAAK,EAAE;AACjD;","names":[]}
1
+ {"version":3,"sources":["../../../src/core/util/errorLogFields.ts"],"sourcesContent":["export interface ErrorLogFields {\n error: string;\n stack?: string | undefined;\n}\n\nfunction formatObjectErrorForLog(error: object): string {\n try {\n return JSON.stringify(error);\n } catch {\n return `unserialisable error (${Object.keys(error).join(', ')})`;\n }\n}\n\n/** Single-hop message for any thrown value (no cause walk). */\nfunction messageOfUnknown(error: unknown): string {\n if (error instanceof Error) {\n return error.message;\n }\n if (typeof error !== 'object' || error === null) {\n return String(error);\n }\n const message: unknown = Reflect.get(error, 'message');\n if (typeof message === 'string' && message.length > 0) {\n return message;\n }\n try {\n return JSON.stringify(error);\n } catch {\n return '';\n }\n}\n\n/**\n * Walk `Error.cause` (and plain `{ cause }` objects) so undici-style\n * `TypeError: fetch failed` surfaces the nested `ECONNREFUSED` / cert reason.\n */\nfunction describeErrorChain(error: unknown, seen: Set<unknown>): string {\n if (error === undefined || error === null || seen.has(error)) {\n return '';\n }\n seen.add(error);\n\n const head = messageOfUnknown(error).trim();\n const nestedCause = typeof error === 'object' && 'cause' in error ? Reflect.get(error, 'cause') : undefined;\n const tail = describeErrorChain(nestedCause, seen).trim();\n\n if (head.length === 0) {\n return tail;\n }\n if (tail.length === 0 || head.includes(tail)) {\n return head;\n }\n return `${head}: ${tail}`;\n}\n\n/**\n * User/turn-facing message for any thrown value. Prefer Error.message / `.message`;\n * never surface developer-only strings (e.g. unserialisable dumps) in Agent Steps.\n * Includes nested `cause` messages when present (e.g. undici \"fetch failed\").\n */\nexport function describeUnknownError(error: unknown): string {\n const chain = describeErrorChain(error, new Set());\n if (chain.length > 0) {\n return chain;\n }\n if (typeof error === 'object' && error !== null) {\n return 'An unexpected error occurred';\n }\n return String(error);\n}\n\nexport function extractErrorLogFields(error: unknown): ErrorLogFields {\n if (error instanceof Error) {\n const chain = describeErrorChain(error, new Set());\n return {\n error: chain.length > 0 ? chain : formatObjectErrorForLog(error),\n stack: error.stack,\n };\n }\n if (typeof error !== 'object' || error === null) {\n return { error: String(error) };\n }\n const chain = describeErrorChain(error, new Set());\n if (chain.length > 0) {\n return { error: chain };\n }\n return { error: formatObjectErrorForLog(error) };\n}\n"],"mappings":";AAKA,SAAS,wBAAwB,OAAuB;AACtD,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO,yBAAyB,OAAO,KAAK,KAAK,EAAE,KAAK,IAAI,CAAC;AAAA,EAC/D;AACF;AAGA,SAAS,iBAAiB,OAAwB;AAChD,MAAI,iBAAiB,OAAO;AAC1B,WAAO,MAAM;AAAA,EACf;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,QAAM,UAAmB,QAAQ,IAAI,OAAO,SAAS;AACrD,MAAI,OAAO,YAAY,YAAY,QAAQ,SAAS,GAAG;AACrD,WAAO;AAAA,EACT;AACA,MAAI;AACF,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,SAAS,mBAAmB,OAAgB,MAA4B;AACtE,MAAI,UAAU,UAAa,UAAU,QAAQ,KAAK,IAAI,KAAK,GAAG;AAC5D,WAAO;AAAA,EACT;AACA,OAAK,IAAI,KAAK;AAEd,QAAM,OAAO,iBAAiB,KAAK,EAAE,KAAK;AAC1C,QAAM,cAAc,OAAO,UAAU,YAAY,WAAW,QAAQ,QAAQ,IAAI,OAAO,OAAO,IAAI;AAClG,QAAM,OAAO,mBAAmB,aAAa,IAAI,EAAE,KAAK;AAExD,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO;AAAA,EACT;AACA,MAAI,KAAK,WAAW,KAAK,KAAK,SAAS,IAAI,GAAG;AAC5C,WAAO;AAAA,EACT;AACA,SAAO,GAAG,IAAI,KAAK,IAAI;AACzB;AAOO,SAAS,qBAAqB,OAAwB;AAC3D,QAAM,QAAQ,mBAAmB,OAAO,oBAAI,IAAI,CAAC;AACjD,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO;AAAA,EACT;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO;AAAA,EACT;AACA,SAAO,OAAO,KAAK;AACrB;AAEO,SAAS,sBAAsB,OAAgC;AACpE,MAAI,iBAAiB,OAAO;AAC1B,UAAMA,SAAQ,mBAAmB,OAAO,oBAAI,IAAI,CAAC;AACjD,WAAO;AAAA,MACL,OAAOA,OAAM,SAAS,IAAIA,SAAQ,wBAAwB,KAAK;AAAA,MAC/D,OAAO,MAAM;AAAA,IACf;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,MAAM;AAC/C,WAAO,EAAE,OAAO,OAAO,KAAK,EAAE;AAAA,EAChC;AACA,QAAM,QAAQ,mBAAmB,OAAO,oBAAI,IAAI,CAAC;AACjD,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO,EAAE,OAAO,MAAM;AAAA,EACxB;AACA,SAAO,EAAE,OAAO,wBAAwB,KAAK,EAAE;AACjD;","names":["chain"]}
@@ -0,0 +1,10 @@
1
+ export declare function configureOutboundUrlGuard(config: {
2
+ enabled?: boolean;
3
+ allowedHosts: readonly string[];
4
+ blockedHosts: readonly string[];
5
+ }): void;
6
+ export declare function assertSafeOutboundUrl(input: string | URL | Request): Promise<void>;
7
+ export declare function ssrfFetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
8
+ /** Same as `ssrfFetch` with a 30m bodyTimeout for idle MCP SSE / streamable-HTTP. */
9
+ export declare function mcpSsrfFetch(input: string | URL | Request, init?: RequestInit): Promise<Response>;
10
+ //# sourceMappingURL=ssrfGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssrfGuard.d.ts","sourceRoot":"","sources":["../../../src/core/util/ssrfGuard.ts"],"names":[],"mappings":"AA2EA,wBAAgB,yBAAyB,CAAC,MAAM,EAAE;IAChD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC,GAAG,IAAI,CAIP;AAyGD,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAmBxF;AAmGD,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEpG;AAED,qFAAqF;AACrF,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEvG"}