@axiom-lattice/gateway 10.2.1 → 10.2.3

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.
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-JUA3VJE7.mjs";
5
5
  import {
6
6
  resolveContextSandbox
7
- } from "./chunk-PAKR5N7U.mjs";
7
+ } from "./chunk-IR46RQWH.mjs";
8
8
  import "./chunk-EXPERMZR.mjs";
9
9
  import {
10
10
  openLattice
@@ -12,7 +12,7 @@ import {
12
12
  import "./chunk-NFEGQTCC.mjs";
13
13
 
14
14
  // src/routes/open-files.ts
15
- import { getStoreLattice } from "@axiom-lattice/core";
15
+ import { getStoreLattice, resolveSandboxAgentsPathAlias } from "@axiom-lattice/core";
16
16
  function extractBearer(request) {
17
17
  const header = request.headers.authorization;
18
18
  if (!header?.startsWith("Bearer ")) return void 0;
@@ -21,7 +21,8 @@ function extractBearer(request) {
21
21
  function safePath(raw) {
22
22
  if (typeof raw !== "string" || raw.length === 0) return null;
23
23
  if (raw.includes("..")) return null;
24
- return raw.startsWith("/") ? raw : `/${raw}`;
24
+ const aliased = resolveSandboxAgentsPathAlias(raw);
25
+ return aliased.startsWith("/") ? aliased : `/${aliased}`;
25
26
  }
26
27
  function registerOpenFilesRoutes(app, deps = {}) {
27
28
  const openAuth = deps.openAuth ?? new OpenCredentialService(
@@ -143,4 +144,4 @@ function registerOpenFilesRoutes(app, deps = {}) {
143
144
  export {
144
145
  registerOpenFilesRoutes
145
146
  };
146
- //# sourceMappingURL=open-files-JPB2NBL6.mjs.map
147
+ //# sourceMappingURL=open-files-WK4SQHE3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/routes/open-files.ts"],"sourcesContent":["/**\n * Open sandbox file transfer (binary sugar): download/upload raw bytes.\n *\n * MCP is awkward for binary transfer, so these REST sugar endpoints exist\n * alongside /open/mcp. They share the same credential pipeline and\n * authorization kernel — only the transport differs.\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport { getStoreLattice, resolveSandboxAgentsPathAlias } from \"@axiom-lattice/core\";\nimport type { A2AApiKeyStore, OpenAuditStore, ProjectStore } from \"@axiom-lattice/protocols\";\nimport { OpenCredentialService } from \"../services/open/openCredentialService\";\nimport { openLattice } from \"../services/open/openLattice\";\nimport { resolveContextSandbox } from \"../services/open/providers/sandboxProvider\";\nimport { OpenAuditService } from \"../services/open/openAuditService\";\n\nexport interface OpenFilesRouteDeps {\n openAuth?: OpenCredentialService;\n audit?: OpenAuditService;\n}\n\nfunction extractBearer(request: FastifyRequest): string | undefined {\n const header = request.headers.authorization;\n if (!header?.startsWith(\"Bearer \")) return undefined;\n return header.slice(\"Bearer \".length).trim() || undefined;\n}\n\nfunction safePath(raw: unknown): string | null {\n if (typeof raw !== \"string\" || raw.length === 0) return null;\n if (raw.includes(\"..\")) return null;\n const aliased = resolveSandboxAgentsPathAlias(raw);\n return aliased.startsWith(\"/\") ? aliased : `/${aliased}`;\n}\n\nexport function registerOpenFilesRoutes(\n app: FastifyInstance,\n deps: OpenFilesRouteDeps = {},\n): void {\n const openAuth =\n deps.openAuth ??\n new OpenCredentialService(\n getStoreLattice(\"default\", \"a2aApiKey\").store as A2AApiKeyStore,\n getStoreLattice(\"default\", \"project\").store as ProjectStore,\n );\n\n const audit =\n deps.audit ??\n (() => {\n try {\n const store = getStoreLattice(\"default\", \"openAudit\").store as OpenAuditStore;\n return new OpenAuditService(store);\n } catch {\n return undefined;\n }\n })();\n\n // Buffer raw octet-stream bodies so upload can accept arbitrary bytes.\n app.addContentTypeParser(\n \"application/octet-stream\",\n { parseAs: \"buffer\" },\n (_req, body, done) => done(null, body),\n );\n\n app.get(\"/open/sandbox/files/download\", async (request: FastifyRequest, reply: FastifyReply) => {\n const context = await openAuth.resolve(extractBearer(request));\n if (!context) return reply.status(401).send({ error: \"AUTH_FAILED\" });\n const path = safePath((request.query as { path?: string }).path);\n if (!path) return reply.status(400).send({ error: \"PATH_REQUIRED\" });\n if (!openLattice.isAllowed(context.grants, \"sandbox_files_read\")) {\n return reply.status(403).send({ error: \"GRANT_DENIED\" });\n }\n const startedAt = Date.now();\n try {\n const sandbox = await resolveContextSandbox({\n tenantId: context.tenantId,\n projectId: context.projectId,\n workspaceId: context.workspaceId,\n runConfig: {},\n });\n const data = await sandbox.file.downloadFile({ file: path });\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_read\",\n args: { path },\n status: \"ok\",\n durationMs: Date.now() - startedAt,\n });\n return reply\n .status(200)\n .type(\"application/octet-stream\")\n .header(\"Content-Disposition\", \"attachment\")\n .send(data);\n } catch (err) {\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_read\",\n args: { path },\n status: \"error\",\n errorCode: \"EXECUTION_ERROR\",\n durationMs: Date.now() - startedAt,\n });\n return reply.status(502).send({\n error: err instanceof Error ? err.message : \"SANDBOX_ERROR\",\n });\n }\n });\n\n app.post(\"/open/sandbox/files/upload\", async (request: FastifyRequest, reply: FastifyReply) => {\n const context = await openAuth.resolve(extractBearer(request));\n if (!context) return reply.status(401).send({ error: \"AUTH_FAILED\" });\n const path = safePath((request.query as { path?: string }).path);\n if (!path) return reply.status(400).send({ error: \"PATH_REQUIRED\" });\n if (!openLattice.isAllowed(context.grants, \"sandbox_files_write\")) {\n return reply.status(403).send({ error: \"GRANT_DENIED\" });\n }\n const body = request.body;\n if (!Buffer.isBuffer(body)) {\n return reply.status(400).send({ error: \"BINARY_BODY_REQUIRED\" });\n }\n const startedAt = Date.now();\n try {\n const sandbox = await resolveContextSandbox({\n tenantId: context.tenantId,\n projectId: context.projectId,\n workspaceId: context.workspaceId,\n runConfig: {},\n });\n await sandbox.file.uploadFile({ file: path, data: body });\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_write\",\n args: { path, bytes: body.length },\n status: \"ok\",\n durationMs: Date.now() - startedAt,\n });\n return reply.status(200).send({ success: true, path });\n } catch (err) {\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_write\",\n args: { path, bytes: body.length },\n status: \"error\",\n errorCode: \"EXECUTION_ERROR\",\n durationMs: Date.now() - startedAt,\n });\n return reply.status(502).send({\n error: err instanceof Error ? err.message : \"SANDBOX_ERROR\",\n });\n }\n });\n}\n"],"mappings":"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","names":[]}
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  createOpenSandboxProvider,
3
3
  resolveContextSandbox
4
- } from "./chunk-PAKR5N7U.mjs";
4
+ } from "./chunk-IR46RQWH.mjs";
5
5
  import "./chunk-NFEGQTCC.mjs";
6
6
  export {
7
7
  createOpenSandboxProvider,
8
8
  resolveContextSandbox
9
9
  };
10
- //# sourceMappingURL=sandboxProvider-FILMFVIM.mjs.map
10
+ //# sourceMappingURL=sandboxProvider-LA6B5SKU.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/gateway",
3
- "version": "10.2.1",
3
+ "version": "10.2.3",
4
4
  "main": "dist/index.js",
5
5
  "files": [
6
6
  "dist"
@@ -59,8 +59,8 @@
59
59
  "zod": "4.6.7",
60
60
  "@axiom-lattice/auth-oidc": "0.2.0",
61
61
  "@axiom-lattice/cli-a2a": "0.2.33",
62
- "@axiom-lattice/core": "12.2.1",
63
- "@axiom-lattice/pg-stores": "5.1.1",
62
+ "@axiom-lattice/core": "12.2.2",
63
+ "@axiom-lattice/pg-stores": "5.1.2",
64
64
  "@axiom-lattice/protocols": "7.2.0",
65
65
  "@axiom-lattice/queue-redis": "1.1.33"
66
66
  },
@@ -1 +0,0 @@
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 } 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(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"]}
@@ -1 +0,0 @@
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 { 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(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(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 await sandbox.file.writeFile(path, content);\n return { content: [{ type: \"text\", text: `wrote ${path}` }] };\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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/routes/open-files.ts"],"sourcesContent":["/**\n * Open sandbox file transfer (binary sugar): download/upload raw bytes.\n *\n * MCP is awkward for binary transfer, so these REST sugar endpoints exist\n * alongside /open/mcp. They share the same credential pipeline and\n * authorization kernel — only the transport differs.\n */\n\nimport type { FastifyInstance, FastifyReply, FastifyRequest } from \"fastify\";\nimport { getStoreLattice } from \"@axiom-lattice/core\";\nimport type { A2AApiKeyStore, OpenAuditStore, ProjectStore } from \"@axiom-lattice/protocols\";\nimport { OpenCredentialService } from \"../services/open/openCredentialService\";\nimport { openLattice } from \"../services/open/openLattice\";\nimport { resolveContextSandbox } from \"../services/open/providers/sandboxProvider\";\nimport { OpenAuditService } from \"../services/open/openAuditService\";\n\nexport interface OpenFilesRouteDeps {\n openAuth?: OpenCredentialService;\n audit?: OpenAuditService;\n}\n\nfunction extractBearer(request: FastifyRequest): string | undefined {\n const header = request.headers.authorization;\n if (!header?.startsWith(\"Bearer \")) return undefined;\n return header.slice(\"Bearer \".length).trim() || undefined;\n}\n\nfunction safePath(raw: unknown): string | null {\n if (typeof raw !== \"string\" || raw.length === 0) return null;\n if (raw.includes(\"..\")) return null;\n return raw.startsWith(\"/\") ? raw : `/${raw}`;\n}\n\nexport function registerOpenFilesRoutes(\n app: FastifyInstance,\n deps: OpenFilesRouteDeps = {},\n): void {\n const openAuth =\n deps.openAuth ??\n new OpenCredentialService(\n getStoreLattice(\"default\", \"a2aApiKey\").store as A2AApiKeyStore,\n getStoreLattice(\"default\", \"project\").store as ProjectStore,\n );\n\n const audit =\n deps.audit ??\n (() => {\n try {\n const store = getStoreLattice(\"default\", \"openAudit\").store as OpenAuditStore;\n return new OpenAuditService(store);\n } catch {\n return undefined;\n }\n })();\n\n // Buffer raw octet-stream bodies so upload can accept arbitrary bytes.\n app.addContentTypeParser(\n \"application/octet-stream\",\n { parseAs: \"buffer\" },\n (_req, body, done) => done(null, body),\n );\n\n app.get(\"/open/sandbox/files/download\", async (request: FastifyRequest, reply: FastifyReply) => {\n const context = await openAuth.resolve(extractBearer(request));\n if (!context) return reply.status(401).send({ error: \"AUTH_FAILED\" });\n const path = safePath((request.query as { path?: string }).path);\n if (!path) return reply.status(400).send({ error: \"PATH_REQUIRED\" });\n if (!openLattice.isAllowed(context.grants, \"sandbox_files_read\")) {\n return reply.status(403).send({ error: \"GRANT_DENIED\" });\n }\n const startedAt = Date.now();\n try {\n const sandbox = await resolveContextSandbox({\n tenantId: context.tenantId,\n projectId: context.projectId,\n workspaceId: context.workspaceId,\n runConfig: {},\n });\n const data = await sandbox.file.downloadFile({ file: path });\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_read\",\n args: { path },\n status: \"ok\",\n durationMs: Date.now() - startedAt,\n });\n return reply\n .status(200)\n .type(\"application/octet-stream\")\n .header(\"Content-Disposition\", \"attachment\")\n .send(data);\n } catch (err) {\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_read\",\n args: { path },\n status: \"error\",\n errorCode: \"EXECUTION_ERROR\",\n durationMs: Date.now() - startedAt,\n });\n return reply.status(502).send({\n error: err instanceof Error ? err.message : \"SANDBOX_ERROR\",\n });\n }\n });\n\n app.post(\"/open/sandbox/files/upload\", async (request: FastifyRequest, reply: FastifyReply) => {\n const context = await openAuth.resolve(extractBearer(request));\n if (!context) return reply.status(401).send({ error: \"AUTH_FAILED\" });\n const path = safePath((request.query as { path?: string }).path);\n if (!path) return reply.status(400).send({ error: \"PATH_REQUIRED\" });\n if (!openLattice.isAllowed(context.grants, \"sandbox_files_write\")) {\n return reply.status(403).send({ error: \"GRANT_DENIED\" });\n }\n const body = request.body;\n if (!Buffer.isBuffer(body)) {\n return reply.status(400).send({ error: \"BINARY_BODY_REQUIRED\" });\n }\n const startedAt = Date.now();\n try {\n const sandbox = await resolveContextSandbox({\n tenantId: context.tenantId,\n projectId: context.projectId,\n workspaceId: context.workspaceId,\n runConfig: {},\n });\n await sandbox.file.uploadFile({ file: path, data: body });\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_write\",\n args: { path, bytes: body.length },\n status: \"ok\",\n durationMs: Date.now() - startedAt,\n });\n return reply.status(200).send({ success: true, path });\n } catch (err) {\n await audit?.record({\n tenantId: context.tenantId,\n credentialId: context.credentialId,\n credentialKind: context.kind,\n projectId: context.projectId,\n domain: \"sandbox\",\n action: \"files_write\",\n args: { path, bytes: body.length },\n status: \"error\",\n errorCode: \"EXECUTION_ERROR\",\n durationMs: Date.now() - startedAt,\n });\n return reply.status(502).send({\n error: err instanceof Error ? err.message : \"SANDBOX_ERROR\",\n });\n }\n });\n}\n"],"mappings":"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","names":[]}