import { Router } from "express";
import axios from "axios";
import { spawn } from "node:child_process";
import { createHash, timingSafeEqual } from "node:crypto";
import { resolveSchedulerBaseUrlFrom } from "../config.js";
import { validateToken } from "../middleware/auth.js";
import { getConfiguredConnectionKeys, requestRuntimeAccessTokenRefreshForServer } from "../services/auto-register.js";
import { discoverCcSwitchConfiguredItems, loadCcSwitchSdk } from "../services/cc-switch-sdk.js";
import { initInstance, syncLegacyStateFromSdk } from "../services/instance-init-service.js";
import { createBridgeLifecycleOperation, deliverPendingBridgeLifecycleNotifications, upsertBridgeLifecycleOperation, } from "../services/lifecycle-state.js";
import { loadInstanceState, saveInstanceState, } from "../services/instance-state.js";
import { getRuntimeAccessToken } from "../services/runtime-binding.js";
import { detectToolStatuses, ensureToolsInstalled, SUPPORTED_INSTALLABLE_TOOLS, SUPPORTED_UNINSTALLABLE_TOOLS, uninstallTools, } from "../services/tool-installer.js";
import { createLogger } from "../utils/logger.js";
const log = createLogger("instance");
const PANEL_TOOL_STATUS_KEYS = ["acode", "claude", "opencode", "codex"];
// ★★★ 导出 gracefulShutdown 的触发函数
// 用于 aws-mcp-server 通知 bridge 保留会话状态关闭
let gracefulShutdownFn = null;
export function setGracefulShutdownFn(fn) {
    gracefulShutdownFn = fn;
}
export const instanceRouter = Router();
function resolveNpmExecutable() {
    return process.platform === "win32" ? "npm.cmd" : "npm";
}
function truncateCommandOutput(output) {
    const maxLength = 16_000;
    return output.length > maxLength
        ? `${output.slice(output.length - maxLength)}\n[output truncated]`
        : output;
}
function runCommand(command, args) {
    return new Promise((resolve, reject) => {
        const child = spawn(command, args, {
            shell: false,
            windowsHide: true,
        });
        let stdout = "";
        let stderr = "";
        child.stdout.on("data", (chunk) => {
            stdout += chunk.toString("utf8");
        });
        child.stderr.on("data", (chunk) => {
            stderr += chunk.toString("utf8");
        });
        child.on("error", reject);
        child.on("close", (exitCode) => {
            resolve({
                command,
                args,
                exitCode,
                stdout: truncateCommandOutput(stdout),
                stderr: truncateCommandOutput(stderr),
            });
        });
    });
}
function normalizeOptionalText(value) {
    const normalized = String(value || "").trim();
    return normalized || undefined;
}
function resolveRequestRuntimeBridgeBaseUrl(req) {
    const host = req.get("host");
    if (!host) {
        return undefined;
    }
    return `${req.protocol}://${host}`;
}
async function runBridgeUpdateInBackground(input) {
    const runningOperation = {
        ...input.operation,
        status: "running",
        message: "Bridge 更新正在后台执行。",
    };
    await upsertBridgeLifecycleOperation(runningOperation);
    try {
        log.info("[update-bridge] 正在后台更新 aws-runtime-bridge 到最新版本");
        const result = await runCommand(input.command, input.args);
        const succeeded = result.exitCode === 0;
        await upsertBridgeLifecycleOperation({
            ...runningOperation,
            status: succeeded ? "succeeded" : "failed",
            message: succeeded
                ? "Bridge 更新完成，请重启实例 Bridge 以加载新版本。"
                : `Bridge 更新失败，npm 退出码: ${result.exitCode ?? "unknown"}`,
            error: succeeded ? undefined : result.stderr || result.stdout,
        });
        await deliverPendingBridgeLifecycleNotifications();
    }
    catch (error) {
        const err = error;
        log.error("[update-bridge] background update failed:", err);
        await upsertBridgeLifecycleOperation({
            ...runningOperation,
            status: "failed",
            message: "Bridge 更新失败。",
            error: err.message || "update bridge failed",
        });
        await deliverPendingBridgeLifecycleNotifications();
    }
}
async function writeRestartSessionFlag(preserveSessions) {
    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");
    if (preserveSessions) {
        await fs.mkdir(path.dirname(restartFlagFile), { recursive: true });
        await fs.writeFile(restartFlagFile, JSON.stringify({
            preserveSessions: true,
            timestamp: new Date().toISOString(),
        }), "utf-8");
        log.info(`[prepare-restart] 已创建保留会话标记文件: ${restartFlagFile}`);
        return;
    }
    try {
        await fs.unlink(restartFlagFile);
        log.info("[prepare-restart] 已删除保留会话标记文件");
    }
    catch {
        // 文件不存在时无需处理。
    }
}
function md5Hex(value) {
    return createHash("md5").update(value, "utf8").digest("hex");
}
function hasMatchingConnectionKeyDigest(connectionKeyMd5) {
    const provided = connectionKeyMd5.trim().toLowerCase();
    if (!/^[a-f0-9]{32}$/.test(provided)) {
        return false;
    }
    const providedBuffer = Buffer.from(provided, "hex");
    return getConfiguredConnectionKeys()
        .map((key) => key.trim())
        .filter(Boolean)
        .some((key) => {
        const configuredBuffer = Buffer.from(md5Hex(key), "hex");
        return (configuredBuffer.length === providedBuffer.length &&
            timingSafeEqual(configuredBuffer, providedBuffer));
    });
}
export function buildConnectionCheckResponse(connectionKeyMd5) {
    const configuredConnectionKeys = getConfiguredConnectionKeys();
    const connectionKeyRequired = configuredConnectionKeys.length > 0;
    if (!connectionKeyRequired) {
        return {
            status: 200,
            body: {
                ok: true,
                runtimeBridge: "healthy",
                connectionKeyMatched: true,
                connectionKeyRequired: false,
            },
        };
    }
    if (hasMatchingConnectionKeyDigest(connectionKeyMd5)) {
        return {
            status: 200,
            body: {
                ok: true,
                runtimeBridge: "healthy",
                connectionKeyMatched: true,
                connectionKeyRequired: true,
            },
        };
    }
    return {
        status: 401,
        body: {
            ok: false,
            error: "connection_key_mismatch",
            connectionKeyMatched: false,
            connectionKeyRequired: true,
        },
    };
}
/**
 * 构建调度中心 ping 失败响应。
 * 主流程：保留底层错误与 scheduler 地址，同时明确 bridge 本身已连通、失败点在 bridge 回连调度中心。
 */
