refactor(core): migrate to Bun and converge acquisition policies and Agent boundaries
This commit is contained in:
1 parent
3f1c6d02f6
commit
27c0f27434
268 files changed
+7468
-7852
No files matched your search
+28
-26
@@ -1,5 +1,7 @@
|
||||
import "reflect-metadata";
|
||||
import { parseArgs } from "node:util";
|
||||
import denoJson from "../deno.json" with { type: "json" };
|
||||
import { runtimeReadTextFile, serve } from "@umt/runtime";
|
||||
import packageJson from "../package.json" with { type: "json" };
|
||||
import type { Config } from "./config.ts";
|
||||
import type { MappingExport } from "./ops.ts";
|
||||
|
||||
@@ -34,7 +36,7 @@ type OneShotRole = (typeof OneShot)[number];
|
||||
const isOneShot = (r: Role): r is OneShotRole => (OneShot as readonly Role[]).includes(r);
|
||||
|
||||
const { values } = parseArgs({
|
||||
args: Deno.args,
|
||||
args: process.argv.slice(2),
|
||||
options: {
|
||||
role: { type: "string", default: "all" },
|
||||
out: { type: "string" },
|
||||
@@ -46,32 +48,32 @@ const { values } = parseArgs({
|
||||
const role = values.role as Role;
|
||||
if (!Roles.includes(role)) {
|
||||
console.error(`--role 必须是 ${Roles.join("|")}`);
|
||||
Deno.exit(2);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
// fsproxy 执行子进程:必须在加载 db/任务引擎/模块之前分派——每个文件操作起一次,只加载 fs 操作表,启动保持轻量
|
||||
if (role === "fsworker") {
|
||||
const { fsWorkerMain } = await import("@umt/adapters/fsworker");
|
||||
await fsWorkerMain();
|
||||
Deno.exit(0);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 沙箱子进程:同样在加载其余模块前分派,只加载沙箱任务表
|
||||
if (role === "sandbox") {
|
||||
const { sandboxWorkerMain } = await import("./sandbox/worker.ts");
|
||||
await sandboxWorkerMain();
|
||||
Deno.exit(0);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// 健康探测:只打本机回环端口,不需要完整配置(HEALTHCHECK 用,镜像里不装 curl)
|
||||
if (role === "probe") {
|
||||
const port = Deno.env.get("PORT") ?? "4380";
|
||||
const port = process.env.PORT ?? "4380";
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:${port}/api/v1/health`, { signal: AbortSignal.timeout(3000) });
|
||||
const body = (await res.json()) as { data?: { status?: string } };
|
||||
Deno.exit(res.ok && body.data?.status === "ok" ? 0 : 1);
|
||||
process.exit(res.ok && body.data?.status === "ok" ? 0 : 1);
|
||||
} catch {
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,16 +100,16 @@ try {
|
||||
config = loadConfig();
|
||||
} catch (err) {
|
||||
console.error((err as Error).message);
|
||||
Deno.exit(2);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
// 运行期写入统一落在 DATA_DIR;Agent 会话目录经环境变量传给 agent 模块(它只读 AGENT_SESSION_DIR)
|
||||
const paths = dataPaths(config);
|
||||
Deno.env.set("AGENT_SESSION_DIR", paths.sessionDir);
|
||||
process.env.AGENT_SESSION_DIR = paths.sessionDir;
|
||||
|
||||
const logger = createLogger({
|
||||
level: config.LOG_LEVEL,
|
||||
pretty: config.NODE_ENV === "development" && Deno.stdout.isTerminal(),
|
||||
pretty: config.NODE_ENV === "development" && Boolean(process.stdout.isTTY),
|
||||
});
|
||||
const db = createDb({ connectionString: config.DATABASE_URL });
|
||||
|
||||
@@ -121,7 +123,7 @@ if (isOneShot(role)) {
|
||||
} finally {
|
||||
await db.destroy();
|
||||
}
|
||||
Deno.exit(code);
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
async function runOneShot(r: OneShotRole): Promise<number> {
|
||||
@@ -155,9 +157,9 @@ async function runOneShot(r: OneShotRole): Promise<number> {
|
||||
}
|
||||
await auditStorageWarnings(db, storage.warnings).catch((err) => logger.warn({ err }, "存储告警写审计失败"));
|
||||
// 验收开关误设提示(验收 07 S-10):只有 UMT_FS_TEST_HOOKS=1 时才真正生效
|
||||
if (Deno.env.get("UMT_FS_TEST_HANG_AFTER_BYTES"))
|
||||
if (process.env.UMT_FS_TEST_HANG_AFTER_BYTES)
|
||||
logger.warn(
|
||||
{ active: Deno.env.get("UMT_FS_TEST_HOOKS") === "1" },
|
||||
{ active: process.env.UMT_FS_TEST_HOOKS === "1" },
|
||||
"设置了 UMT_FS_TEST_HANG_AFTER_BYTES(fsproxy 挂死验收开关);生产环境不要设置,生效时所有数据类文件操作都会卡死到被 SIGKILL",
|
||||
);
|
||||
for (const p of problems) logger.error({ check: p.check }, p.message);
|
||||
@@ -175,11 +177,11 @@ async function runOneShot(r: OneShotRole): Promise<number> {
|
||||
}
|
||||
case "export-mappings": {
|
||||
const out = values.out ?? `${paths.exportsDir}/mappings-${new Date().toISOString().replace(/[:.]/g, "-")}.json`;
|
||||
const data = await exportMappings(db, denoJson.version);
|
||||
const data = await exportMappings(db, packageJson.version);
|
||||
await writeMappingExport(out, data);
|
||||
logger.info({ out, counts: data.counts }, "映射清单已导出");
|
||||
if (!values.compare) return 0;
|
||||
const base = JSON.parse(await Deno.readTextFile(values.compare)) as MappingExport;
|
||||
const base = JSON.parse(await runtimeReadTextFile(values.compare)) as MappingExport;
|
||||
const diff = diffCounts(base.counts, data.counts);
|
||||
if (diff.length) {
|
||||
logger.error({ diff }, "映射清单行数与基准不一致");
|
||||
@@ -206,7 +208,7 @@ try {
|
||||
} catch (err) {
|
||||
logger.error({ err }, "SSE 总线启动失败");
|
||||
await db.destroy();
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
const stops: Array<() => Promise<void>> = [];
|
||||
|
||||
@@ -217,7 +219,7 @@ try {
|
||||
} catch (err) {
|
||||
logger.error({ err }, "启动装配失败");
|
||||
await db.destroy();
|
||||
Deno.exit(1);
|
||||
process.exit(1);
|
||||
}
|
||||
const { ctx, outbox, engine } = booted;
|
||||
// 统一元数据缓存:LISTEN 跨进程失效(在线才启用进程内一级缓存)+ 命中统计定期落库
|
||||
@@ -230,13 +232,13 @@ if (role === "api" || role === "all") {
|
||||
bus,
|
||||
db,
|
||||
role,
|
||||
version: denoJson.version,
|
||||
version: packageJson.version,
|
||||
...(config.WEB_ROOT ? { webRoot: config.WEB_ROOT } : {}),
|
||||
modules: SERVER_MODULES,
|
||||
ctx,
|
||||
});
|
||||
const ac = new AbortController();
|
||||
const server = Deno.serve(
|
||||
const server = serve(
|
||||
{
|
||||
port: config.PORT,
|
||||
hostname: config.HOST,
|
||||
@@ -250,7 +252,7 @@ if (role === "api" || role === "all") {
|
||||
app.fetch,
|
||||
);
|
||||
stops.push(async () => {
|
||||
// abort 后 Deno.serve 会断开 SSE 等长连接,finished 在所有连接关闭后 resolve
|
||||
// abort 后 serve 会断开 SSE 等长连接,finished 在所有连接关闭后 resolve
|
||||
ac.abort();
|
||||
await server.finished;
|
||||
});
|
||||
@@ -280,18 +282,18 @@ if (role === "worker" || role === "worker:agent" || role === "all") {
|
||||
}
|
||||
|
||||
let stopping = false;
|
||||
async function shutdown(signal: Deno.Signal) {
|
||||
async function shutdown(signal: NodeJS.Signals) {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
logger.info({ signal }, "正在退出");
|
||||
// 兜底:10 秒内没退干净就强退,避免容器 stop 超时被 SIGKILL
|
||||
const force = setTimeout(() => Deno.exit(1), 10_000);
|
||||
const force = setTimeout(() => process.exit(1), 10_000);
|
||||
await Promise.allSettled(stops.map((s) => s()));
|
||||
await engine.stop().catch((err) => logger.error({ err }, "任务引擎关闭失败"));
|
||||
await bus.stop().catch(() => {});
|
||||
await db.destroy();
|
||||
clearTimeout(force);
|
||||
Deno.exit(0);
|
||||
process.exit(0);
|
||||
}
|
||||
Deno.addSignalListener("SIGINT", () => void shutdown("SIGINT"));
|
||||
Deno.addSignalListener("SIGTERM", () => void shutdown("SIGTERM"));
|
||||
process.on("SIGINT", () => void shutdown("SIGINT"));
|
||||
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
||||
Reference in new issue
Block a user