@axiom-lattice/gateway 10.2.1 → 10.2.2

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.
@@ -10,7 +10,7 @@ import {
10
10
  resolveRunTaskMaxActivePerTenant,
11
11
  saveProjectFile,
12
12
  wakeRunTaskQueue
13
- } from "./chunk-67CDUQM3.mjs";
13
+ } from "./chunk-JNP5T5N4.mjs";
14
14
  import {
15
15
  getRateLimitService
16
16
  } from "./chunk-4XWI7U3L.mjs";
@@ -4141,4 +4141,4 @@ data: ${JSON.stringify(errorResponse)}
4141
4141
  export {
4142
4142
  registerA2AStandardRoutes
4143
4143
  };
4144
- //# sourceMappingURL=a2a-standard-OANCQYQ2.mjs.map
4144
+ //# sourceMappingURL=a2a-standard-HZP5F74J.mjs.map
@@ -1,6 +1,6 @@
1
1
  // src/services/open/providers/sandboxProvider.ts
2
2
  import { z } from "zod";
3
- import { sandboxLatticeManager } from "@axiom-lattice/core";
3
+ import { resolveSandboxAgentsPathAlias, sandboxLatticeManager } from "@axiom-lattice/core";
4
4
  var MAX_OUTPUT_BYTES = 64e3;
