hunian
feat: 添加 CORS 支持和 session 文件系统,优化沙箱后端逻辑
67a584e
Raw
History Blame Contribute Delete
4.38 kB
/**
* 短生命周期打开 AgentFS:回调结束即 close,不进入 sandbox host 缓存。
*
* 适用:
* - 无 live host 时的只读 browse
* - checkpoint 库(无 live host)
* - 已在 HostRegistry.withExclusiveAccess 保护下的 session 库读写
*
* session 库优先走 withSessionFileSystem:复用 live host,禁止与 host 双开。
* 禁止:在 live host 可能仍占用 session .db 时,绕过 exclusive/shared 直接
* withAgentFsAtPath(sessionDb) 做 create/restore 等大范围改写。
*/
import { mkdir, stat } from "node:fs/promises";
import { dirname } from "node:path";
import { AgentFS } from "agentfs-sdk";
import type { FileSystem } from "agentfs-sdk";
import { hostRegistry } from "../agentfs-sandbox/host.js";
import { AgentFsDebugError, mapAgentFsOpenError } from "./errors.js";
import { resolveSessionDbPath } from "./paths.js";
export type OpenAgent = Awaited<ReturnType<typeof AgentFS.open>>;
async function closeQuietly(agent: OpenAgent): Promise<void> {
try {
await agent.close();
} catch {
// 关闭失败不影响主结果
}
}
/** 确认磁盘上的 .db 已存在,避免 AgentFS.open 对缺失路径静默建空库。 */
export async function assertDbExists(
dbPath: string,
notFound: { code: string; message: string },
): Promise<void> {
try {
await stat(dbPath);
} catch (err) {
const code = (err as NodeJS.ErrnoException).code;
if (code === "ENOENT") {
throw new AgentFsDebugError(404, notFound.code, notFound.message);
}
throw err;
}
}
/** 确认 session .db 已存在。 */
export async function assertSessionDbExists(sessionId: string): Promise<string> {
const sessionDb = resolveSessionDbPath(sessionId);
await assertDbExists(sessionDb, {
code: "session_not_found",
message: `session 不存在: ${sessionId}`,
});
return sessionDb;
}
/**
* 在指定 db 路径上打开 AgentFS,执行回调后关闭。
* @param mustExist 为 true 时禁止 open 静默创建空库;为 false 时会 mkdir 父目录后 open(checkpoint 新建)。
* @param notFound mustExist 时 404 的 code/message;默认 session_not_found。
*/
export async function withAgentFsAtPath<T>(
dbPath: string,
fn: (agent: OpenAgent) => Promise<T>,
opts?: {
mustExist?: boolean;
label?: string;
notFound?: { code: string; message: string };
},
): Promise<T> {
const mustExist = opts?.mustExist ?? true;
const label = opts?.label ?? dbPath;
if (mustExist) {
await assertDbExists(
dbPath,
opts?.notFound ?? {
code: "session_not_found",
message: `库不存在: ${label}`,
},
);
} else {
await mkdir(dirname(dbPath), { recursive: true });
}
let agent: OpenAgent;
try {
agent = await AgentFS.open({ path: dbPath });
} catch (err) {
throw mapAgentFsOpenError(err, label);
}
try {
return await fn(agent);
} finally {
await closeQuietly(agent);
}
}
/**
* 访问 session 虚拟 FS:优先复用 HostRegistry live handle(不二次 open SQLite);
* 无 live 时在 shared 串行锁内短生命周期 open。
*
* 与 exclusive / getOrOpen 同路径串行,避免 browse/create 与 restore 竞态双开。
*/
export async function withSessionFileSystem<T>(
sessionId: string,
fn: (fs: FileSystem) => Promise<T>,
): Promise<T> {
const dbPath = await assertSessionDbExists(sessionId);
return hostRegistry.withSharedAccess(dbPath, async () => {
const live = hostRegistry.getByDbPath(dbPath);
if (live) {
return fn(live.agentFs.fs);
}
return withAgentFsAtPath(
dbPath,
(agent) => fn(agent.fs),
{
mustExist: true,
label: sessionId,
notFound: {
code: "session_not_found",
message: `session 不存在: ${sessionId}`,
},
},
);
});
}
/**
* @deprecated 优先 withSessionFileSystem,避免与 live host 双开。
* 保留给确知无 live、或已在 exclusive 内的调用方。
*/
export async function withAgentFs<T>(
sessionId: string,
fn: (agent: OpenAgent) => Promise<T>,
): Promise<T> {
const dbPath = resolveSessionDbPath(sessionId);
return withAgentFsAtPath(dbPath, fn, {
mustExist: true,
label: sessionId,
notFound: {
code: "session_not_found",
message: `session 不存在: ${sessionId}`,
},
});
}