export function buildSchedulerPingFailureResponse(error, configuredSchedulerBaseUrl) {
    const trimmedSchedulerBaseUrl = configuredSchedulerBaseUrl.trim();
    const schedulerUrl = trimmedSchedulerBaseUrl || "http://localhost:8080";
    const isLocalhostScheduler = /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?(?:\/|$)/i.test(schedulerUrl);
    return {
        ok: false,
        error: error.message || "scheduler ping failed",
        failureStage: "scheduler_ping",
        runtimeBridge: "healthy",
        schedulerBaseUrl: schedulerUrl,
        hint: isLocalhostScheduler
            ? "aws-runtime-bridge 已连通，但它回连调度中心失败。请在 bridge 启动环境中将 AWS_RUNTIME_SCHEDULER_BASE_URL 配置为该实例可访问的调度中心地址，例如 http://<server-host>:7380。"
            : "aws-runtime-bridge 已连通，但它回连调度中心失败。请确认 AWS_RUNTIME_SCHEDULER_BASE_URL 指向的调度中心地址可从 bridge 机器访问，并且运行时访问令牌仍有效。",
    };
}
function getAxiosStatusCode(error) {
    if (typeof error !== "object" || error === null) {
        return undefined;
    }
    const response = Reflect.get(error, "response");
    if (typeof response !== "object" || response === null) {
        return undefined;
    }
    const status = Reflect.get(response, "status");
    return typeof status === "number" ? status : undefined;
}
async function pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken) {
    try {
        const schedulerResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": runtimeAccessToken } });
        return {
            schedulerReachable: schedulerResponse.status >= 200 && schedulerResponse.status < 300,
            tokenRefreshed: false,
        };
    }
    catch (error) {
        if (getAxiosStatusCode(error) !== 401) {
            throw error;
        }
        const refreshResult = await requestRuntimeAccessTokenRefreshForServer(schedulerBaseUrl);
        if (!refreshResult.success || !refreshResult.runtimeAccessToken) {
            throw error;
        }
        const retryResponse = await axios.get(`${schedulerBaseUrl}/api/runtime/ping`, { headers: { "X-Runtime-Token": refreshResult.runtimeAccessToken } });
        return {
            schedulerReachable: retryResponse.status >= 200 && retryResponse.status < 300,
            tokenRefreshed: true,
        };
    }
}
/**
 * Build the response for ambiguous scheduler targets.
 * Main flow: never silently pick the first target; require an explicit scheduler URL or future scheduler identity routing.
 */
