File size: 3,080 Bytes
c60f42b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | /**
* 调试 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` 优先,否则 `<cwd>/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 目录:`<sessionsDir>/checkpoints/<sessionId>/` */
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}`;
}
|