/** * 调试 API 的路径解析与校验。 * * 数据布局复用 `lib/agentfs/paths.ts`;此处补 session id / 虚拟路径 / checkpoint 校验。 */ import { basename, join, resolve } from "node:path"; import { AGENTFS_BACKEND_NAME, resolveDataDir as resolveDataDirFromAppRoot, resolveSessionsDir as resolveSessionsDirFromDataDir, } from "../agentfs/paths.js"; import { CHECKPOINT_NAME_RE, SESSION_ID_RE } from "./constants.js"; import { AgentFsDebugError } from "./errors.js"; export { AGENTFS_BACKEND_NAME }; /** 数据目录根:`EVEAGENT_DATA_DIR` 优先,否则 `/data`。 */ export function resolveDataDir(): string { return resolveDataDirFromAppRoot(); } /** sessions 目录绝对路径。 */ export function resolveSessionsDir(): string { return resolveSessionsDirFromDataDir(resolveDataDir()); } /** 校验 session id,防止路径穿越。 */ export function assertSessionId(id: string): string { if (!SESSION_ID_RE.test(id)) { throw new AgentFsDebugError(400, "invalid_session_id", "session id 非法"); } return id; } /** * 解析虚拟 FS 路径:必须为绝对路径、无 `..`、无空字节。 * 空或 `/` → `/`。 */ export function normalizeVirtualPath(raw: string | null | undefined): string { const input = (raw ?? "/").trim() || "/"; if (input.includes("\0")) { throw new AgentFsDebugError(400, "invalid_path", "路径含非法字符"); } const withSlash = input.startsWith("/") ? input : `/${input}`; const parts = withSlash.split("/").filter((p) => p.length > 0); const out: string[] = []; for (const part of parts) { if (part === ".") continue; if (part === "..") { throw new AgentFsDebugError(400, "invalid_path", "路径不允许包含 .."); } out.push(part); } return out.length === 0 ? "/" : `/${out.join("/")}`; } /** 将 session id 解析为磁盘上的 .db 绝对路径(id 已禁止路径分隔符)。 */ export function resolveSessionDbPath(sessionId: string): string { const id = assertSessionId(sessionId); const sessionsDir = resolveSessionsDir(); const dbPath = resolve(join(sessionsDir, `${id}.db`)); if (basename(dbPath) !== `${id}.db`) { throw new AgentFsDebugError(400, "invalid_session_id", "session 路径越界"); } return dbPath; } /** checkpoints 目录:`/checkpoints//` */ export function resolveCheckpointsDir(sessionId: string): string { return join(resolveSessionsDir(), "checkpoints", assertSessionId(sessionId)); } /** 单个存档点 .db 文件路径。 */ export function resolveCheckpointDbPath( sessionId: string, name: string, ): string { if (!CHECKPOINT_NAME_RE.test(name)) { throw new AgentFsDebugError( 400, "invalid_checkpoint_name", "存档点名非法(允许中文、字母、数字、._-)", ); } return join(resolveCheckpointsDir(sessionId), `${name}.db`); } /** 拼接虚拟目录下的子路径。 */ export function joinVirtualPath(dir: string, name: string): string { return dir === "/" ? `/${name}` : `${dir}/${name}`; }