5
5
  async function resolveContextSandbox(ctx) {
6
6
  const sandboxName = ctx.runConfig?.sandboxName;
@@ -63,20 +63,21 @@ function createOpenSandboxProvider() {
63
63
  switch (actionName) {
64
64
  case "files_read": {
65
65
  const { path } = args;
66
- const file = await sandbox.file.readFile(path);
66
+ const file = await sandbox.file.readFile(resolveSandboxAgentsPathAlias(path));
67
67
  return { content: [{ type: "text", text: file.content }] };
68
68
  }
69
69
  case "files_list": {
70
70
  const { path, recursive } = args;
71
- const listing = await sandbox.file.listPath(path, { recursive });
71
+ const listing = await sandbox.file.listPath(resolveSandboxAgentsPathAlias(path), { recursive });
72
72
  return {
73
73
  content: [{ type: "text", text: JSON.stringify(listing.files) }]
74
74
  };
75
75
  }
76
76
  case "files_write": {
77
77
  const { path, content } = args;
78
- await sandbox.file.writeFile(path, content);
79
- return { content: [{ type: "text", text: `wrote ${path}` }] };
78
+ const resolvedPath = resolveSandboxAgentsPathAlias(path);
79
+ await sandbox.file.writeFile(resolvedPath, content);
80
+ return { content: [{ type: "text", text: `wrote ${resolvedPath}` }] };
80
81
  }
81
82
  case "shell_exec": {
82
83
  const { command, exec_dir, timeout } = args;
@@ -113,4 +114,4 @@ export {
113
114
  resolveContextSandbox,
114
115
  createOpenSandboxProvider
115
116
  };
116
- //# sourceMappingURL=chunk-PAKR5N7U.mjs.map
117
+ //# sourceMappingURL=chunk-IR46RQWH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/open/providers/sandboxProvider.ts"],"sourcesContent":["/**\n * Built-in sandbox provider for the Open surface: project-scoped file\n * read/list and shell execution with self-binding for sandbox tokens.\n *\n * API keys that hold sandbox-domain grants resolve the sandbox by the\n * credential's tenant + project (their project's sandbox). Sandbox tokens\n * with self-binding are resolved by the token's sandboxName field.\n */\n\nimport { z } from \"zod\";\nimport type { OpenExecutionContext, OpenExecutionResult } from \"@axiom-lattice/protocols\";\nimport { resolveSandboxAgentsPathAlias, sandboxLatticeManager } from \"@axiom-lattice/core\";\nimport type { OpenProviderDeclaration } from \"../openLattice\";\n\nconst MAX_OUTPUT_BYTES = 64000;\n\n/** Resolve the credential-addressed sandbox (self-binding when the token names one). */\nexport async function resolveContextSandbox(ctx: OpenExecutionContext) {\n const sandboxName = ctx.runConfig?.sandboxName;\n if (typeof sandboxName === \"string\" && sandboxName.length > 0) {\n try {\n return await sandboxLatticeManager.getSandbox(sandboxName);\n } catch {\n // fall through to config resolution (e.g. sandbox not materialized yet)\n }\n }\n return sandboxLatticeManager.getSandboxFromConfig({\n assistant_id: \"\",\n thread_id: \"\",\n tenantId: ctx.tenantId,\n workspaceId: ctx.workspaceId ?? \"\",\n projectId: ctx.projectId,\n });\n}\n\nexport function createOpenSandboxProvider(): OpenProviderDeclaration {\n return {\n domain: \"sandbox\",\n label: \"Sandbox\",\n source: \"builtin\",\n scopeKind: \"project\",\n actions: [\n {\n name: \"files_read\",\n label: \"Read files\",\n description: \"Read a file from the project sandbox.\",\n schema: { path: z.string().min(1) },\n annotations: { readOnlyHint: true, destructiveHint: false },\n },\n {\n name: \"files_list\",\n label: \"List files\",\n description: \"List files under a directory in the project sandbox.\",\n schema: { path: z.string(), recursive: z.boolean().optional() },\n annotations: { readOnlyHint: true, destructiveHint: false },\n },\n {\n name: \"files_write\",\n label: \"Write files\",\n description: \"Write content to a file in the project sandbox.\",\n schema: { path: z.string().min(1), content: z.string() },\n annotations: { readOnlyHint: false, destructiveHint: true },\n },\n {\n name: \"shell_exec\",\n label: \"Run shell commands\",\n description:\n \"Execute a shell command in the project sandbox. Returns stdout (up to 64KB) and exit code.\",\n schema: {\n command: z.string().min(1),\n exec_dir: z.string().optional(),\n timeout: z.number().int().min(1000).optional(),\n },\n annotations: { readOnlyHint: false, destructiveHint: true },\n },\n ],\n execute: async (\n actionName: string,\n args: unknown,\n ctx: OpenExecutionContext,\n ): Promise<OpenExecutionResult> => {\n // Self-binding is inherent: the sandbox is resolved from the credential's\n // tenant/workspace/project triple (minted server-side, unforgeable), so a\n // sandbox token can only ever reach the sandbox it was issued for.\n const sandbox = await resolveContextSandbox(ctx);\n\n switch (actionName) {\n case \"files_read\": {\n const { path } = args as { path: string };\n const file = await sandbox.file.readFile(resolveSandboxAgentsPathAlias(path));\n return { content: [{ type: \"text\", text: file.content }] };\n }\n case \"files_list\": {\n const { path, recursive } = args as { path: string; recursive?: boolean };\n const listing = await sandbox.file.listPath(resolveSandboxAgentsPathAlias(path), { recursive });\n return {\n content: [{ type: \"text\", text: JSON.stringify(listing.files) }],\n };\n }\n case \"files_write\": {\n const { path, content } = args as { path: string; content: string };\n const resolvedPath = resolveSandboxAgentsPathAlias(path);\n await sandbox.file.writeFile(resolvedPath, content);\n return { content: [{ type: \"text\", text: `wrote ${resolvedPath}` }] };\n }\n case \"shell_exec\": {\n const { command, exec_dir, timeout } = args as {\n command: string;\n exec_dir?: string;\n timeout?: number;\n };\n const result = await sandbox.shell.execCommand({\n command,\n exec_dir,\n timeoutMs: timeout ?? 120_000,\n });\n const truncated =\n result.output.length > MAX_OUTPUT_BYTES\n ? result.output.slice(0, MAX_OUTPUT_BYTES) +\n `\\n...[truncated ${result.output.length - MAX_OUTPUT_BYTES} bytes]`\n : result.output;\n return {\n content: [\n {\n type: \"text\",\n text: `exit_code: ${result.exit_code}\\n${truncated}`,\n },\n ],\n };\n }\n default:\n return {\n content: [\n { type: \"text\", text: `Unknown sandbox action: ${actionName}` },\n ],\n isError: true,\n };\n }\n },\n };\n}"],"mappings":"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","names":[]}
@@ -67,7 +67,7 @@ function getPlatformSettingsProvider() {
67
67
  // src/services/workspace-file-service.ts
68
68
  import * as fs2 from "fs/promises";
69
69
  import * as path2 from "path";
70
- import { getStoreLattice, getSandBoxManager } from "@axiom-lattice/core";
70
+ import { getStoreLattice, getSandBoxManager, resolveSandboxAgentsPathAlias } from "@axiom-lattice/core";
71
71
 
72
72
  // src/utils/path-containment.ts
73
73
  import * as fs from "fs/promises";
@@ -184,7 +184,7 @@ async function saveProjectFile(params) {
184
184
  workspaceId,
185
185
  projectId: params.projectId
186
186
  };
187
- const relativePath = pathValue ? path2.posix.join(pathValue, filename) : path2.posix.join("/project/uploads", filename);
187
+ const relativePath = pathValue ? path2.posix.join(resolveSandboxAgentsPathAlias(pathValue), filename) : path2.posix.join("/project/uploads", filename);
188
188
  const isBinary = isBinaryContentType(filename);
189
189
  const volumeBackend = await sandboxManager.getVolumeBackendForPath(
190
190
  volumeConfig,
@@ -262,4 +262,4 @@ export {
262
262
  saveProjectFile,
263
263
  formatFileRefsSection
264
264
  };
265
- //# sourceMappingURL=chunk-67CDUQM3.mjs.map
265
+ //# sourceMappingURL=chunk-JNP5T5N4.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/services/run-cancel/runCancelRuntime.ts","../src/services/run-task/runTaskQuota.ts","../src/services/run-task/runTaskRuntimeRegistry.ts","../src/services/settings/settingsRuntime.ts","../src/services/workspace-file-service.ts","../src/utils/path-containment.ts","../src/services/files/task-file-refs.ts"],"sourcesContent":["import type { RunCancelRouter } from \"./RunCancelRouter\";\n\nlet runCancelRouter: RunCancelRouter | undefined;\n\n/**\n * Register the process-wide run cancellation router.\n *\n * @param router - Router available to HTTP controllers, or `undefined` to\n * clear it during shutdown.\n *\n * @remarks\n * The router is only constructed when the gateway uses the Redis chunk source,\n * a Redis URL, and a durable run ledger. Controllers treat a missing router as\n * cancellation being unavailable rather than aborting locally.\n */\nexport function setRunCancelRouter(router?: RunCancelRouter): void {\n runCancelRouter = router;\n}\n\n/**\n * Return the registered run cancellation router, if any.\n *\n * @returns The process-wide router, or `undefined` when cancellation routing\n * is not configured for this replica.\n */\nexport function getRunCancelRouter(): RunCancelRouter | undefined {\n return runCancelRouter;\n}\n\n/**\n * Clear the process-wide run cancellation router between tests.\n *\n * @remarks Test-only helper; production code clears the router through\n * {@link setRunCancelRouter} during shutdown.\n */\nexport function resetRunCancelRouterForTests(): void {\n runCancelRouter = undefined;\n}\n","import type { PlatformSettingsProvider } from \"@axiom-lattice/protocols\";\n\n/**\n * Resolves the optional per-tenant run-queue admission limit.\n *\n * @param env Environment source; defaults to `process.env`.\n * @param settings Optional platform settings provider; its cached snapshot\n * takes precedence over the environment.\n * @returns The configured positive safe integer, or `undefined` when unlimited.\n * @remarks Unset, blank, zero, negative, fractional and unsafe values all mean\n * unlimited. An explicit platform `null` means unlimited and overrides the\n * environment. This is admission control (pending + running `run_api` rows per\n * tenant); it is not fair scheduling or per-tenant execution concurrency.\n */\nexport function resolveRunTaskMaxActivePerTenant(\n env: NodeJS.ProcessEnv = process.env,\n settings?: Pick<PlatformSettingsProvider, \"getRateLimits\">,\n): number | undefined {\n const platform = settings?.getRateLimits().maxActiveRunTasksPerTenant;\n if (platform === null) return undefined;\n if (platform !== undefined) return platform;\n return resolvePositiveInteger(env.RUN_TASK_MAX_ACTIVE_PER_TENANT);\n}\n\n/**\n * Resolves the optional per-tenant execution concurrency cap.\n *\n * @param env Environment source; defaults to `process.env`.\n * @returns The configured positive safe integer, or `undefined` when unlimited.\n * @remarks Unset, blank, zero, negative, fractional and unsafe values all mean\n * unlimited. This caps `running` `run_api` rows per tenant; it does not admit\n * or reject new work (that is {@link resolveRunTaskMaxActivePerTenant}) and it\n * is not strict round-robin fairness across tenants.\n */\nexport function resolveRunTaskMaxRunningPerTenant(\n env: NodeJS.ProcessEnv = process.env,\n): number | undefined {\n return resolvePositiveInteger(env.RUN_TASK_MAX_RUNNING_PER_TENANT);\n}\n\nfunction resolvePositiveInteger(raw: string | undefined): number | undefined {\n if (raw === undefined) return undefined;\n const parsed = Number(raw);\n return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;\n}\n","import type {\n RunTaskWakePublisher,\n RunTaskWakeStats,\n} from \"./RedisRunTaskWake\";\nimport type { RunTaskRuntime } from \"./RunTaskRuntime\";\n\n/** Structural source of wake transport posture for process metrics. */\nexport interface RunTaskWakeStatsSource {\n /** Returns a point-in-time wake posture snapshot. */\n stats(): RunTaskWakeStats;\n}\n\nlet current: RunTaskRuntime | undefined;\nlet publisher: RunTaskWakePublisher | undefined;\nlet wakeStatsSource: RunTaskWakeStatsSource | undefined;\n\n/**\n * Registers the process-local run task runtime.\n *\n * @param runtime The running runtime, or `undefined` on shutdown.\n * @remarks The registry is intentionally process-local: it can only wake the\n * worker in this process. Other replicas still discover pending runs through DB\n * polling, which remains authoritative.\n */\nexport function setRunTaskRuntime(runtime?: RunTaskRuntime): void {\n current = runtime;\n}\n\n/**\n * Returns the process-local run task runtime, when this replica runs one.\n *\n * @returns The registered runtime, or `undefined`.\n */\nexport function getRunTaskRuntime(): RunTaskRuntime | undefined {\n return current;\n}\n\n/**\n * Registers the best-effort cross-replica wake publisher.\n *\n * @param next The Redis-backed publisher, or `undefined` on shutdown.\n * @remarks API-only replicas may register a publisher without subscribing. A\n * missing publisher leaves the process-local wake (and DB polling) intact.\n */\nexport function setRunTaskWakePublisher(\n next?: RunTaskWakePublisher,\n): void {\n publisher = next;\n}\n\n/**\n * Registers the process-local wake transport used for metrics.\n *\n * @param next The wake transport, or `undefined` on shutdown.\n * @remarks Kept separate from the publish registry so API-only replicas can\n * expose publisher connection posture even without a local worker.\n */\nexport function setRunTaskWakeStatsSource(\n next?: RunTaskWakeStatsSource,\n): void {\n wakeStatsSource = next;\n}\n\n/**\n * Returns the process-local wake transport registered for metrics.\n *\n * @returns The wake stats source, or `undefined` when no Redis wake exists.\n */\nexport function getRunTaskWakeStatsSource(): RunTaskWakeStatsSource | undefined {\n return wakeStatsSource;\n}\n\n/**\n * Best-effort wake for newly created pending runs.\n *\n * @returns A promise that resolves after the local wake attempt; the Redis\n * publish continues in the background.\n * @remarks Never authoritative and never throws. The local runtime is woken\n * first so a hung or failed publisher cannot suppress it. The cross-replica\n * publish is fire-and-forget and wakes other worker replicas; this replica does\n * not depend on round-tripping its own frame. A missing runtime, missing\n * publisher, or transport failure leaves the durable run to polling.\n */\nexport async function wakeRunTaskQueue(): Promise<void> {\n try {\n await current?.wake();\n } catch {\n // Polling is the durable fallback; wake failures are best-effort.\n }\n const next = publisher;\n if (!next) return;\n try {\n void next.publish().catch(() => undefined);\n } catch {\n // Polling is the durable fallback; wake failures are best-effort.\n }\n}\n","import type { PlatformSettingsProvider } from \"@axiom-lattice/protocols\";\n\n/**\n * Composition-root provider used by legacy gateway route modules.\n *\n * New code should accept an injected {@link PlatformSettingsProvider}; this\n * process-local accessor only bridges modules that are not yet wired through\n * dependency injection.\n */\nlet current: PlatformSettingsProvider | undefined;\n\n/** Installs (or clears) the process-local platform settings provider. */\nexport function setPlatformSettingsProvider(\n provider: PlatformSettingsProvider | undefined,\n): void {\n current = provider;\n}\n\n/** Returns the process-local provider, or undefined before composition. */\nexport function getPlatformSettingsProvider():\n | PlatformSettingsProvider\n | undefined {\n return current;\n}\n","import * as fs from \"fs/promises\";\nimport * as path from \"path\";\nimport { getStoreLattice, getSandBoxManager, resolveSandboxAgentsPathAlias } from \"@axiom-lattice/core\";\nimport { isPathWithinRoot } from \"../utils/path-containment\";\n\n/** Binary file extensions handled as raw bytes rather than UTF-8 text. */\nconst BINARY_EXTENSIONS = new Set([\n \"pdf\", \"xlsx\", \"xls\", \"docx\", \"doc\", \"pptx\", \"ppt\",\n \"png\", \"jpg\", \"jpeg\", \"gif\", \"bmp\", \"ico\", \"webp\",\n \"mp3\", \"mp4\", \"webm\", \"ogg\", \"wav\", \"avi\", \"mov\",\n \"m4v\", \"mkv\", \"3gp\", \"m4a\", \"aac\", \"flac\", \"opus\", \"oga\", \"wma\", \"aiff\",\n \"zip\", \"tar\", \"gz\", \"rar\", \"7z\",\n]);\n\n/**\n * Whether a filename denotes a binary (non-UTF-8) payload, used to choose the\n * raw-bytes write path in sandbox volume backends.\n */\nexport function isBinaryContentType(filename: string): boolean {\n const ext = filename.split(\".\").pop()?.toLowerCase() || \"\";\n return BINARY_EXTENSIONS.has(ext);\n}\n\n/**\n * Filesystem root directory for a project, mirroring the layout used by the\n * workspace file backend (`/lattice_store/tenants/:tenant/workspaces/:ws/:project`).\n */\nexport function filesystemRootDirectory(\n tenantId: string,\n workspaceId: string,\n projectId: string,\n): string {\n return `/lattice_store/tenants/${tenantId}/workspaces/${workspaceId}/${projectId}`;\n}\n\n/** Raised when a file write request is rejected for path/name safety reasons. */\nexport class ProjectFileValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ProjectFileValidationError\";\n }\n}\n\n/** Parameters for writing a file into a project's workspace storage. */\nexport interface SaveProjectFileParams {\n tenantId: string;\n projectId: string;\n /**\n * Pre-resolved workspace id. When omitted it is derived from the project via\n * the project store (needed by callers that only know the project, e.g. A2A).\n */\n workspaceId?: string;\n name: string;\n bytes: Uint8Array;\n mimeType?: string;\n /** Relative subdirectory under the project root; sandbox defaults to `/project/uploads`. */\n path?: string;\n assistantId?: string;\n}\n\n/** Result of a successful project-file write. */\nexport interface SavedProjectFile {\n /** Storage path usable as a `TaskFileRef.uri` (e.g. `/project/uploads/a.pdf`). */\n uri: string;\n name: string;\n size: number;\n}\n\n/**\n * Persist file bytes into a project's workspace storage and return a URI to it.\n *\n * Supports both sandbox-backed workspaces (delegating to the sandbox/volume\n * upload API) and filesystem-backed workspaces (writing under the project root).\n * The returned URI is the storage path agents reference via {@link TaskFileRef}.\n */\nexport async function saveProjectFile(\n params: SaveProjectFileParams,\n): Promise<SavedProjectFile> {\n const workspaceStore = getStoreLattice(\"default\", \"workspace\").store;\n const projectStore = getStoreLattice(\"default\", \"project\").store;\n\n let workspaceId = params.workspaceId;\n if (!workspaceId) {\n const project = await projectStore.getProjectById(\n params.tenantId,\n params.projectId,\n );\n if (!project) {\n throw new Error(\"Project not found\");\n }\n workspaceId = project.workspaceId;\n }\n if (!workspaceId) throw new Error(\"Workspace not found\");\n\n const workspace = await workspaceStore.getWorkspaceById(\n params.tenantId,\n workspaceId,\n );\n if (!workspace) {\n throw new Error(\"Workspace not found\");\n }\n\n const buffer = Buffer.from(params.bytes);\n const filename = params.name || \"file\";\n const pathValue = params.path;\n\n // Validate the untrusted filename before any path join or disk I/O, equally\n // for sandbox and filesystem branches: reject anything that is not a bare\n // basename on either path flavor (blocks \"../../etc/passwd\", \"a\\\\b\", \"..\").\n if (\n path.basename(filename) !== filename ||\n path.posix.basename(filename) !== filename ||\n filename === \"..\"\n ) {\n throw new ProjectFileValidationError(\"Invalid filename\");\n }\n\n if (workspace.storageType === \"sandbox\") {\n const sandboxManager = getSandBoxManager();\n const volumeConfig = {\n assistant_id: params.assistantId || \"\",\n thread_id: \"\",\n tenantId: params.tenantId,\n workspaceId,\n projectId: params.projectId,\n };\n\n const relativePath = pathValue\n ? path.posix.join(resolveSandboxAgentsPathAlias(pathValue), filename)\n : path.posix.join(\"/project/uploads\", filename);\n\n const isBinary = isBinaryContentType(filename);\n const volumeBackend = await sandboxManager.getVolumeBackendForPath(\n volumeConfig,\n relativePath,\n );\n if (volumeBackend && !isBinary) {\n await volumeBackend.write(relativePath, buffer.toString(\"utf-8\"));\n } else if (volumeBackend && volumeBackend.writeBinary) {\n await volumeBackend.writeBinary(relativePath, buffer);\n } else {\n const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);\n await sandbox.file.uploadFile({ file: relativePath, data: buffer });\n }\n\n return { uri: relativePath, name: filename, size: buffer.length };\n }\n\n const rootDir = filesystemRootDirectory(\n params.tenantId,\n workspaceId,\n params.projectId,\n );\n await fs.mkdir(rootDir, { recursive: true });\n const relativeDirectory = pathValue?.replace(/^[/\\\\]+/, \"\") || \".\";\n const targetDir = path.resolve(rootDir, relativeDirectory);\n if (!isPathWithinRoot(path.resolve(rootDir), targetDir)) {\n throw new ProjectFileValidationError(\n \"Upload path must stay within the project directory\",\n );\n }\n await fs.mkdir(targetDir, { recursive: true });\n\n const [resolvedRoot, resolvedTargetDir] = await Promise.all([\n fs.realpath(rootDir),\n fs.realpath(targetDir),\n ]);\n if (!isPathWithinRoot(resolvedRoot, resolvedTargetDir)) {\n throw new ProjectFileValidationError(\n \"Upload path must stay within the project directory\",\n );\n }\n\n const targetPath = path.join(resolvedTargetDir, filename);\n await fs.writeFile(targetPath, buffer);\n const stat = await fs.stat(targetPath);\n\n const uri = path.posix.join(\"/\", relativeDirectory, filename);\n return { uri, name: filename, size: stat.size };\n}\n","import * as fs from \"node:fs/promises\";\nimport * as path from \"node:path\";\n\nexport class PathOutsideRootError extends Error {\n constructor(\n public readonly rootPath: string,\n public readonly targetPath: string,\n ) {\n super(`Path is outside the allowed root: ${targetPath}`);\n this.name = \"PathOutsideRootError\";\n }\n}\n\nexport function isPathWithinRoot(rootPath: string, targetPath: string): boolean {\n const relative = path.relative(rootPath, targetPath);\n return (\n relative === \"\" ||\n (relative !== \"..\" &&\n !relative.startsWith(`..${path.sep}`) &&\n !path.isAbsolute(relative))\n );\n}\n\nexport async function resolvePathWithinRoot(\n rootPath: string,\n targetPath: string,\n): Promise<{ rootPath: string; targetPath: string }> {\n const [resolvedRoot, resolvedTarget] = await Promise.all([\n fs.realpath(rootPath),\n fs.realpath(targetPath),\n ]);\n\n if (!isPathWithinRoot(resolvedRoot, resolvedTarget)) {\n throw new PathOutsideRootError(resolvedRoot, resolvedTarget);\n }\n\n return { rootPath: resolvedRoot, targetPath: resolvedTarget };\n}\n","import type { TaskFileRef } from \"@axiom-lattice/protocols\";\n\n/** Render task file references for Agent-visible message text. */\nexport function formatFileRefsSection(refs: TaskFileRef[]): string {\n const lines = refs.map((ref) => {\n const label = ref.name ?? ref.uri;\n const mime = ref.mimeType ? ` (${ref.mimeType})` : \"\";\n return `- ${label}${mime}: ${ref.uri}`;\n });\n return [\"[附件]\", ...lines].join(\"\\n\");\n}\n"],"mappings":"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","names":["current","fs","path","relative","stat"]}
package/dist/index.js CHANGED
@@ -695,7 +695,7 @@ async function saveProjectFile(params) {
695
695
  workspaceId,
696
696
  projectId: params.projectId
697
697
  };
698
- const relativePath = pathValue ? path3.posix.join(pathValue, filename) : path3.posix.join("/project/uploads", filename);
698
+ const relativePath = pathValue ? path3.posix.join((0, import_core38.resolveSandboxAgentsPathAlias)(pathValue), filename) : path3.posix.join("/project/uploads", filename);
699
699
  const isBinary = isBinaryContentType(filename);
700
700
  const volumeBackend = await sandboxManager.getVolumeBackendForPath(
701
701
  volumeConfig,
@@ -9438,20 +9438,21 @@ function createOpenSandboxProvider() {
9438
9438
  switch (actionName) {
9439
9439
  case "files_read": {
9440
9440
  const { path: path7 } = args;
9441
- const file = await sandbox.file.readFile(path7);
9441
+ const file = await sandbox.file.readFile((0, import_core120.resolveSandboxAgentsPathAlias)(path7));
9442
9442
  return { content: [{ type: "text", text: file.content }] };
9443
9443
  }
9444
9444
  case "files_list": {
9445
9445
  const { path: path7, recursive } = args;
9446
- const listing = await sandbox.file.listPath(path7, { recursive });
9446
+ const listing = await sandbox.file.listPath((0, import_core120.resolveSandboxAgentsPathAlias)(path7), { recursive });
9447
9447
  return {
9448
9448
  content: [{ type: "text", text: JSON.stringify(listing.files) }]
9449
9449
  };
9450
9450
  }
9451
9451
  case "files_write": {
9452
9452
  const { path: path7, content } = args;
9453
- await sandbox.file.writeFile(path7, content);
9454
- return { content: [{ type: "text", text: `wrote ${path7}` }] };
9453
+ const resolvedPath = (0, import_core120.resolveSandboxAgentsPathAlias)(path7);
9454
+ await sandbox.file.writeFile(resolvedPath, content);
9455
+ return { content: [{ type: "text", text: `wrote ${resolvedPath}` }] };
9455
9456
  }
9456
9457
  case "shell_exec": {
9457
9458
  const { command, exec_dir, timeout } = args;
@@ -9506,7 +9507,8 @@ function extractBearer2(request) {
9506
9507
  function safePath(raw) {
9507
9508
  if (typeof raw !== "string" || raw.length === 0) return null;
9508
9509
  if (raw.includes("..")) return null;
9509
- return raw.startsWith("/") ? raw : `/${raw}`;
9510
+ const aliased = (0, import_core121.resolveSandboxAgentsPathAlias)(raw);
9511
+ return aliased.startsWith("/") ? aliased : `/${aliased}`;
9510
9512
  }
9511
9513
  function registerOpenFilesRoutes(app2, deps = {}) {
9512
9514
  const openAuth = deps.openAuth ?? new OpenCredentialService(
@@ -21279,7 +21281,8 @@ function registerSandboxProxyRoutes(app2) {
21279
21281
  vmIsolation
21280
21282
  });
21281
21283
  try {
21282
- const resolvedPath = filePath.startsWith("/") ? filePath : `/${filePath}`;
21284
+ const aliasedPath = (0, import_core37.resolveSandboxAgentsPathAlias)(filePath);
21285
+ const resolvedPath = aliasedPath.startsWith("/") ? aliasedPath : `/${aliasedPath}`;
21283
21286
  const filename = getFilenameFromPath(resolvedPath);
21284
21287
  const inferredContentType = getContentTypeFromFilename(filename);
21285
21288
  try {
@@ -22039,7 +22042,10 @@ var WorkspaceController = class {
22039
22042
  if (!workspace) {
22040
22043
  throw new Error("Workspace not found");
22041
22044
  }
22042
- console.log(`[getBackend] storageType=${workspace.storageType} filePath=${filePath}`);
22045
+ const resolvedPath = filePath !== void 0 && workspace.storageType === "sandbox" ? (0, import_core43.resolveSandboxAgentsPathAlias)(filePath) : filePath;
22046
+ console.log(
22047
+ `[getBackend] storageType=${workspace.storageType} filePath=${filePath} resolvedPath=${resolvedPath}`
22048
+ );
22043
22049
  if (workspace.storageType === "sandbox") {
22044
22050
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22045
22051
  const volumeConfig = {
@@ -22049,11 +22055,11 @@ var WorkspaceController = class {
22049
22055
  workspaceId,
22050
22056
  projectId
22051
22057
  };
22052
- console.log(`[getBackend] trying volume backend for path=${filePath} assistant_id=${assistantId}`);
22053
- const volumeBackend = await sandboxManager.getVolumeBackendForPath(volumeConfig, filePath || "/project");
22058
+ console.log(`[getBackend] trying volume backend for path=${resolvedPath} assistant_id=${assistantId}`);
22059
+ const volumeBackend = await sandboxManager.getVolumeBackendForPath(volumeConfig, resolvedPath || "/project");
22054
22060
  if (volumeBackend) {
22055
22061
  console.log(`[getBackend] using VolumeFilesystem`);
22056
- return { backend: volumeBackend, workspace };
22062
+ return { backend: volumeBackend, workspace, resolvedPath };
22057
22063
  }
22058
22064
  console.log(`[getBackend] volume not found, falling back to SandboxFilesystem`);
22059
22065
  const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
@@ -22062,7 +22068,8 @@ var WorkspaceController = class {
22062
22068
  backend: new import_core42.SandboxFilesystem({
22063
22069
  sandboxInstance: sandbox
22064
22070
  }),
22065
- workspace
22071
+ workspace,
22072
+ resolvedPath
22066
22073
  };
22067
22074
  }
22068
22075
  const rootDir = filesystemRootDirectory(
@@ -22076,7 +22083,8 @@ var WorkspaceController = class {
22076
22083
  rootDir,
22077
22084
  virtualMode: true
22078
22085
  }),
22079
- workspace
22086
+ workspace,
22087
+ resolvedPath
22080
22088
  };
22081
22089
  }
22082
22090
  async downloadFile(request, reply) {
@@ -22088,8 +22096,14 @@ var WorkspaceController = class {
22088
22096
  return reply.status(400).send({ success: false, error: "Path is required" });
22089
22097
  }
22090
22098
  try {
22091
- const { workspace } = await this.getBackend(tenantId, workspaceId, projectId, assistantId);
22092
- const resolvedPath = filePath;
22099
+ const { workspace, resolvedPath: aliasedPath } = await this.getBackend(
22100
+ tenantId,
22101
+ workspaceId,
22102
+ projectId,
22103
+ assistantId,
22104
+ filePath
22105
+ );
22106
+ const resolvedPath = aliasedPath ?? filePath;
22093
22107
  if (workspace.storageType === "sandbox") {
22094
22108
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22095
22109
  const volumeConfig = {
@@ -22151,6 +22165,7 @@ var WorkspaceController = class {
22151
22165
  if (!project || project.workspaceId !== workspaceId) {
22152
22166
  throw new FolderDownloadError(404, "PROJECT_NOT_FOUND", "Project not found");
22153
22167
  }
22168
+ const resolvedFolderPath = storedWorkspace.storageType === "sandbox" ? (0, import_core43.resolveSandboxAgentsPathAlias)(folderPath) : folderPath;
22154
22169
  if (storedWorkspace.storageType === "sandbox") {
22155
22170
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22156
22171
  const volumeConfig = {
@@ -22166,7 +22181,7 @@ var WorkspaceController = class {
22166
22181
  }
22167
22182
  let packed;
22168
22183
  try {
22169
- packed = await sandbox.packDirectory(folderPath);
22184
+ packed = await sandbox.packDirectory(resolvedFolderPath);
22170
22185
  if (packed.size === void 0) {
22171
22186
  throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not report packed archive size");
22172
22187
  }
@@ -22190,7 +22205,7 @@ var WorkspaceController = class {
22190
22205
  workspaceId,
22191
22206
  projectId,
22192
22207
  assistantId,
22193
- folderPath
22208
+ resolvedFolderPath
22194
22209
  );
22195
22210
  if (!backend.lsInfo) {
22196
22211
  throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not support folder downloads");
@@ -22198,7 +22213,7 @@ var WorkspaceController = class {
22198
22213
  let listEntries2;
22199
22214
  if (workspace.storageType !== "sandbox") {
22200
22215
  const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
22201
- const target = path4.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
22216
+ const target = path4.resolve(root, `.${resolvedFolderPath.startsWith("/") ? resolvedFolderPath : `/${resolvedFolderPath}`}`);
22202
22217
  const contained = await resolvePathWithinRoot(root, target).catch((error) => {
22203
22218
  if (error instanceof Error && error.code === "ENOENT") {
22204
22219
  throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
@@ -22217,7 +22232,7 @@ var WorkspaceController = class {
22217
22232
  if (!stat3.isDirectory()) {
22218
22233
  throw new FolderDownloadError(400, "NOT_A_FOLDER", "Path is not a folder");
22219
22234
  }
22220
- } else if (folderPath) {
22235
+ } else if (resolvedFolderPath) {
22221
22236
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22222
22237
  const volumeConfig = {
22223
22238
  assistant_id: assistantId || "",
@@ -22226,11 +22241,11 @@ var WorkspaceController = class {
22226
22241
  workspaceId,
22227
22242
  projectId
22228
22243
  };
22229
- const volume = await sandboxManager.getVolumeBackendForPath(volumeConfig, folderPath);
22244
+ const volume = await sandboxManager.getVolumeBackendForPath(volumeConfig, resolvedFolderPath);
22230
22245
  if (!volume) {
22231
22246
  const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
22232
22247
  try {
22233
- await sandbox.file.listPath(folderPath, { recursive: false });
22248
+ await sandbox.file.listPath(resolvedFolderPath, { recursive: false });
22234
22249
  } catch (error) {
22235
22250
  const code = error instanceof Error ? error.code : void 0;
22236
22251
  const message = error instanceof Error ? error.message : String(error);
@@ -22250,7 +22265,7 @@ var WorkspaceController = class {
22250
22265
  if (!listEntries2) {
22251
22266
  listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
22252
22267
  }
22253
- const folderName = path4.basename(folderPath.replace(/\/$/, "")) || "folder";
22268
+ const folderName = path4.basename(resolvedFolderPath.replace(/\/$/, "")) || "folder";
22254
22269
  const prefix = `${folderName}/`;
22255
22270
  const entries = [{ name: prefix, data: Buffer.alloc(0), directory: true }];
22256
22271
  let totalSize = 0;
@@ -22362,7 +22377,7 @@ var WorkspaceController = class {
22362
22377
  throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
22363
22378
  }
22364
22379
  const childPath = child.path;
22365
- if (childPath.includes("\\") || childPath.split("/").includes("..") || !childPath.startsWith(folderPath.endsWith("/") ? folderPath : `${folderPath}/`)) {
22380
+ if (childPath.includes("\\") || childPath.split("/").includes("..") || !childPath.startsWith(resolvedFolderPath.endsWith("/") ? resolvedFolderPath : `${resolvedFolderPath}/`)) {
22366
22381
  throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains an unsafe child path");
22367
22382
  }
22368
22383
  if (workspace.storageType !== "sandbox") {
@@ -22399,10 +22414,10 @@ var WorkspaceController = class {
22399
22414
  }
22400
22415
  };
22401
22416
  if (workspace.storageType === "sandbox") {
22402
- await collect(folderPath, prefix);
22417
+ await collect(resolvedFolderPath, prefix);
22403
22418
  } else {
22404
22419
  const root = filesystemRoot;
22405
- const target = path4.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
22420
+ const target = path4.resolve(root, `.${resolvedFolderPath.startsWith("/") ? resolvedFolderPath : `/${resolvedFolderPath}`}`);
22406
22421
  await collectFilesystem(target, prefix);
22407
22422
  }
22408
22423
  const zip = createZip(entries);
@@ -22430,8 +22445,14 @@ var WorkspaceController = class {
22430
22445
  return reply.status(400).send({ success: false, error: "Path is required" });
22431
22446
  }
22432
22447
  try {
22433
- const { workspace } = await this.getBackend(tenantId, workspaceId, projectId, assistantId);
22434
- const resolvedPath = filePath;
22448
+ const { workspace, resolvedPath: aliasedPath } = await this.getBackend(
22449
+ tenantId,
22450
+ workspaceId,
22451
+ projectId,
22452
+ assistantId,
22453
+ filePath
22454
+ );
22455
+ const resolvedPath = aliasedPath ?? filePath;
22435
22456
  if (workspace.storageType === "sandbox") {
22436
22457
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22437
22458
  const volumeConfig = {
@@ -22529,9 +22550,10 @@ var WorkspaceController = class {
22529
22550
  const path7 = request.query.path || "/";
22530
22551
  const assistantId = request.query.assistantId;
22531
22552
  console.log(`[listPath] tenantId=${tenantId} workspaceId=${workspaceId} projectId=${projectId} path=${path7} assistantId=${assistantId}`);
22532
- const { backend } = await this.getBackend(tenantId, workspaceId, projectId, assistantId, path7);
22533
- console.log(`[listPath] backend type=${backend.constructor.name} calling lsInfo(${path7})`);
22534
- const files = await backend.lsInfo(path7);
22553
+ const { backend, resolvedPath } = await this.getBackend(tenantId, workspaceId, projectId, assistantId, path7);
22554
+ const targetPath = resolvedPath ?? path7;
22555
+ console.log(`[listPath] backend type=${backend.constructor.name} calling lsInfo(${targetPath})`);
22556
+ const files = await backend.lsInfo(targetPath);
22535
22557
  console.log(`[listPath] result count=${files.length}`);
22536
22558
  return { success: true, data: files };
22537
22559
  }
@@ -22539,8 +22561,8 @@ var WorkspaceController = class {
22539
22561
  const tenantId = this.getTenantId(request);
22540
22562
  const { workspaceId, projectId } = request.params;
22541
22563
  const { path: path7, offset = 0, limit = 1e3, assistantId } = request.query;
22542
- const { backend } = await this.getBackend(tenantId, workspaceId, projectId, assistantId, path7);
22543
- const content = await backend.read(path7, Number(offset), Number(limit));
22564
+ const { backend, resolvedPath } = await this.getBackend(tenantId, workspaceId, projectId, assistantId, path7);
22565
+ const content = await backend.read(resolvedPath ?? path7, Number(offset), Number(limit));
22544
22566
  return { success: true, data: { content, offset, limit } };
22545
22567
  }
22546
22568
  /**