export function buildAmbiguousSchedulerBaseUrlResponse(targetUrls) {
    return {
        ok: false,
        error: "ambiguous_scheduler_base_url",
        failureStage: "scheduler_ping",
        runtimeBridge: "healthy",
        schedulerBaseUrl: "",
        autoRegisterTargetUrls: targetUrls,
        hint: "Multiple autoRegisterTargets.serverUrl values were found, so bridge cannot safely infer which scheduler /runtime/ping should call back. Set AWS_RUNTIME_SCHEDULER_BASE_URL explicitly, or use future schedulerId/token-bound multi-scheduler routing.",
    };
}
/**
 * 构建工具状态检测目标列表。
 * 主流程：合并实例启用工具与配置面板固定展示工具，避免历史状态缺少 Codex 时前端误判为待检测。
 */
export function buildToolStatusDetectionTargets(enabledTools) {
    return Array.from(new Set([...enabledTools, ...PANEL_TOOL_STATUS_KEYS]
        .map((tool) => String(tool || "").trim().toLowerCase())
        .filter(Boolean)));
}
/**
 * 汇总卸载后仍可执行的工具，用于避免把部分卸载误报为成功。
 */
export function buildUninstallFailureMessage(requestedTools, toolStatus) {
    const failedTools = requestedTools.filter((tool) => toolStatus[tool]?.installed);
    if (failedTools.length === 0) {
        return "";
    }
    return failedTools
        .map((tool) => {
        const status = toolStatus[tool];
        const detail = status?.error || status?.executable || "command is still available";
        return `${tool}: ${detail}`;
    })
        .join("; ");
}
export function summarizeToolStatus(toolStatus) {
    return Object.fromEntries(Object.entries(toolStatus || {}).map(([tool, status]) => [
        tool,
        {
            installed: Boolean(status?.installed),
            version: status?.version || null,
            executable: status?.executable || null,
            error: status?.error || null,
        },
    ]));
}
instanceRouter.get("/healthz", (_req, res) => {
    res.json({
        ok: true,
        runtimeBridge: "healthy",
    });
});
instanceRouter.get("/connection-check", (req, res) => {
    const connectionKeyMd5 = String(req.header("X-Bridge-Connection-Key-MD5") || "");
    const result = buildConnectionCheckResponse(connectionKeyMd5);
    res.status(result.status).json(result.body);
});
instanceRouter.get("/ping", validateToken, async (req, res) => {
    const schedulerBaseUrlResolution = resolveSchedulerBaseUrlFrom(req.header("X-Scheduler-Base-Url"));
    if (schedulerBaseUrlResolution.source === "ambiguous-auto-register-targets") {
        res
            .status(400)
            .json(buildAmbiguousSchedulerBaseUrlResponse(schedulerBaseUrlResolution.ambiguousTargetUrls));
        return;
    }
    const schedulerBaseUrl = schedulerBaseUrlResolution.url;
    try {
        const runtimeAccessToken = getRuntimeAccessToken(undefined, schedulerBaseUrl);
        if (!runtimeAccessToken) {
            res.status(401).json({ ok: false, error: "runtime_access_token_required" });
            return;
        }
        const schedulerPing = await pingSchedulerWithToken(schedulerBaseUrl, runtimeAccessToken);
        res.json({
            ok: true,
            runtimeBridge: "healthy",
            schedulerReachable: schedulerPing.schedulerReachable,
            tokenRefreshed: schedulerPing.tokenRefreshed,
            schedulerBaseUrl,
        });
    }
    catch (error) {
        const err = error;
        res
            .status(502)
            .json(buildSchedulerPingFailureResponse(err, schedulerBaseUrl));
    }
});
instanceRouter.post("/init-instance", validateToken, async (req, res) => {
    const { agentId, workspacePath, skillEnabled, mcpEnabled, ccSwitchEnabledTools, skillPackage, skillPackages, mcpServers, } = req.body || {};
    if (!agentId || !workspacePath) {
        res.status(400).json({ error: "agentId and workspacePath are required" });
        return;
    }
    try {
        log.info("[init-instance] Request received", {
            agentId: String(agentId),
            workspacePath: String(workspacePath),
            skillEnabled,
            mcpEnabled,
            ccSwitchEnabledTools,
        });
        const result = await initInstance(agentId, workspacePath, {
            skillEnabled,
            mcpEnabled,
            ccSwitchEnabledTools,
            skillPackage,
            skillPackages,
            mcpServers,
        });
        log.info("[init-instance] Initialization completed", {
            agentId: String(agentId),
            ok: result.ok,
            logCount: result.logs?.length || 0,
            enabledTools: result.enabledTools,
        });
        res.json(result);
    }
    catch (error) {
        const err = error;
        log.error("[init-instance] Initialization failed", {
            agentId: String(agentId),
            workspacePath: String(workspacePath),
            error: err?.message || String(error),
            stack: err?.stack || null,
        });
        res.status(400).json({
            error: err?.message || "initialize instance failed",
        });
    }
});
instanceRouter.post("/cc-switch/state", validateToken, async (req, res) => {
    const { agentId } = req.body || {};
    if (!agentId) {
        res.status(400).json({ error: "agentId is required" });
        return;
    }
    try {
        log.info("[cc-switch/state] Request received", { agentId: String(agentId) });
        const sdk = await loadCcSwitchSdk(agentId);
        const imported = await discoverCcSwitchConfiguredItems(sdk);
        const synced = await syncLegacyStateFromSdk(agentId, sdk);
        const state = synced.state;
        const detectionTargets = buildToolStatusDetectionTargets(state.enabledTools || []);
        log.info("[cc-switch/state] Detecting SDK statuses", {
            agentId: String(agentId),
            detectionTargets,
        });
        const toolStatus = await detectToolStatuses(detectionTargets);
        log.info("[cc-switch/state] SDK status detection completed", {
            agentId: String(agentId),
            toolStatus: summarizeToolStatus(toolStatus),
            importedMcpCount: imported.mcpServers,
            importedSkillCount: imported.skills,
        });
        res.json({
            ok: true,
            agentId: String(agentId),
            imported: {
                mcpServers: imported.mcpServers,
                skills: imported.skills,
            },
            state: { ...state, toolStatus },
        });
    }
    catch (error) {
        const err = error;
        log.error("[cc-switch/state] Failed to load state", {
            agentId: String(agentId),
            error: err?.message || String(error),
            stack: err?.stack || null,
        });
        res.status(400).json({ error: err?.message || "load state failed" });
    }
});
instanceRouter.post("/cc-switch/install-tools", validateToken, async (req, res) => {
    const { agentId, tools } = req.body || {};
    if (!agentId) {
        res.status(400).json({ error: "agentId is required" });
        return;
    }
    const requestedTools = Array.isArray(tools)
        ? tools
            .map((tool) => String(tool || "")
            .trim()
            .toLowerCase())
            .filter(Boolean)
        : [];
    log.info("[cc-switch/install-tools] Request received", {
        agentId: String(agentId),
        requestedTools,
    });
    const supportedInstallableTools = new Set(SUPPORTED_INSTALLABLE_TOOLS);
    const installableTools = requestedTools.filter((tool) => supportedInstallableTools.has(tool));
    log.info("[cc-switch/install-tools] Request normalized", {
        agentId: String(agentId),
        requestedTools,
        installableTools,
        supportedInstallableTools: SUPPORTED_INSTALLABLE_TOOLS,
    });
    if (installableTools.length === 0) {
        log.warn("[cc-switch/install-tools] No supported installable tools requested", {
            agentId: String(agentId),
            requestedTools,
        });
        res
            .status(400)
            .json({
            error: `tools must include ${SUPPORTED_INSTALLABLE_TOOLS.join(", ")}`,
        });
        return;
    }
    try {
        const state = await loadInstanceState(agentId);
        const enabledTools = Array.from(new Set([...(state.enabledTools || []), ...installableTools]));
        log.info("[cc-switch/install-tools] Starting installer", {
            agentId: String(agentId),
            installableTools,
            previousEnabledTools: state.enabledTools || [],
            nextEnabledTools: enabledTools,
        });
        const toolStatus = await ensureToolsInstalled(installableTools);
        log.info("[cc-switch/install-tools] Installer completed", {
            agentId: String(agentId),
            toolStatus: summarizeToolStatus(toolStatus),
        });
        const refreshedToolStatus = await detectToolStatuses(enabledTools);
        log.info("[cc-switch/install-tools] Refreshed enabled SDK statuses", {
            agentId: String(agentId),
            refreshedToolStatus: summarizeToolStatus(refreshedToolStatus),
        });
        const nextToolStatus = {
            ...refreshedToolStatus,
            ...toolStatus,
        };
        const savedState = await saveInstanceState(agentId, {
            ...state,
            enabledTools,
            toolStatus: nextToolStatus,
        });
        const installableToolSet = new Set(installableTools);
        const failedTools = Object.entries(nextToolStatus)
            .filter(([tool]) => installableToolSet.has(tool))
            .filter(([, status]) => !status.installed)
            .map(([tool, status]) => ({ tool, error: status.error || null }));
        if (failedTools.length > 0) {
            log.warn("[cc-switch/install-tools] Some SDKs remain unavailable", {
                agentId: String(agentId),
                failedTools,
            });
        }
        log.info("[cc-switch/install-tools] State saved", {
            agentId: String(agentId),
            enabledTools: savedState.enabledTools,
            toolStatus: summarizeToolStatus(nextToolStatus),
        });
        res.json({
            ok: failedTools.length === 0,
            agentId: String(agentId),
            toolStatus: nextToolStatus,
            failedTools,
            state: savedState,
        });
    }
    catch (error) {
        const err = error;
        log.error("[cc-switch/install-tools] Install route failed", {
            agentId: String(agentId),
            requestedTools,
            installableTools,
            error: err?.message || String(error),
            stack: err?.stack || null,
        });
        res.status(400).json({ error: err?.message || "install tools failed" });
    }
});
instanceRouter.post("/cc-switch/uninstall-tools", validateToken, async (req, res) => {
    const { agentId, tools } = req.body || {};
    if (!agentId) {
        res.status(400).json({ error: "agentId is required" });
        return;
    }
    const requestedTools = Array.isArray(tools)
        ? tools
            .map((tool) => String(tool || "")
            .trim()
            .toLowerCase())
            .filter(Boolean)
        : [];
    const supportedUninstallableTools = new Set(SUPPORTED_UNINSTALLABLE_TOOLS);
    const uninstallableTools = requestedTools.filter((tool) => supportedUninstallableTools.has(tool));
    if (uninstallableTools.length === 0) {
        res.status(400).json({
            error: `tools must include ${SUPPORTED_UNINSTALLABLE_TOOLS.join(", ")}`,
        });
        return;
    }
    try {
        const state = await loadInstanceState(agentId);
        const uninstallableToolSet = new Set(uninstallableTools);
        const enabledTools = (state.enabledTools || []).filter((tool) => !uninstallableToolSet.has(String(tool || "").trim().toLowerCase()));
        const uninstalledToolStatus = await uninstallTools(uninstallableTools);
        const refreshedToolStatus = await detectToolStatuses([
            ...enabledTools,
            ...uninstallableTools,
        ]);
        const nextToolStatus = {
            ...refreshedToolStatus,
            ...uninstalledToolStatus,
        };
        const uninstallFailureMessage = buildUninstallFailureMessage(uninstallableTools, nextToolStatus);
        const nextEnabledTools = uninstallFailureMessage
            ? state.enabledTools || []
            : enabledTools;
        const savedState = await saveInstanceState(agentId, {
            ...state,
            enabledTools: nextEnabledTools,
            toolStatus: nextToolStatus,
        });
        if (uninstallFailureMessage) {
            res.status(400).json({
                ok: false,
                agentId: String(agentId),
                error: `uninstall incomplete: ${uninstallFailureMessage}`,
                toolStatus: nextToolStatus,
                state: savedState,
            });
            return;
        }
        res.json({
            ok: true,
            agentId: String(agentId),
            toolStatus: nextToolStatus,
            state: savedState,
        });
    }
    catch (error) {
        const err = error;
        res.status(400).json({ error: err?.message || "uninstall tools failed" });
    }
});
/**
 * ★★★ 新增：aws-mcp-server 通知 bridge 准备重启
 * POST /runtime/prepare-restart
 *
 * 当 aws-mcp-server 准备重启时，调用此接口通知 bridge：
 * - bridge 保留所有会话状态（不清空持久化）
 * - aws-mcp-server 重启后可以从 bridge 恢复 agent 状态
 *
 * 请求体：
 * - preserveSessions: true = 保留会话（aws-mcp-server 重启）
 *                     false = 清理会话（bridge 自己关闭）
 */
