@axiom-lattice/gateway 10.2.0 → 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.
@@ -2,7 +2,7 @@ import {
2
2
  Logger,
3
3
  getUpdates,
4
4
  sendMessage
5
- } from "./chunk-6CUQGDJI.mjs";
5
+ } from "./chunk-ELYWN3TJ.mjs";
6
6
  import "./chunk-NFEGQTCC.mjs";
7
7
 
8
8
  // src/channels/wechat/WechatChannelAdapter.ts
@@ -304,4 +304,4 @@ var wechatChannelAdapter = {
304
304
  export {
305
305
  wechatChannelAdapter
306
306
  };
307
- //# sourceMappingURL=WechatChannelAdapter-QLPJJD6S.mjs.map
307
+ //# sourceMappingURL=WechatChannelAdapter-EEETBUR3.mjs.map
@@ -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
@@ -2,9 +2,12 @@
2
2
  import pino from "pino";
3
3
  import "pino-pretty";
4
4
  import "pino-roll";
5
+ function resolveLegacyLogFile(env = process.env) {
6
+ const file = env.LOG_FILE?.trim();
7
+ return file ? file : null;
8
+ }
5
9
  var PinoLoggerFactory = class _PinoLoggerFactory {
6
10
  constructor() {
7
- const isProd = process.env.NODE_ENV === "production";
8
11
  const loggerConfig = {
9
12
  // 自定义时间戳格式
10
13
  timestamp: () => `,"@timestamp":"${(/* @__PURE__ */ new Date()).toISOString()}"`,
@@ -25,14 +28,15 @@ var PinoLoggerFactory = class _PinoLoggerFactory {
25
28
  }
26
29
  }
27
30
  };
28
- if (isProd) {
31
+ const logFile = resolveLegacyLogFile(process.env);
32
+ if (logFile) {
29
33
  try {
30
34
  this.pinoLogger = pino(
31
35
  loggerConfig,
32
36
  pino.transport({
33
37
  target: "pino-roll",
34
38
  options: {
35
- file: "./logs/fin_ai_graph_server",
39
+ file: logFile,
36
40
  frequency: "daily",
37
41
  mkdir: true
38
42
  }
@@ -235,4 +239,4 @@ export {
235
239
  getQrCode,
236
240
  getQrCodeStatus
237
241
  };
238
- //# sourceMappingURL=chunk-6CUQGDJI.mjs.map
242
+ //# sourceMappingURL=chunk-ELYWN3TJ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/logger/Logger.ts","../src/channels/wechat/wechat-client.ts"],"sourcesContent":["import pino from \"pino\";\nimport \"pino-pretty\";\nimport \"pino-roll\";\nimport { AsyncLocalStorage } from \"async_hooks\";\n\nexport interface LoggerContext {\n \"x-user-id\"?: string;\n \"x-tenant-id\"?: string;\n \"x-request-id\"?: string;\n \"x-task-id\"?: string;\n \"x-thread-id\"?: string;\n}\n\nexport interface LoggerOptions {\n name?: string;\n serviceName?: string;\n context?: LoggerContext;\n}\n\n/**\n * Resolve the optional file destination for the legacy pino logger.\n *\n * File logging is strictly opt-in through `LOG_FILE`. `NODE_ENV` is ignored so\n * a production process on a read-only filesystem (e.g. Kubernetes) never tries\n * to create a `logs/` directory unless the operator asked for it.\n *\n * @param env Environment variables to read (defaults to `process.env`).\n * @returns The configured log file path, or `null` when file logging is off.\n *\n * @example\n * ```ts\n * resolveLegacyLogFile({ LOG_FILE: \"./logs/app\" }); // → \"./logs/app\"\n * resolveLegacyLogFile({ NODE_ENV: \"production\" }); // → null\n * ```\n */\nexport function resolveLegacyLogFile(\n env: NodeJS.ProcessEnv = process.env\n): string | null {\n const file = env.LOG_FILE?.trim();\n return file ? file : null;\n}\n\n/**\n * 单例的Pino日志工厂类,管理底层pino实例\n */\nclass PinoLoggerFactory {\n private static instance: PinoLoggerFactory;\n private pinoLogger: pino.Logger;\n\n private constructor() {\n const loggerConfig: pino.LoggerOptions = {\n // 自定义时间戳格式\n timestamp: () => `,\"@timestamp\":\"${new Date().toISOString()}\"`,\n\n // 关闭默认的时间戳键\n base: {\n \"@version\": \"1\",\n app_name: \"lattice\",\n service_name: \"lattice/graph-server\",\n thread_name: \"main\",\n logger_name: \"lattice-graph-logger\",\n },\n\n formatters: {\n level: (label, number) => {\n return {\n level: label.toUpperCase(),\n level_value: number * 1000,\n };\n },\n },\n };\n\n // 仅在显式配置 LOG_FILE 时写入文件;默认输出到控制台\n const logFile = resolveLegacyLogFile(process.env);\n if (logFile) {\n try {\n this.pinoLogger = pino(\n loggerConfig,\n pino.transport({\n target: \"pino-roll\",\n options: {\n file: logFile,\n frequency: \"daily\",\n mkdir: true,\n },\n })\n );\n } catch (error) {\n console.error(\n \"无法初始化 pino-roll 日志记录器,回退到控制台日志\",\n error\n );\n // 回退到控制台输出\n this.pinoLogger = pino({\n ...loggerConfig,\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n },\n },\n });\n }\n } else {\n // 默认使用格式化输出\n this.pinoLogger = pino({\n ...loggerConfig,\n transport: {\n target: \"pino-pretty\",\n options: {\n colorize: true,\n },\n },\n });\n }\n }\n\n public static getInstance(): PinoLoggerFactory {\n if (!PinoLoggerFactory.instance) {\n PinoLoggerFactory.instance = new PinoLoggerFactory();\n }\n return PinoLoggerFactory.instance;\n }\n\n public getPinoLogger(): pino.Logger {\n return this.pinoLogger;\n }\n}\n\n/**\n * Logger类,可以创建多个实例,每个实例有自己的上下文\n */\nexport class Logger {\n private context: LoggerContext;\n private name: string;\n private serviceName: string;\n\n constructor(options?: LoggerOptions) {\n this.context = options?.context || {};\n this.name = options?.name || \"lattice-graph-logger\";\n this.serviceName = options?.serviceName || \"lattice/graph-server\";\n }\n\n /**\n * 获取合并了上下文的日志对象\n * @param additionalContext 额外的上下文数据\n * @returns 带有上下文的pino日志对象\n */\n private getContextualLogger(additionalContext?: object): pino.Logger {\n const pinoLogger = PinoLoggerFactory.getInstance().getPinoLogger();\n\n // 合并Logger实例的上下文和额外上下文\n const contextObj = {\n \"x-user-id\": this.context[\"x-user-id\"] || \"\",\n \"x-tenant-id\": this.context[\"x-tenant-id\"] || \"\",\n \"x-request-id\": this.context[\"x-request-id\"] || \"\",\n \"x-task-id\": this.context[\"x-task-id\"] || \"\",\n \"x-thread-id\": this.context[\"x-thread-id\"] || \"\",\n service_name: this.serviceName,\n logger_name: this.name,\n ...additionalContext,\n };\n\n // 创建带有上下文的子日志记录器\n return pinoLogger.child(contextObj);\n }\n\n info(msg: string, obj?: object): void {\n this.getContextualLogger(obj).info(msg);\n }\n\n error(msg: string, obj?: object | Error): void {\n this.getContextualLogger(obj).error(msg);\n }\n\n warn(msg: string, obj?: object): void {\n this.getContextualLogger(obj).warn(msg);\n }\n\n debug(msg: string, obj?: object): void {\n this.getContextualLogger(obj).debug(msg);\n }\n\n /**\n * 更新Logger实例的上下文\n */\n updateContext(context: Partial<LoggerContext>): void {\n this.context = {\n ...this.context,\n ...context,\n };\n }\n\n /**\n * 创建一个新的Logger实例,继承当前Logger的上下文\n */\n child(options: Partial<LoggerOptions>): Logger {\n return new Logger({\n name: options.name || this.name,\n serviceName: options.serviceName || this.serviceName,\n context: {\n ...this.context,\n ...options.context,\n },\n });\n }\n}\n","import { randomBytes } from \"crypto\";\n\nconst DEFAULT_BASE_URL = \"https://ilinkai.weixin.qq.com\";\n\nfunction getBaseUrl(): string {\n return process.env.WECHAT_ILINK_BASE_URL || DEFAULT_BASE_URL;\n}\n\nfunction generateWechatUin(): string {\n const buf = Buffer.alloc(4);\n buf.writeUInt32BE(Math.floor(Math.random() * 0xffffffff), 0);\n return buf.toString(\"base64url\");\n}\n\nasync function apiGet<T>(path: string, extraHeaders?: Record<string, string>): Promise<T> {\n const url = `${getBaseUrl()}${path}`;\n const headers: Record<string, string> = { ...extraHeaders };\n const res = await fetch(url, { headers });\n if (!res.ok) {\n throw new Error(`iLink GET ${path} failed: ${res.status} ${res.statusText}`);\n }\n return res.json() as Promise<T>;\n}\n\nasync function apiGetAuth<T>(path: string, botToken: string, extraHeaders?: Record<string, string>): Promise<T> {\n const url = `${getBaseUrl()}${path}`;\n const headers: Record<string, string> = {\n \"AuthorizationType\": \"ilink_bot_token\",\n \"X-WECHAT-UIN\": generateWechatUin(),\n \"Authorization\": `Bearer ${botToken}`,\n ...extraHeaders,\n };\n const res = await fetch(url, { headers });\n if (!res.ok) {\n throw new Error(`iLink GET ${path} failed: ${res.status} ${res.statusText}`);\n }\n return res.json() as Promise<T>;\n}\n\nasync function apiPost<T>(path: string, body: unknown, botToken: string, signal?: AbortSignal): Promise<T> {\n const url = `${getBaseUrl()}${path}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"AuthorizationType\": \"ilink_bot_token\",\n \"X-WECHAT-UIN\": generateWechatUin(),\n \"Authorization\": `Bearer ${botToken}`,\n };\n\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) {\n throw new Error(`iLink POST ${path} failed: ${res.status} ${res.statusText}`);\n }\n return res.json() as Promise<T>;\n}\n\nexport async function getUpdates(\n botToken: string,\n syncBuffer: string,\n signal?: AbortSignal,\n): Promise<{ msgs: Array<{\n message_type?: number;\n from_user_id?: string;\n client_id?: string;\n item_list?: Array<{ type?: number; text_item?: { text?: string } }>;\n context_token?: string;\n create_time_ms?: number;\n}>; syncBuffer: string }> {\n const data = await apiPost<{\n ret?: number;\n errcode?: number;\n errmsg?: string;\n msgs?: Array<{\n message_type?: number;\n from_user_id?: string;\n client_id?: string;\n item_list?: Array<{ type?: number; text_item?: { text?: string } }>;\n context_token?: string;\n create_time_ms?: number;\n }>;\n get_updates_buf?: string;\n }>(\"/ilink/bot/getupdates\", {\n get_updates_buf: syncBuffer,\n base_info: { channel_version: \"1.0.2\" },\n }, botToken, signal);\n\n // Check API-level error codes (HTTP 200 can still be an error)\n if ((data.ret !== undefined && data.ret !== 0) || (data.errcode !== undefined && data.errcode !== 0)) {\n throw new Error(`iLink getUpdates error: ret=${data.ret} errcode=${data.errcode} errmsg=${data.errmsg ?? \"\"}`);\n }\n\n return {\n msgs: data.msgs ?? [],\n syncBuffer: data.get_updates_buf ?? syncBuffer,\n };\n}\n\nfunction generateClientId(): string {\n return `axiom-wechat:${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\nexport async function sendMessage(\n botToken: string,\n toUserId: string,\n text: string,\n contextToken: string,\n): Promise<void> {\n await apiPost<unknown>(\"/ilink/bot/sendmessage\", {\n msg: {\n from_user_id: \"\",\n to_user_id: toUserId,\n client_id: generateClientId(),\n message_type: 2, // MSG_TYPE_BOT\n message_state: 2, // MSG_STATE_FINISH\n item_list: [{ type: 1, text_item: { text } }],\n context_token: contextToken,\n },\n base_info: { channel_version: \"1.0.2\" },\n }, botToken);\n}\n\nexport async function getQrCode(): Promise<{ qrcode: string; qrcodeImgUrl: string }> {\n const data = await apiGet<{ qrcode?: string; qrcode_img_content?: string }>(\n \"/ilink/bot/get_bot_qrcode?bot_type=3\",\n );\n if (!data.qrcode || !data.qrcode_img_content) {\n throw new Error(\"Failed to get WeChat QR code\");\n }\n return { qrcode: data.qrcode, qrcodeImgUrl: data.qrcode_img_content };\n}\n\nexport async function getQrCodeStatus(\n qrcode: string,\n signal?: AbortSignal,\n): Promise<{\n status: \"wait\" | \"scaned\" | \"confirmed\" | \"expired\";\n botToken?: string;\n uin?: string;\n botId?: string;\n baseUrl?: string;\n}> {\n const data = await apiGet<{\n status?: string;\n bot_token?: string;\n ilink_bot_id?: string;\n ilink_user_id?: string;\n baseurl?: string;\n }>(`/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`, {\n \"iLink-App-ClientVersion\": \"1\",\n });\n return {\n status: (data.status as \"wait\" | \"scaned\" | \"confirmed\" | \"expired\") || \"wait\",\n botToken: data.bot_token,\n uin: data.ilink_user_id,\n botId: data.ilink_bot_id,\n baseUrl: data.baseurl,\n };\n}\n"],"mappings":"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","names":[]}
@@ -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
@@ -115,6 +115,10 @@ var init_settingsRuntime = __esm({
115
115
  });
116
116
 
117
117
  // src/logger/Logger.ts
118
+ function resolveLegacyLogFile(env = process.env) {
119
+ const file = env.LOG_FILE?.trim();
120
+ return file ? file : null;
121
+ }
118
122
  var import_pino, import_pino_pretty, import_pino_roll, PinoLoggerFactory, Logger;
119
123
  var init_Logger = __esm({
120
124
  "src/logger/Logger.ts"() {
@@ -124,7 +128,6 @@ var init_Logger = __esm({
124
128
  import_pino_roll = require("pino-roll");
125
129
  PinoLoggerFactory = class _PinoLoggerFactory {
126
130
  constructor() {
127
- const isProd = process.env.NODE_ENV === "production";
128
131
  const loggerConfig = {
129
132
  // 自定义时间戳格式
130
133
  timestamp: () => `,"@timestamp":"${(/* @__PURE__ */ new Date()).toISOString()}"`,
@@ -145,14 +148,15 @@ var init_Logger = __esm({
145
148
  }
146
149
  }
147
150
  };
148
- if (isProd) {
151
+ const logFile = resolveLegacyLogFile(process.env);
152
+ if (logFile) {
149
153
  try {
150
154
  this.pinoLogger = (0, import_pino.default)(
151
155
  loggerConfig,
152
156
  import_pino.default.transport({
153
157
  target: "pino-roll",
154
158
  options: {
155
- file: "./logs/fin_ai_graph_server",
159
+ file: logFile,
156
160
  frequency: "daily",
157
161
  mkdir: true
158
162
  }
@@ -691,7 +695,7 @@ async function saveProjectFile(params) {
691
695
  workspaceId,
692
696
  projectId: params.projectId
693
697
  };
694
- 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);
695
699
  const isBinary = isBinaryContentType(filename);
696
700
  const volumeBackend = await sandboxManager.getVolumeBackendForPath(
697
701
  volumeConfig,
@@ -9434,20 +9438,21 @@ function createOpenSandboxProvider() {
9434
9438
  switch (actionName) {
9435
9439
  case "files_read": {
9436
9440
  const { path: path7 } = args;
9437
- const file = await sandbox.file.readFile(path7);
9441
+ const file = await sandbox.file.readFile((0, import_core120.resolveSandboxAgentsPathAlias)(path7));
9438
9442
  return { content: [{ type: "text", text: file.content }] };
9439
9443
  }
9440
9444
  case "files_list": {
9441
9445
  const { path: path7, recursive } = args;
9442
- const listing = await sandbox.file.listPath(path7, { recursive });
9446
+ const listing = await sandbox.file.listPath((0, import_core120.resolveSandboxAgentsPathAlias)(path7), { recursive });
9443
9447
  return {
9444
9448
  content: [{ type: "text", text: JSON.stringify(listing.files) }]
9445
9449
  };
9446
9450
  }
9447
9451
  case "files_write": {
9448
9452
  const { path: path7, content } = args;
9449
- await sandbox.file.writeFile(path7, content);
9450
- 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}` }] };
9451
9456
  }
9452
9457
  case "shell_exec": {
9453
9458
  const { command, exec_dir, timeout } = args;
@@ -9502,7 +9507,8 @@ function extractBearer2(request) {
9502
9507
  function safePath(raw) {
9503
9508
  if (typeof raw !== "string" || raw.length === 0) return null;
9504
9509
  if (raw.includes("..")) return null;
9505
- return raw.startsWith("/") ? raw : `/${raw}`;
9510
+ const aliased = (0, import_core121.resolveSandboxAgentsPathAlias)(raw);
9511
+ return aliased.startsWith("/") ? aliased : `/${aliased}`;
9506
9512
  }
9507
9513
  function registerOpenFilesRoutes(app2, deps = {}) {
9508
9514
  const openAuth = deps.openAuth ?? new OpenCredentialService(
@@ -21275,7 +21281,8 @@ function registerSandboxProxyRoutes(app2) {
21275
21281
  vmIsolation
21276
21282
  });
21277
21283
  try {
21278
- const resolvedPath = filePath.startsWith("/") ? filePath : `/${filePath}`;
21284
+ const aliasedPath = (0, import_core37.resolveSandboxAgentsPathAlias)(filePath);
21285
+ const resolvedPath = aliasedPath.startsWith("/") ? aliasedPath : `/${aliasedPath}`;
21279
21286
  const filename = getFilenameFromPath(resolvedPath);
21280
21287
  const inferredContentType = getContentTypeFromFilename(filename);
21281
21288
  try {
@@ -22035,7 +22042,10 @@ var WorkspaceController = class {
22035
22042
  if (!workspace) {
22036
22043
  throw new Error("Workspace not found");
22037
22044
  }
22038
- 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
+ );
22039
22049
  if (workspace.storageType === "sandbox") {
22040
22050
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22041
22051
  const volumeConfig = {
@@ -22045,11 +22055,11 @@ var WorkspaceController = class {
22045
22055
  workspaceId,
22046
22056
  projectId
22047
22057
  };
22048
- console.log(`[getBackend] trying volume backend for path=${filePath} assistant_id=${assistantId}`);
22049
- 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");
22050
22060
  if (volumeBackend) {
22051
22061
  console.log(`[getBackend] using VolumeFilesystem`);
22052
- return { backend: volumeBackend, workspace };
22062
+ return { backend: volumeBackend, workspace, resolvedPath };
22053
22063
  }
22054
22064
  console.log(`[getBackend] volume not found, falling back to SandboxFilesystem`);
22055
22065
  const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
@@ -22058,7 +22068,8 @@ var WorkspaceController = class {
22058
22068
  backend: new import_core42.SandboxFilesystem({
22059
22069
  sandboxInstance: sandbox
22060
22070
  }),
22061
- workspace
22071
+ workspace,
22072
+ resolvedPath
22062
22073
  };
22063
22074
  }
22064
22075
  const rootDir = filesystemRootDirectory(
@@ -22072,7 +22083,8 @@ var WorkspaceController = class {
22072
22083
  rootDir,
22073
22084
  virtualMode: true
22074
22085
  }),
22075
- workspace
22086
+ workspace,
22087
+ resolvedPath
22076
22088
  };
22077
22089
  }
22078
22090
  async downloadFile(request, reply) {
@@ -22084,8 +22096,14 @@ var WorkspaceController = class {
22084
22096
  return reply.status(400).send({ success: false, error: "Path is required" });
22085
22097
  }
22086
22098
  try {
22087
- const { workspace } = await this.getBackend(tenantId, workspaceId, projectId, assistantId);
22088
- 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;
22089
22107
  if (workspace.storageType === "sandbox") {
22090
22108
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22091
22109
  const volumeConfig = {
@@ -22147,6 +22165,7 @@ var WorkspaceController = class {
22147
22165
  if (!project || project.workspaceId !== workspaceId) {
22148
22166
  throw new FolderDownloadError(404, "PROJECT_NOT_FOUND", "Project not found");
22149
22167
  }
22168
+ const resolvedFolderPath = storedWorkspace.storageType === "sandbox" ? (0, import_core43.resolveSandboxAgentsPathAlias)(folderPath) : folderPath;
22150
22169
  if (storedWorkspace.storageType === "sandbox") {
22151
22170
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22152
22171
  const volumeConfig = {
@@ -22162,7 +22181,7 @@ var WorkspaceController = class {
22162
22181
  }
22163
22182
  let packed;
22164
22183
  try {
22165
- packed = await sandbox.packDirectory(folderPath);
22184
+ packed = await sandbox.packDirectory(resolvedFolderPath);
22166
22185
  if (packed.size === void 0) {
22167
22186
  throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not report packed archive size");
22168
22187
  }
@@ -22186,7 +22205,7 @@ var WorkspaceController = class {
22186
22205
  workspaceId,
22187
22206
  projectId,
22188
22207
  assistantId,
22189
- folderPath
22208
+ resolvedFolderPath
22190
22209
  );
22191
22210
  if (!backend.lsInfo) {
22192
22211
  throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not support folder downloads");
@@ -22194,7 +22213,7 @@ var WorkspaceController = class {
22194
22213
  let listEntries2;
22195
22214
  if (workspace.storageType !== "sandbox") {
22196
22215
  const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
22197
- const target = path4.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
22216
+ const target = path4.resolve(root, `.${resolvedFolderPath.startsWith("/") ? resolvedFolderPath : `/${resolvedFolderPath}`}`);
22198
22217
  const contained = await resolvePathWithinRoot(root, target).catch((error) => {
22199
22218
  if (error instanceof Error && error.code === "ENOENT") {
22200
22219
  throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
@@ -22213,7 +22232,7 @@ var WorkspaceController = class {
22213
22232
  if (!stat3.isDirectory()) {
22214
22233
  throw new FolderDownloadError(400, "NOT_A_FOLDER", "Path is not a folder");
22215
22234
  }
22216
- } else if (folderPath) {
22235
+ } else if (resolvedFolderPath) {
22217
22236
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22218
22237
  const volumeConfig = {
22219
22238
  assistant_id: assistantId || "",
@@ -22222,11 +22241,11 @@ var WorkspaceController = class {
22222
22241
  workspaceId,
22223
22242
  projectId
22224
22243
  };
22225
- const volume = await sandboxManager.getVolumeBackendForPath(volumeConfig, folderPath);
22244
+ const volume = await sandboxManager.getVolumeBackendForPath(volumeConfig, resolvedFolderPath);
22226
22245
  if (!volume) {
22227
22246
  const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
22228
22247
  try {
22229
- await sandbox.file.listPath(folderPath, { recursive: false });
22248
+ await sandbox.file.listPath(resolvedFolderPath, { recursive: false });
22230
22249
  } catch (error) {
22231
22250
  const code = error instanceof Error ? error.code : void 0;
22232
22251
  const message = error instanceof Error ? error.message : String(error);
@@ -22246,7 +22265,7 @@ var WorkspaceController = class {
22246
22265
  if (!listEntries2) {
22247
22266
  listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
22248
22267
  }
22249
- const folderName = path4.basename(folderPath.replace(/\/$/, "")) || "folder";
22268
+ const folderName = path4.basename(resolvedFolderPath.replace(/\/$/, "")) || "folder";
22250
22269
  const prefix = `${folderName}/`;
22251
22270
  const entries = [{ name: prefix, data: Buffer.alloc(0), directory: true }];
22252
22271
  let totalSize = 0;
@@ -22358,7 +22377,7 @@ var WorkspaceController = class {
22358
22377
  throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
22359
22378
  }
22360
22379
  const childPath = child.path;
22361
- 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}/`)) {
22362
22381
  throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains an unsafe child path");
22363
22382
  }
22364
22383
  if (workspace.storageType !== "sandbox") {
@@ -22395,10 +22414,10 @@ var WorkspaceController = class {
22395
22414
  }
22396
22415
  };
22397
22416
  if (workspace.storageType === "sandbox") {
22398
- await collect(folderPath, prefix);
22417
+ await collect(resolvedFolderPath, prefix);
22399
22418
  } else {
22400
22419
  const root = filesystemRoot;
22401
- const target = path4.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
22420
+ const target = path4.resolve(root, `.${resolvedFolderPath.startsWith("/") ? resolvedFolderPath : `/${resolvedFolderPath}`}`);
22402
22421
  await collectFilesystem(target, prefix);
22403
22422
  }
22404
22423
  const zip = createZip(entries);
@@ -22426,8 +22445,14 @@ var WorkspaceController = class {
22426
22445
  return reply.status(400).send({ success: false, error: "Path is required" });
22427
22446
  }
22428
22447
  try {
22429
- const { workspace } = await this.getBackend(tenantId, workspaceId, projectId, assistantId);
22430
- 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;
22431
22456
  if (workspace.storageType === "sandbox") {
22432
22457
  const sandboxManager = (0, import_core43.getSandBoxManager)();
22433
22458
  const volumeConfig = {
@@ -22525,9 +22550,10 @@ var WorkspaceController = class {
22525
22550
  const path7 = request.query.path || "/";
22526
22551
  const assistantId = request.query.assistantId;
22527
22552
  console.log(`[listPath] tenantId=${tenantId} workspaceId=${workspaceId} projectId=${projectId} path=${path7} assistantId=${assistantId}`);
22528
- const { backend } = await this.getBackend(tenantId, workspaceId, projectId, assistantId, path7);
22529
- console.log(`[listPath] backend type=${backend.constructor.name} calling lsInfo(${path7})`);
22530
- 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);
22531
22557
  console.log(`[listPath] result count=${files.length}`);
22532
22558
  return { success: true, data: files };
22533
22559
  }
@@ -22535,8 +22561,8 @@ var WorkspaceController = class {
22535
22561
  const tenantId = this.getTenantId(request);
22536
22562
  const { workspaceId, projectId } = request.params;
22537
22563
  const { path: path7, offset = 0, limit = 1e3, assistantId } = request.query;
22538
- const { backend } = await this.getBackend(tenantId, workspaceId, projectId, assistantId, path7);
22539
- 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));
22540
22566
  return { success: true, data: { content, offset, limit } };
22541
22567
  }
22542
22568
  /**