#!/usr/bin/env node
/**
 * Runtime Bridge 服务主入口
 *
 * 提供 Agent 运行时与调度器之间的桥接功能
 */
import express from "express";
import cors from "cors";
import { existsSync, readFileSync } from "node:fs";
import path from "node:path";
import os from "node:os";
import { allowedCorsOrigins, port, validateProductionToken } from "./config.js";
import { propertiesRouter } from "./routes/properties.js";
import { ymlRouter } from "./routes/yml.js";
import { instanceRouter, setGracefulShutdownFn } from "./routes/instance.js";
import { skillsRouter } from "./routes/skills.js";
import { mcpRouter } from "./routes/mcp.js";
import { terminalRouter, sdkSessions } from "./routes/terminal.js";
import { sessionsRouter } from "./routes/sessions.js";
import { gitRouter } from "./routes/git.js";
import { memoryRouter } from "./routes/memory.js";
import { fileBrowserRouter } from "./routes/file-browser.js";
import { aiSourcesRouter } from "./routes/ai-sources.js";
import { processesRouter } from "./routes/processes.js";
import { systemMetricsRouter } from "./routes/system-metrics.js";
import { attachPtyWebSocketServer, closeAllPtySessions, ptyRouter, ptySessions, } from "./routes/pty.js";
import { runtimeBindingRouter, logRuntimeBindingStartupState, } from "./routes/runtime-binding.js";
import { loadPersistedSessions, savePersistedSessions, } from "./services/terminal-persistence.js";
import { detectOrphanProcesses } from "./services/process-detector.js";
import { startOrphanMonitor, stopOrphanMonitor, } from "./services/orphan-monitor.js";
import { getAgentProcessManager } from "./services/agent-process-manager.js";
import { adapterRegistry } from "./adapter/index.js";
import { logger } from "./utils/logger.js";
import { autoRegister, unregister, bridgeRestartCleanup, } from "./services/auto-register.js";
import { ensureAwsClientAgentMcpReleased } from "./services/aws-client-agent-mcp.js";
import { ensureAcodePackageReleased } from "./services/acode-package.js";
import { ensureStartupConfig } from "./services/startup-config-wizard.js";
import { handleCliCommand } from "./services/cli-commands.js";
import { buildGracefulShutdownPlan, shouldCleanupPersistedSessionsOnStartup, } from "./services/runtime-lifecycle-policy.js";
import { deliverPendingBridgeLifecycleNotifications } from "./services/lifecycle-state.js";
const cliCommandResult = await handleCliCommand();
if (cliCommandResult.handled) {
    process.exit(cliCommandResult.exitCode);
}
// 验证生产环境配置
await ensureStartupConfig();
validateProductionToken();
ensureAwsClientAgentMcpReleased();
ensureAcodePackageReleased();
logRuntimeBindingStartupState();
// ★ 启动时自动注册
async function performAutoRegister() {
    try {
        const success = await autoRegister();
        if (success) {
            logger.info("[runtime-bridge] 自动注册成功");
        }
        else {
            // 检查注册状态
            const { loadConfig } = await import("./services/auto-register.js");
            const config = loadConfig();
            // 判断是否有配置（配置文件存在或环境变量设置了必填项）
            const hasConfig = config.tenantId && config.userKey;
            if (hasConfig && config.enabled) {
                // 有配置但注册失败，服务启动失败
                logger.error("[runtime-bridge] 自动注册已启用但注册失败，服务启动失败");
                logger.error("[runtime-bridge] 请检查调度中心是否可达、配置是否正确");
                process.exitCode = 1;
            }
            else {
                // 没有配置，正常启动服务
                logger.info("[runtime-bridge] 自动注册未配置，继续启动服务");
                logger.info("[runtime-bridge] 要启用自动注册，请创建配置文件 ~/.aws-bridge/config.json");
            }
        }
    }
    catch (error) {
        const err = error;
        logger.warn("[runtime-bridge] 自动注册出错:", err.message);
    }
}
performAutoRegister()
    .then(() => {
    // Bridge 启动后只做非破坏性同步：调度中心会通过 /runtime/sessions 恢复运行态。
    // 不再默认把该 bridge 下的 Agent 标记为 offline/stopped，避免 server/bridge
    // 短暂重启导致正在运行的 Agent 被误停。
    return performBridgeRestartCleanup();
})
    .then(() => deliverPendingBridgeLifecycleNotifications())
    .then(() => {
    logger.info("[runtime-bridge] Bridge 生命周期通知同步完成");
})
    .catch((err) => {
    logger.warn("[runtime-bridge] 自动注册或清理出错:", err);
});
// Bridge 启动状态同步。
// 默认不清理 Agent 状态；只有显式设置 AWS_BRIDGE_RESTART_CLEANUP=true 时才保留旧行为。
async function performBridgeRestartCleanup() {
    try {
        if (process.env.AWS_BRIDGE_RESTART_CLEANUP !== "true") {
            logger.info("[runtime-bridge] 跳过 Bridge 重启清理，保留 Agent 状态供调度中心恢复");
            return;
        }
        // 只有在已注册的情况下才执行清理
        const { getRegistrationState } = await import("./services/auto-register.js");
        const state = getRegistrationState();
        if (!state.registered || !state.instanceId) {
            logger.info("[runtime-bridge] 实例未注册，跳过 Bridge 重启清理");
            return;
        }
        logger.info("[runtime-bridge] ★ Bridge 重启清理已显式启用，正在通知调度中心清理 Agent 状态...");
        const result = await bridgeRestartCleanup();
        if (result.success) {
            logger.info(`[runtime-bridge] ★ Bridge 重启清理成功，清理了 ${result.agentCount || 0} 个 Agent`);
        }
        else {
            logger.warn(`[runtime-bridge] ★ Bridge 重启清理失败: ${result.error}`);
        }
    }
    catch (error) {
        const err = error;
        logger.warn("[runtime-bridge] Bridge 重启清理出错:", err.message);
    }
}
// ★ 启动时重建进程注册表
async function rebuildProcessRegistry() {
    try {
        const persistedSessions = await loadPersistedSessions();
        if (persistedSessions.length === 0) {
            logger.info("[runtime-bridge] 无持久化会话，跳过进程注册表重建");
            return;
        }
        const manager = getAgentProcessManager();
        await manager.rebuildRegistry(persistedSessions.map((s) => ({
            agentId: s.agentId,
            sessionId: s.sessionId,
            pid: s.pid,
            workspacePath: s.workspacePath,
            command: s.command,
            mode: "sdk",
        })));
        logger.info(`[runtime-bridge] 进程注册表重建完成: ${persistedSessions.length} 条记录`);
    }
    catch (error) {
        const err = error;
        logger.warn("[runtime-bridge] 进程注册表重建失败:", err.message);
    }
}
rebuildProcessRegistry().catch((err) => {
    logger.warn("[runtime-bridge] 进程注册表重建出错:", err);
});
// ★ 启动时检测孤儿进程。
// 默认保留持久化 running 会话并重建进程注册表，避免 runtime-bridge/server
// 重启误杀仍在运行的 SDK 进程。只有显式设置
// AWS_BRIDGE_CLEAN_ORPHANS_ON_STARTUP=true 时才执行历史清理逻辑。
async function detectOrphansOnStartup() {
    try {
        // ★★★ 检查是否存在保留会话标记文件
        const fs = await import("node:fs/promises");
        const path = await import("node:path");
        const os = await import("node:os");
        const restartFlagFile = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
        let preserveSessions = false;
        try {
            const flagContent = await fs.readFile(restartFlagFile, "utf-8");
            const flag = JSON.parse(flagContent);
            preserveSessions = flag.preserveSessions === true;
            // 读取后删除标记文件
            await fs.unlink(restartFlagFile);
            logger.info(`[runtime-bridge] ★ 检测到保留会话标记，preserveSessions=${preserveSessions}`);
        }
        catch {
            // 标记文件不存在，正常启动
        }
        const persistedSessions = await loadPersistedSessions();
        if (persistedSessions.length === 0) {
            logger.info("[runtime-bridge] 启动时无持久化会话，无需检测孤儿进程");
            return;
        }
        const shouldCleanup = shouldCleanupPersistedSessionsOnStartup(preserveSessions);
        if (!shouldCleanup) {
            logger.info(`[runtime-bridge] ★ 保留 ${persistedSessions.length} 个持久化会话并重建进程注册表`);
            // 重建进程注册表
            const manager = getAgentProcessManager();
            await manager.rebuildRegistry(persistedSessions.map((s) => ({
                agentId: s.agentId,
                sessionId: s.sessionId,
                pid: s.pid,
                workspacePath: s.workspacePath,
                command: s.command,
                mode: "sdk",
            })));
            return;
        }
        logger.info(`[runtime-bridge] ★ 已显式启用启动清理，检测 ${persistedSessions.length} 个持久化会话的孤儿进程...`);
        const orphans = detectOrphanProcesses(persistedSessions.map((s) => ({
            agentId: s.agentId,
            pid: s.pid,
            workspacePath: s.workspacePath,
            command: s.command,
        })));
        if (orphans.length > 0) {
            logger.warn(`[runtime-bridge] ★ 检测到 ${orphans.length} 个孤儿进程仍在运行：`);
            for (const orphan of orphans) {
                logger.warn(`  - agentId=${orphan.agentId}, pid=${orphan.process.pid}, command=${orphan.process.command?.slice(0, 50)}`);
            }
            // ★ 自动清理所有孤儿进程
            logger.info("[runtime-bridge] 正在自动清理所有孤儿进程...");
            for (const orphan of orphans) {
                try {
                    if (orphan.process.pid) {
                        // 使用跨平台方式终止进程
                        if (process.platform === "win32") {
                            const { execSync } = await import("node:child_process");
                            execSync(`taskkill /PID ${orphan.process.pid} /T /F`, {
                                encoding: "utf8",
                                timeout: 3000,
                            });
                            logger.info(`[runtime-bridge] 已自动终止孤儿进程: agentId=${orphan.agentId}, PID=${orphan.process.pid}`);
                        }
                        else {
                            process.kill(orphan.process.pid, "SIGKILL");
                            logger.info(`[runtime-bridge] 已自动终止孤儿进程: agentId=${orphan.agentId}, PID=${orphan.process.pid}`);
                        }
                    }
                }
                catch (killError) {
                    const err = killError;
                    logger.warn(`[runtime-bridge] 自动终止孤儿进程失败: agentId=${orphan.agentId}, PID=${orphan.process.pid}, error=${err.message}`);
                }
            }
            // 等待进程完全终止
            await new Promise((resolve) => setTimeout(resolve, 1000));
        }
        await savePersistedSessions([]);
        logger.info(`[runtime-bridge] ★ 启动清理完成，已清空所有持久化会话（清理了 ${orphans.length} 个孤儿进程）`);
    }
    catch (error) {
        const err = error;
        logger.warn("[runtime-bridge] 启动时孤儿进程检测失败:", err.message);
    }
}
// 执行孤儿进程检测
detectOrphansOnStartup().catch((err) => {
    logger.warn("[runtime-bridge] 孤儿进程检测出错:", err);
});
// ★ 启动孤儿进程定期监控
startOrphanMonitor().catch((err) => {
    logger.warn("[runtime-bridge] 启动孤儿进程监控失败:", err);
});
// 创建 Express 应用
const app = express();
// 中间件配置
app.use(cors({
    origin(origin, callback) {
        if (!origin || allowedCorsOrigins.includes(origin)) {
            callback(null, true);
            return;
        }
        callback(new Error(`[runtime-bridge] CORS origin is not allowed: ${origin}`));
    },
}));
app.use(express.json({ limit: "1mb" }));
// 注册路由
app.use("/runtime", runtimeBindingRouter);
app.use("/runtime", propertiesRouter);
app.use("/runtime", ymlRouter);
app.use("/runtime", instanceRouter);
app.use("/runtime", skillsRouter);
app.use("/runtime", mcpRouter);
app.use("/runtime", terminalRouter);
app.use("/runtime", sessionsRouter);
app.use("/runtime", processesRouter);
app.use("/runtime", systemMetricsRouter);
app.use("/runtime", gitRouter);
app.use("/runtime", memoryRouter);
app.use("/runtime", aiSourcesRouter);
app.use("/pty", ptyRouter);
app.use("/api/file-browser", fileBrowserRouter);
/**
 * 启动 HTTP 服务并对端口占用给出可诊断错误，避免未捕获异常直接崩溃。
 */