instanceRouter.post("/prepare-restart", validateToken, async (req, res) => {
    const { preserveSessions = true } = req.body || {};
    try {
        log.info(`[prepare-restart] 收到准备重启通知，preserveSessions=${preserveSessions}`);
        // 记录重启模式到文件，供下次启动时判断。
        await writeRestartSessionFlag(Boolean(preserveSessions));
        res.json({
            ok: true,
            preserveSessions,
            message: preserveSessions
                ? "Bridge will preserve sessions for aws-mcp-server restart"
                : "Bridge will clean up sessions on shutdown",
        });
    }
    catch (error) {
        const err = error;
        log.error("[prepare-restart] Error:", err);
        res.status(500).json({ error: err.message });
    }
});
/**
 * POST /runtime/update-bridge
 *
 * 通过当前 Bridge 进程执行全局 npm 包更新，等价于 CLI 的
 * `awsb update`，用于面板上的“更新实例 Bridge”。
 */
instanceRouter.post("/update-bridge", validateToken, async (req, res) => {
    const command = resolveNpmExecutable();
    const args = ["install", "-g", "aws-runtime-bridge@latest"];
    const requestedByUserId = normalizeOptionalText(req.body?.requestedByUserId);
    try {
        if (!requestedByUserId) {
            res.status(400).json({ error: "requestedByUserId is required" });
            return;
        }
        const operation = createBridgeLifecycleOperation({
            operationType: "update",
            requestedByUserId,
            schedulerBaseUrl: req.body?.schedulerBaseUrl,
            runtimeBridgeBaseUrl: resolveRequestRuntimeBridgeBaseUrl(req),
            status: "accepted",
            message: "Bridge 更新请求已收到，将在后台执行。",
        });
        await upsertBridgeLifecycleOperation(operation);
        void runBridgeUpdateInBackground({ command, args, operation });
        res.json({
            ok: true,
            operationId: operation.operationId,
            message: "Bridge 更新已在后台开始，完成后会通过消息中心通知结果。",
        });
    }
    catch (error) {
        const err = error;
        log.error("[update-bridge] Error:", err);
        res.status(500).json({ error: err.message || "update bridge failed" });
    }
});
/**
 * POST /runtime/restart-bridge
 *
 * 写入保留会话标记后优雅关闭当前 Bridge 进程。真正的重新拉起由
 * systemd、PM2、Docker 外部守护或用户手动启动完成。
 */
