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}`;
}