function startServer() {
    const server = app.listen(port, () => {
        logger.info(`[runtime-bridge] listening on ${port}`);
    });
    attachPtyWebSocketServer(server);
    server.on("error", (error) => {
        if (error.code === "EADDRINUSE") {
            logger.error(`[runtime-bridge] port ${port} is already in use. ` +
                "Stop the existing runtime-bridge process or set AWS_RUNTIME_BRIDGE_PORT to another port before starting.");
            process.exitCode = 1;
            return;
        }
        logger.error("[runtime-bridge] failed to start server:", error);
        process.exitCode = 1;
    });
    return server;
}
/**
 * 优雅关闭处理
 *
 * 在收到终止信号时：
 * 1. 停止所有 SDK 会话（终止 Agent 进程）
 * 2. 验证所有进程已终止
 * 3. 清空持久化状态（避免恢复残留会话）
 * 4. 关闭 HTTP 服务器
 *
 * ★★★ 修复：添加 preserveSessions 参数
 * - preserveSessions=true: aws-mcp-server 触发的关闭，保留会话状态
 * - preserveSessions=false: bridge 自己关闭，清理所有会话
 */
let isShuttingDown = false;
async function gracefulShutdown(signal, server, preserveSessions = false) {
    if (isShuttingDown) {
        logger.info(`[runtime-bridge] 已在关闭中，忽略 ${signal}`);
        return;
    }
    isShuttingDown = true;
    // ★★★ 防御性检测：如果 preserveSessions 未显式设为 true，
    // 检查 .preserve-sessions 标记文件是否存在。
    // 这是为了覆盖所有调用路径（包括非信号处理器路径）都能正确识别 aws-mcp-server 重启场景。
    if (!preserveSessions) {
        const flagExists = checkPreserveFlagSync();
        if (flagExists) {
            preserveSessions = true;
            logger.info(`[runtime-bridge] ★ 检测到 .preserve-sessions 标记文件，自动升级为保留会话模式`);
        }
    }
    logger.info(`[runtime-bridge] 收到 ${signal} 信号，开始优雅关闭... (preserveSessions=${preserveSessions})`);
    const startTime = Date.now();
    const shutdownPlan = buildGracefulShutdownPlan(preserveSessions);
    try {
        // 1. 停止所有 SDK 会话
        const sdkCount = sdkSessions.size;
        if (shutdownPlan.terminateSdkSessions) {
            logger.info(`[runtime-bridge] 正在停止 ${sdkCount} 个 SDK 会话...`);
            for (const [sessionId, entry] of sdkSessions.entries()) {
                try {
                    const providerId = entry.providerId || "claude-code";
                    const adapter = adapterRegistry.get(providerId);
                    // 尝试获取 PID 用于日志
                    let pidInfo = "";
                    try {
                        const pid = adapter.getSessionPid?.(sessionId);
                        if (pid) {
                            pidInfo = ` (PID: ${pid})`;
                        }
                    }
                    catch {
                        // 忽略 PID 获取错误
                    }
                    await adapter.terminateSession(sessionId);
                    logger.info(`[runtime-bridge] SDK 会话 ${sessionId} 已终止${pidInfo}`);
                    sdkSessions.delete(sessionId);
                }
                catch (e) {
                    const err = e;
                    logger.error(`[runtime-bridge] SDK 会话 ${sessionId} 终止失败:`, err.message);
                    // 即使失败也从内存中移除
                    sdkSessions.delete(sessionId);
                }
            }
        }
        else {
            logger.info(`[runtime-bridge] ★ 保留 ${sdkCount} 个 SDK 会话，不在重启恢复场景中终止 Agent 进程`);
        }
        const ptyCount = ptySessions.size;
        if (shutdownPlan.closePtySessions) {
            logger.info(`[runtime-bridge] 正在停止 ${ptyCount} 个 PTY 会话...`);
            closeAllPtySessions("shutdown");
        }
        else {
            logger.info(`[runtime-bridge] ★ 保留 ${ptyCount} 个 PTY 会话，不在重启恢复场景中关闭终端进程`);
        }
        // 3. 停止孤儿进程监控
        logger.info("[runtime-bridge] 正在停止孤儿进程监控...");
        stopOrphanMonitor();
        // 4. ★ 验证所有进程已终止（等待一小段时间让进程完全退出）
        if (shutdownPlan.terminateResidualProcesses) {
            logger.info("[runtime-bridge] 等待进程完全终止...");
            await new Promise((resolve) => setTimeout(resolve, 1000));
        }
        // 5. 检查是否还有残留进程，如果有则强制终止
        if (shutdownPlan.terminateResidualProcesses) {
            try {
                const { detectOrphanProcesses, terminateProcessTree } = await import("./services/process-detector.js");
                const { loadPersistedSessions } = await import("./services/terminal-persistence.js");
                const persistedSessions = await loadPersistedSessions();
                if (persistedSessions.length > 0) {
                    const orphans = detectOrphanProcesses(persistedSessions.map((s) => ({
                        agentId: s.agentId,
                        pid: s.pid,
                        workspacePath: s.workspacePath,
                        command: s.command,
                    })));
                    if (orphans.length > 0) {
                        logger.warn(`[runtime-bridge] 检测到 ${orphans.length} 个残留进程，正在强制终止...`);
                        for (const orphan of orphans) {
                            if (orphan.process.pid) {
                                // 使用进程树终止（跨平台）
                                const result = terminateProcessTree(orphan.process.pid);
                                logger.info(`[runtime-bridge] 已强制终止残留进程树：agentId=${orphan.agentId}, PID=${orphan.process.pid}, 终止了 ${result.terminated} 个进程`);
                            }
                        }
                    }
                }
            }
            catch (checkError) {
                const err = checkError;
                logger.warn("[runtime-bridge] 残留进程检查失败:", err.message);
            }
        }
        else {
            logger.info("[runtime-bridge] ★ 保留模式跳过残留进程强制终止，等待调度中心恢复会话");
        }
        // 6. 注销实例
        if (shutdownPlan.unregisterInstance) {
            logger.info("[runtime-bridge] 正在注销实例...");
            try {
                await unregister();
            }
            catch (e) {
                const err = e;
                logger.error("[runtime-bridge] 注销实例失败:", err.message);
            }
        }
        else {
            logger.info("[runtime-bridge] ★ 保留模式跳过实例注销，避免调度中心将 Agent 标记为 stopped");
        }
        // 7. ★★★ 修复：根据 preserveSessions 决定是否清空持久化状态
        // - preserveSessions=true (aws-mcp-server 触发): 保留会话状态，aws-mcp-server 重启后可恢复
        // - preserveSessions=false (bridge 自己关闭): 清空所有会话，agent 应被清理
        if (shutdownPlan.clearPersistedSessions) {
            logger.info("[runtime-bridge] 正在清空持久化会话状态...");
            try {
                await savePersistedSessions([]);
            }
            catch (e) {
                const err = e;
                logger.error("[runtime-bridge] 清空持久化状态失败:", err.message);
            }
        }
        else {
            logger.info("[runtime-bridge] ★ 保留持久化会话状态（aws-mcp-server 重启恢复场景）");
        }
        // 8. 关闭 HTTP 服务器
        const elapsed = Date.now() - startTime;
        logger.info(`[runtime-bridge] 优雅关闭完成 (耗时 ${elapsed}ms)，停止 ${sdkCount} SDK 会话`);
        server.close(() => {
            logger.info("[runtime-bridge] HTTP 服务器已关闭");
            process.exit(0);
        });
        // 强制退出超时（防止卡住）
        setTimeout(() => {
            logger.warn("[runtime-bridge] 优雅关闭超时，强制退出");
            process.exit(1);
        }, 8000); // 增加到 8 秒，给进程终止更多时间
    }
    catch (e) {
        const err = e;
        logger.error("[runtime-bridge] 优雅关闭过程中发生错误:", err);
        process.exit(1);
    }
}
// 启动服务器
const server = startServer();
/**
 * ★★★ 修复：同步检测 .preserve-sessions 标记文件
 *
 * 当 aws-mcp-server 调用 /runtime/prepare-restart 后，
 * 会在临时目录创建该标记文件。桥接器收到 SIGTERM/SIGINT 时，
 * 应检查此文件来决定是否保留会话。
 *
 * 信号处理器必须使用同步 I/O，因为异步操作在信号上下文中不可靠。
 */