instanceRouter.post("/restart-bridge", validateToken, async (req, res) => {
    const { preserveSessions = true } = req.body || {};
    const requestedByUserId = normalizeOptionalText(req.body?.requestedByUserId);
    try {
        if (!requestedByUserId) {
            res.status(400).json({ error: "requestedByUserId is required" });
            return;
        }
        log.info(`[restart-bridge] 收到实例 Bridge 重启请求，preserveSessions=${preserveSessions}`);
        const operation = createBridgeLifecycleOperation({
            operationType: "restart",
            requestedByUserId,
            schedulerBaseUrl: req.body?.schedulerBaseUrl,
            runtimeBridgeBaseUrl: resolveRequestRuntimeBridgeBaseUrl(req),
            status: "accepted",
            message: "Bridge 已收到重启请求；下次启动后会发送确认通知。",
        });
        await upsertBridgeLifecycleOperation(operation);
        await writeRestartSessionFlag(Boolean(preserveSessions));
        res.json({
            ok: true,
            operationId: operation.operationId,
            preserveSessions: Boolean(preserveSessions),
            message: "Bridge 正在优雅退出；下次启动后会通过消息中心通知结果。若未由守护进程管理，请手动重新启动。",
        });
        setTimeout(() => {
            if (gracefulShutdownFn) {
                gracefulShutdownFn("PANEL_RESTART", Boolean(preserveSessions)).catch((error) => {
                    const err = error;
                    log.error("[restart-bridge] graceful shutdown failed:", err);
                    process.exit(1);
                });
                return;
            }
            process.exit(0);
        }, 250);
    }
    catch (error) {
        const err = error;
        log.error("[restart-bridge] Error:", err);
        res.status(500).json({ error: err.message || "restart bridge failed" });
    }
});
