feat(server,fs): W5-E production fsworker role, settings auto-investigate mode, replace old file to trash_item

- main.ts: `--role fsworker` dispatched before the rest of the module graph is
  loaded (other imports became dynamic); runs fsWorkerMain from the new
  `@umt/adapters/fsworker` export (~150 ms startup in the compiled binary)
- fsproxy: default worker command picked per runtime (deno CLI -> `deno run
  fsworker.ts`, compiled binary -> `<execPath> --role fsworker`); stats expose
  workerKind; UMT_FS_TEST_HANG_AFTER_BYTES acceptance switch for containers
- compile: --allow-run adds /app/umt-server (spawn self); start task allows deno
- organize: replaced old file moved into the file-manager trash layout
  (<root>/.umt-trash/<day>/<uuid>/<rel>) and registered in trash_item, ledger
  result follows; organize.transfer log carries fsproxy stats
- settings: autoInvestigateMode (investigate/recover) + max recover attempts
- deploy.md: container verification (subprocess self, TIMEOUT -> SIGKILL x3)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 23:50:10 +08:00
1 parent fc49c58359
commit 3641ab9573
12 files changed
+248 -48

No files matched your search

+27 -13
View File
@@ -1,22 +1,15 @@
import { parseArgs } from "node:util";
import { createDb, migrateToLatest } from "@umt/db";
import { migrateEngineSchema } from "@umt/tasks";
import denoJson from "../deno.json" with { type: "json" };
import { DATA_SUBDIRS, dataPaths, loadConfig } from "./config.ts";
import { bootstrap } from "./context.ts";
import { createApp } from "./http/app.ts";
import { PgEventBus } from "./http/sse-pg.ts";
import { createLogger } from "./logger.ts";
import { SERVER_MODULES } from "./modules/index.ts";
import { diffCounts, enableReadOnlyReconcile, exportMappings, type MappingExport, writeMappingExport } from "./ops.ts";
import { auditStorageWarnings, checkStorage, loadMountedPaths, runPreflight } from "./preflight.ts";
import { startWorker } from "./worker/index.ts";
import type { Config } from "./config.ts";
import type { MappingExport } from "./ops.ts";
// api / worker / worker:agent / all 是常驻角色(worker = io 组:除 runAgent/resolveDecision 外的任务 + outbox 中继 +
// 模块后台循环;worker:agent = 只跑 Agent 会话任务;all = api + 两组 worker 同进程);其余是容器入口 / 备份恢复脚本用的一次性子命令:
// migrate | preflight | probe
// export-mappings --out <file.json> [--compare <基准清单.json>] 映射清单导出(可与基准比行数)
// read-only-on [--reason <文本>] 打开 automation.readOnlyReconcile
// fsworker fsproxy 执行子进程(编译二进制里由 fsproxy 以
// `<自身> --role fsworker` 拉起,stdin 一条请求)
const Roles = [
"api",
"worker",
@@ -27,6 +20,7 @@ const Roles = [
"probe",
"export-mappings",
"read-only-on",
"fsworker",
] as const;
type Role = (typeof Roles)[number];
const OneShot = ["migrate", "preflight", "export-mappings", "read-only-on"] as const;
@@ -49,6 +43,13 @@ if (!Roles.includes(role)) {
Deno.exit(2);
}
// fsproxy 执行子进程:必须在加载 db/任务引擎/模块之前分派——每个文件操作起一次,只加载 fs 操作表,启动保持轻量
if (role === "fsworker") {
const { fsWorkerMain } = await import("@umt/adapters/fsworker");
await fsWorkerMain();
Deno.exit(0);
}
// 健康探测:只打本机回环端口,不需要完整配置(HEALTHCHECK 用,镜像里不装 curl)
if (role === "probe") {
const port = Deno.env.get("PORT") ?? "4380";
@@ -61,7 +62,20 @@ if (role === "probe") {
}
}
let config: ReturnType<typeof loadConfig>;
// 其余依赖动态加载(顺序同原静态 import),让上面的 fsworker / probe 分支不必求值整张模块图
const { createDb, migrateToLatest } = await import("@umt/db");
const { migrateEngineSchema } = await import("@umt/tasks");
const { DATA_SUBDIRS, dataPaths, loadConfig } = await import("./config.ts");
const { bootstrap } = await import("./context.ts");
const { createApp } = await import("./http/app.ts");
const { PgEventBus } = await import("./http/sse-pg.ts");
const { createLogger } = await import("./logger.ts");
const { SERVER_MODULES } = await import("./modules/index.ts");
const { diffCounts, enableReadOnlyReconcile, exportMappings, writeMappingExport } = await import("./ops.ts");
const { auditStorageWarnings, checkStorage, loadMountedPaths, runPreflight } = await import("./preflight.ts");
const { startWorker } = await import("./worker/index.ts");
let config: Config;
try {
config = loadConfig();
} catch (err) {
@@ -149,7 +163,7 @@ async function runOneShot(r: OneShotRole): Promise<number> {
}
// SSE 总线:sse_event 表 + LISTEN/NOTIFY,api 与 worker 分进程时事件互相可见
let bus: PgEventBus;
let bus: Awaited<ReturnType<typeof PgEventBus.start>>;
try {
bus = await PgEventBus.start({
db,