const RESTART_FLAG_FILE = path.join(os.tmpdir(), "agentswork-runtime-bridge", ".preserve-sessions");
function checkPreserveFlagSync() {
    try {
        if (existsSync(RESTART_FLAG_FILE)) {
            const content = readFileSync(RESTART_FLAG_FILE, "utf-8");
            const flag = JSON.parse(content);
            return flag.preserveSessions === true;
        }
    }
    catch {
        // 文件不存在或无法解析，按不保留处理
    }
    return false;
}
// ★★★ 设置 gracefulShutdown 函数引用，供 instance router 使用
setGracefulShutdownFn(async (signal, preserveSessions) => {
    await gracefulShutdown(signal, server, preserveSessions);
});
// 注册终止信号处理器
// ★★★ 修复：收到终止信号时先检查 aws-mcp-server 是否已调用 prepare-restart
// 如果标记文件存在，说明是 aws-mcp-server 重启场景，需要保留会话状态
process.on("SIGTERM", () => {
    const preserve = checkPreserveFlagSync();
    gracefulShutdown("SIGTERM", server, preserve);
});
process.on("SIGINT", () => {
    const preserve = checkPreserveFlagSync();
    gracefulShutdown("SIGINT", server, preserve);
});
// Windows 支持（Ctrl+C 在 Windows 上触发 SIGBREAK）
if (process.platform === "win32") {
    process.on("SIGBREAK", () => {
        const preserve = checkPreserveFlagSync();
        gracefulShutdown("SIGBREAK", server, preserve);
    });
}
// 未捕获异常处理
process.on("uncaughtException", (error) => {
    logger.error("[runtime-bridge] 未捕获异常:", error);
    // ★ 未捕获异常时不检查标记文件 — 异常重启需要完整清理
    gracefulShutdown("uncaughtException", server);
});
process.on("unhandledRejection", (reason) => {
    logger.error("[runtime-bridge] 未处理的 Promise 拒绝:", reason);
    // 不立即退出，只记录错误
});
