/**
 * 终端 API 路由
 *
 * 提供 SDK Agent 会话的启动、输入、调整大小和停止功能
 * 终端 UI 仅作为 SDK Agent 的消息输入/状态输出载体
 */
import { spawn } from "node:child_process";
import { promises as fs } from "node:fs";
import os from "node:os";
import path from "node:path";
import { TextDecoder } from "node:util";
import { Router } from "express";
import { v4 as uuidv4 } from "uuid";
import axios from "axios";
import { adapterRegistry, getToolActionInfo, isTransientClipboardPasteFilePath, redactTransientClipboardPastePaths, registerSdkProviders, resolveSdkProviderIdByCommand, } from "../adapter/index.js";
import { validateToken } from "../middleware/auth.js";
import { getAgentProcessManager } from "../services/agent-process-manager.js";
import { getAiConfigService } from "../services/cc-switch-sdk.js";
import { enqueueMcpLaunchBinding } from "../services/mcp-launch-binding-queue.js";
import { findClaudeCodeProcesses, isProcessRunning, } from "../services/process-detector.js";
import { sendOutput, sendQuestionRequest, sendStatus, } from "../services/session-output.js";
import { findPersistedSessionByAgentId, removePersistedSession, savePersistedSessions, updatePersistedSessionAutoCommands, updatePersistedSessionRuntimeState, upsertPersistedSession, } from "../services/terminal-persistence.js";
import { logger as log } from "../utils/logger.js";
export const terminalRouter = Router();
// 导出 SDK 会话存储，供其他模块使用（如 sessions.ts）
export const sdkSessions = new Map();
const TOOL_RESULT_PREVIEW_MAX_LENGTH = 4000;
const AI_REQUEST_BODY_MARKER = "\n\n请求体:\n";
const AI_REQUEST_HEADERS_MARKER = "\n\n请求头:\n";
const startingAgents = new Set();
export function resolveRuntimeContextResetSupport(providerId, hasResetContext) {
    if (providerId !== "acode") {
        return {
            supported: false,
            statusCode: 400,
            error: "context reset without restart is only supported by ACode",
        };
    }
    if (!hasResetContext) {
        return {
            supported: false,
            statusCode: 500,
            error: "ACode adapter does not expose resetContext",
        };
    }
    return { supported: true };
}
/**
 * Decide whether SDK startup should wait for an external process PID.
 * Main flow: in-process providers return immediately; external CLI providers keep PID probing for orphan cleanup.
 */
export function shouldProbeSdkProcessPid(providerId) {
    return providerId !== "acode";
}
/**
 * 主流程：解析 runtime stop 请求的状态通知选项。
 * 内部上下文重置可抑制 stopped 广播；普通手动停止默认继续通知调度端。
 */
export function parseRuntimeStopOptions(body) {
    const suppressStatus = typeof body === "object" &&
        body !== null &&
        "suppressStatus" in body &&
        body.suppressStatus === true;
    const reason = typeof body === "object" &&
        body !== null &&
        "reason" in body &&
        typeof body.reason === "string" &&
        body.reason.trim()
        ? body.reason.trim()
        : undefined;
    return {
        statusNotified: !suppressStatus,
        reason,
    };
}
export function resolveStatusChangeUsage(actionInfo) {
    const usage = actionInfo?.usage;
    if (!usage)
        return undefined;
    const inputTokens = Number.isFinite(usage.inputTokens)
        ? usage.inputTokens
        : 0;
    const outputTokens = Number.isFinite(usage.outputTokens)
        ? usage.outputTokens
        : 0;
    if (inputTokens <= 0 && outputTokens <= 0)
        return undefined;
    return { inputTokens, outputTokens };
}
export function resolveStatusChangeRuntimeState(status, actionInfo) {
    const normalizedStatus = String(status || "")
        .trim()
        .toLowerCase();
    const normalizedActionType = String(actionInfo?.actionType || "")
        .trim()
        .toLowerCase();
    if (normalizedStatus === "tool_using" && normalizedActionType === "idle") {
        return actionInfo
            ? { status: "waiting_input", actionInfo }
            : { status: "waiting_input" };
    }
    return actionInfo ? { status, actionInfo } : { status };
}
const terminalCommandStates = new Map();
const DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING = "gb18030";
const DEFAULT_TERMINAL_OUTPUT_ENCODING = "utf-8";
const TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT = 300;
const TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS = 60_000;
function padDatePart(value) {
    return String(value).padStart(2, "0");
}
export function buildAcodeCommandOutputPath(workspacePath, now = new Date()) {
    const year = now.getFullYear();
    const month = padDatePart(now.getMonth() + 1);
    const day = padDatePart(now.getDate());
    const hour = padDatePart(now.getHours());
    const minute = padDatePart(now.getMinutes());
    const timestamp = `${year}${month}${day}-${hour}${minute}${padDatePart(now.getSeconds())}-${String(now.getMilliseconds()).padStart(3, "0")}`;
    return path.join(workspacePath, ".agentswork", "acode", "cmd-dist", `${year}-${month}-${day}`, hour, minute, `${timestamp}-cmd.out`);
}
export function countTerminalOutputLines(output) {
    if (!output) {
        return 0;
    }
    const normalized = output.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
    const withoutTrailingLineBreak = normalized.endsWith("\n")
        ? normalized.slice(0, -1)
        : normalized;
    return withoutTrailingLineBreak
        ? withoutTrailingLineBreak.split("\n").length
        : 1;
}
export function formatCommandOutputSpilloverNotice(outputPath, lineLimit = TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT) {
    return `\r\n[ACode] 命令输出超过 ${lineLimit} 行，完整输出已写入: ${outputPath}\r\n[ACode] Agent 可自行读取该文件，或在该文件中搜索需要的内容。\r\n`;
}
export function formatCommandInactivityTimeoutNotice(timeoutMs = TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS) {
    return `\r\n[ACode] 命令超过 ${Math.round(timeoutMs / 1000)} 秒没有新的输出，已停止执行并保留此前输出。\r\n`;
}
async function persistTerminalCommandOutput(state) {
    const outputPath = state.outputPath ||
        buildAcodeCommandOutputPath(state.workspacePath, state.startedAt);
    state.outputPath = outputPath;
    await fs.mkdir(path.dirname(outputPath), { recursive: true });
    await fs.writeFile(outputPath, state.chunks.join(""), "utf-8");
    return outputPath;
}
/**
 * 标准化终端输入：将前端回车符还原为 shell 可执行命令文本。
 */
export function normalizeTerminalCommandInput(value) {
    return String(value ?? "")
        .replace(/\r/g, "\n")
        .trim();
}
/**
 * 解析终端命令输出解码编码。
 * 主流程：优先使用环境变量覆盖 -> Windows 默认 GB18030 -> 其他平台默认 UTF-8。
 */
export function resolveTerminalOutputEncoding(platform = process.platform, env = process.env) {
    const configuredEncoding = env.AWS_TERMINAL_OUTPUT_ENCODING?.trim();
    if (configuredEncoding) {
        return configuredEncoding;
    }
    return platform === "win32"
        ? DEFAULT_WINDOWS_TERMINAL_OUTPUT_ENCODING
        : DEFAULT_TERMINAL_OUTPUT_ENCODING;
}
/**
 * 创建终端输出流式解码器，避免多字节中文被 chunk 边界截断。
 */
export function createTerminalOutputDecoder(encoding = resolveTerminalOutputEncoding()) {
    try {
        return new TextDecoder(encoding, { fatal: false });
    }
    catch {
        return new TextDecoder(DEFAULT_TERMINAL_OUTPUT_ENCODING, { fatal: false });
    }
}
/**
 * 解码终端输出 chunk；end=true 时刷新解码器内部剩余字节。
 */
export function decodeTerminalOutputChunk(decoder, chunk, end = false) {
    if (end) {
        return decoder.decode();
    }
    if (!chunk || chunk.length === 0) {
        return "";
    }
    return decoder.decode(chunk, { stream: true });
}
/**
 * 解析 cd 命令目标；返回 undefined 表示不是目录切换命令，null 表示仅查看当前目录。
 */
export function parseTerminalDirectoryChangeTarget(command) {
    const match = command.trim().match(/^cd(?:\s+(.*))?$/i);
    if (!match) {
        return undefined;
    }
    const rawTarget = match[1]?.trim();
    if (!rawTarget) {
        return null;
    }
    const withoutDriveSwitch = rawTarget.replace(/^\/d\s+/i, "").trim();
    if ((withoutDriveSwitch.startsWith('"') && withoutDriveSwitch.endsWith('"')) ||
        (withoutDriveSwitch.startsWith("'") && withoutDriveSwitch.endsWith("'"))) {
        return withoutDriveSwitch.slice(1, -1);
    }
    return withoutDriveSwitch;
}
function getTerminalCommandState(entry) {
    const existing = terminalCommandStates.get(entry.sessionId);
    if (existing) {
        return existing;
    }
    const state = {
        currentDirectory: entry.workspacePath,
        runningProcess: null,
    };
    terminalCommandStates.set(entry.sessionId, state);
    return state;
}
function findActiveSdkSessionByAgentId(agentId) {
    for (const entry of sdkSessions.values()) {
        if (entry.agentId === agentId) {
            return entry;
        }
    }
    return undefined;
}
async function buildExistingSessionResponse(entry) {
    const adapter = adapterRegistry.get(entry.providerId || "claude-code");
    const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(entry.sessionId), entry.currentRuntimeStatus, entry.currentRuntimeActionType);
    return {
        sessionId: entry.sessionId,
        status,
        runtimeStatus: status,
        runtimeActionType: entry.currentRuntimeActionType,
        runtimeActionLabel: entry.currentRuntimeActionLabel,
        runtimeActionDetail: entry.currentRuntimeActionDetail,
        agentId: entry.agentId,
        workspacePath: entry.workspacePath,
        command: entry.config.command,
        mode: "sdk",
        providerSessionId: adapter.getProviderSessionId(entry.sessionId),
        reused: true,
    };
}
/**
 * 解析 SDK 会话对外展示状态。
 * 主流程：优先使用 adapter 当前细粒度状态，其次使用路由内存记录的最近状态，最后才兼容旧会话回退 running。
 */
export function resolveSdkSessionDisplayStatus(adapterStatus, currentRuntimeStatus, currentRuntimeActionType) {
    const normalizedAdapterStatus = String(adapterStatus || "").trim();
    if (normalizedAdapterStatus) {
        const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
        const normalizedActionType = String(currentRuntimeActionType || "")
            .trim()
            .toLowerCase();
        if (normalizedAdapterStatus.toLowerCase() === "tool_using" &&
            normalizedRuntimeStatus.toLowerCase() === "waiting_input" &&
            normalizedActionType === "idle") {
            return normalizedRuntimeStatus;
        }
        return normalizedAdapterStatus;
    }
    const normalizedRuntimeStatus = String(currentRuntimeStatus || "").trim();
    return normalizedRuntimeStatus || "running";
}
/**
 * Decide whether a persisted SDK session can be reused for a start request.
 * Main flow: require a live PID, then reject in-process providers whose adapter control channel cannot be restored.
 */
export function evaluatePersistedSessionReuse(session, isPidRunning = isProcessRunning) {
    if (!session.pid) {
        return { reusable: false, reason: "missing-pid" };
    }
    if (!isPidRunning(session.pid)) {
        return { reusable: false, reason: "dead-pid" };
    }
    if (resolveSdkProviderId(session.command) === "acode") {
        return { reusable: false, reason: "in-process-provider" };
    }
    return { reusable: true, reason: "active-pid" };
}
function stopTerminalCommandProcess(sessionId) {
    const state = terminalCommandStates.get(sessionId);
    if (!state?.runningProcess) {
        terminalCommandStates.delete(sessionId);
        return;
    }
    try {
        state.runningProcess.kill();
    }
    catch {
        // ignore best-effort cleanup failures; session shutdown must continue
    }
    finally {
        state.runningProcess = null;
        terminalCommandStates.delete(sessionId);
    }
}
function appendCommandOutput(entry, output) {
    if (!output) {
        return;
    }
    entry.seq += 1;
    void sendOutput(entry.agentId, output, entry.sessionId, entry.seq, undefined, {
        runtimeAccessToken: entry.runtimeAccessToken,
        schedulerBaseUrl: entry.schedulerBaseUrl,
    });
}
/**
 * 生成显示在终端输出流中的工作目录提示符。
 */
export function formatTerminalPrompt(currentDirectory) {
    return `\x1b[36m${currentDirectory}>\x1b[0m `;
}
function resolveDirectoryTarget(currentDirectory, target) {
    if (target === "~") {
        return os.homedir();
    }
    return path.resolve(currentDirectory, target);
}
/**
 * 处理内置目录切换命令，保持终端后续命令的当前目录连续。
 */
async function handleDirectoryChangeCommand(entry, state, command) {
    const target = parseTerminalDirectoryChangeTarget(command);
    if (target === undefined) {
        return false;
    }
    if (target === null) {
        appendCommandOutput(entry, `${state.currentDirectory}\r\n`);
        return true;
    }
    const nextDirectory = resolveDirectoryTarget(state.currentDirectory, target);
    try {
        const stat = await fs.stat(nextDirectory);
        if (!stat.isDirectory()) {
            appendCommandOutput(entry, `cd: not a directory: ${nextDirectory}\r\n`);
            return true;
        }
        state.currentDirectory = nextDirectory;
        appendCommandOutput(entry, `${state.currentDirectory}\r\n`);
        return true;
    }
    catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        appendCommandOutput(entry, `cd: ${message}\r\n`);
        return true;
    }
}
/**
 * 在当前 Agent 工作目录对应的终端上下文中执行命令。
 */
async function executeTerminalCommand(sessionId, commandValue) {
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        throw new Error("SDK session not found");
    }
    const command = normalizeTerminalCommandInput(commandValue);
    const state = getTerminalCommandState(entry);
    if (!command) {
        return { started: false, cwd: state.currentDirectory };
    }
    if (state.runningProcess) {
        throw new Error("A terminal command is already running for this session");
    }
    const displayCommand = redactTransientClipboardPastePaths(command);
    appendCommandOutput(entry, `${formatTerminalPrompt(state.currentDirectory)}${displayCommand}\r\n`);
    if (isTransientClipboardPasteFilePath(command)) {
        appendCommandOutput(entry, "[Terminal Warning] 检测到剪贴板临时 paste 文件路径，已跳过执行。请粘贴文件内容或使用显式读取命令。\r\n");
        appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
        return { started: false, cwd: state.currentDirectory };
    }
    const handledDirectoryChange = await handleDirectoryChangeCommand(entry, state, command);
    if (handledDirectoryChange) {
        return { started: false, cwd: state.currentDirectory };
    }
    await sendStatus(entry.agentId, sessionId, "tool_using", {
        actionType: "bash",
        actionLabel: "终端命令",
        actionDetail: displayCommand,
    }, undefined, {
        runtimeAccessToken: entry.runtimeAccessToken,
        schedulerBaseUrl: entry.schedulerBaseUrl,
    });
    const child = spawn(command, {
        cwd: state.currentDirectory,
        shell: true,
        env: buildRuntimeEnv(entry.agentId, entry.workspacePath, process.env, entry.config.envOverrides),
    });
    state.runningProcess = child;
    const stdoutDecoder = createTerminalOutputDecoder();
    const stderrDecoder = createTerminalOutputDecoder();
    const usesAcodeCommandOutputPolicy = entry.providerId === "acode";
    const outputSpilloverState = {
        workspacePath: entry.workspacePath,
        startedAt: new Date(),
        chunks: [],
        spilled: false,
    };
    let inactivityTimer;
    const clearInactivityTimer = () => {
        if (inactivityTimer) {
            clearTimeout(inactivityTimer);
            inactivityTimer = undefined;
        }
    };
    const resetInactivityTimer = () => {
        clearInactivityTimer();
        inactivityTimer = setTimeout(() => {
            if (state.runningProcess === child) {
                appendCommandOutput(entry, formatCommandInactivityTimeoutNotice());
                child.kill();
            }
        }, TERMINAL_COMMAND_INACTIVITY_TIMEOUT_MS);
    };
    const appendProcessOutput = (output, trackActivity = true) => {
        if (!output) {
            return;
        }
        if (usesAcodeCommandOutputPolicy) {
            outputSpilloverState.chunks.push(output);
        }
        if (usesAcodeCommandOutputPolicy && trackActivity) {
            resetInactivityTimer();
        }
        if (usesAcodeCommandOutputPolicy && outputSpilloverState.spilled) {
            return;
        }
        if (usesAcodeCommandOutputPolicy &&
            countTerminalOutputLines(outputSpilloverState.chunks.join("")) >
                TERMINAL_COMMAND_OUTPUT_SPILLOVER_LINE_LIMIT) {
            outputSpilloverState.spilled = true;
            void persistTerminalCommandOutput(outputSpilloverState)
                .then((outputPath) => {
                appendCommandOutput(entry, formatCommandOutputSpilloverNotice(outputPath));
            })
                .catch((error) => {
                const message = error instanceof Error ? error.message : String(error);
                appendCommandOutput(entry, `\r\n[ACode] 命令输出落盘失败: ${message}\r\n`);
            });
            return;
        }
        appendCommandOutput(entry, output);
    };
    if (usesAcodeCommandOutputPolicy) {
        resetInactivityTimer();
    }
    child.stdout.on("data", (chunk) => {
        appendProcessOutput(decodeTerminalOutputChunk(stdoutDecoder, chunk));
    });
    child.stderr.on("data", (chunk) => {
        appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, chunk));
    });
    child.on("error", (error) => {
        appendCommandOutput(entry, `\r\n[Terminal Error] ${error.message}\r\n`);
    });
    child.on("close", (code, signal) => {
        clearInactivityTimer();
        state.runningProcess = null;
        appendProcessOutput(decodeTerminalOutputChunk(stdoutDecoder, undefined, true), false);
        appendProcessOutput(decodeTerminalOutputChunk(stderrDecoder, undefined, true), false);
        if (outputSpilloverState.spilled) {
            void persistTerminalCommandOutput(outputSpilloverState).catch((error) => {
                const message = error instanceof Error ? error.message : String(error);
                appendCommandOutput(entry, `\r\n[ACode] 命令输出最终落盘失败: ${message}\r\n`);
            });
        }
        if (code && code !== 0) {
            appendCommandOutput(entry, `\r\n[exit ${code}]\r\n`);
        }
        else if (signal) {
            appendCommandOutput(entry, `\r\n[signal ${signal}]\r\n`);
        }
        appendCommandOutput(entry, formatTerminalPrompt(state.currentDirectory));
        void sendStatus(entry.agentId, sessionId, "waiting_input", undefined, undefined, {
            runtimeAccessToken: entry.runtimeAccessToken,
            schedulerBaseUrl: entry.schedulerBaseUrl,
        });
    });
    return { started: true, cwd: state.currentDirectory };
}
export function buildRuntimeEnv(agentId, workspacePath, baseEnv, envOverrides) {
    const env = {};
    for (const [key, value] of Object.entries(baseEnv)) {
        if (value !== undefined) {
            env[key] = value;
        }
    }
    if (workspacePath) {
        env.AWS_WORKSPACE_PATH = String(workspacePath);
    }
    if (envOverrides) {
        for (const [key, value] of Object.entries(envOverrides)) {
            const normalizedKey = key.trim();
            if (!normalizedKey || value === undefined || value === null) {
                continue;
            }
            const normalizedValue = String(value).trim();
            if (!normalizedValue) {
                continue;
            }
            env[normalizedKey] = normalizedValue;
        }
    }
    env.AWS_AGENT_ID = String(agentId);
    env.AWS_MCP_CLAIM_LAUNCH_BINDING = "true";
    return env;
}
function stripWrappingQuotes(value) {
    const trimmed = value.trim();
    const first = trimmed[0];
    const last = trimmed[trimmed.length - 1];
    if ((first === '"' || first === "'") && first === last) {
        return trimmed.slice(1, -1);
    }
    return trimmed;
}
/**
 * 主流程：SDK 模式输入若只是 mini-term 剪贴板临时文件路径，则展开为文件内容再发给模型。
 * 具体实现：只处理整串匹配的绝对临时 paste 文件路径，避免影响显式 `type "path"` 等正常命令。
 */
export async function resolveSdkInputMessage(input) {
    const message = String(input);
    if (!isTransientClipboardPasteFilePath(message)) {
        return message;
    }
    const filePath = stripWrappingQuotes(message);
    if (!path.isAbsolute(filePath)) {
        return message;
    }
    return fs.readFile(filePath, "utf-8");
}
/**
 * 主流程：将已持久化的 ClaudeCode AI 配置合并到启动配置，再让请求级覆盖保持最高优先级。
 */
export function buildClaudeCodeLaunchConfig(savedClaudeConfig, requestEnvOverrides) {
    const envOverrides = {};
    for (const source of [savedClaudeConfig?.env, requestEnvOverrides]) {
        if (!source) {
            continue;
        }
        for (const [key, value] of Object.entries(source)) {
            const normalizedKey = key.trim();
            if (!normalizedKey || value === undefined || value === null) {
                continue;
            }
            const normalizedValue = String(value).trim();
            if (!normalizedValue) {
                continue;
            }
            envOverrides[normalizedKey] = normalizedValue;
        }
    }
    const model = envOverrides.ANTHROPIC_MODEL?.trim() ||
        envOverrides.CODEX_MODEL?.trim() ||
        undefined;
    return { envOverrides, model };
}
/**
 * 具体逻辑：读取 ClaudeCode live settings；读取失败不阻断启动，保持原 SDK 启动路径可用。
 */
async function readSavedClaudeCodeLaunchConfig() {
    try {
        return await getAiConfigService().readClaudeCodeConfig();
    }
    catch (error) {
        const err = error;
        console.warn(`[Runtime] Failed to read saved ClaudeCode AI config: ${err.message}`);
        return {};
    }
}
function formatToolResultForTimeline(result) {
    if (result === undefined || result === null) {
        return undefined;
    }
    const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
    const normalized = text.trim();
    if (!normalized) {
        return undefined;
    }
    if (normalized.length <= TOOL_RESULT_PREVIEW_MAX_LENGTH) {
        return normalized;
    }
    return `${normalized.slice(0, TOOL_RESULT_PREVIEW_MAX_LENGTH)}\n...（结果过长，已截断）`;
}
function isMcpProviderEvent(event) {
    const actionType = event.data.actionType;
    if (actionType === "mcp") {
        return true;
    }
    const toolName = String(event.data.toolName || "").toLowerCase();
    return (toolName.startsWith("mcp__") ||
        toolName.includes("-mcp__") ||
        toolName.includes("mcp_"));
}
/**
 * 将 SDK 工具结束事件转换为时间线返回数据状态。
 * 主流程：格式化工具结果 -> 保留 MCP 专用展示 -> 其它工具按动作类型追加到对应时间线事件。
 */
export function buildToolResultTimelineActionInfo(event) {
    if (event.type !== "tool_use_end") {
        return undefined;
    }
    const actionResult = formatToolResultForTimeline(event.data.toolResult ?? event.data.actionResult);
    if (!actionResult) {
        return undefined;
    }
    const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
    const actionType = event.data.actionType || inferredAction.actionType;
    if (actionType === "idle") {
        return undefined;
    }
    if (actionType === "mcp" && isMcpProviderEvent(event)) {
        return {
            actionType: "mcp",
            actionLabel: "MCP返回",
            actionDetail: event.data.actionDetail || event.data.toolName,
            actionId: event.data.actionId || event.data.toolUseId,
            actionResult,
        };
    }
    return {
        actionType,
        actionLabel: event.data.actionLabel || inferredAction.actionLabel,
        actionDetail: event.data.actionDetail ||
            inferredAction.actionDetail ||
            event.data.toolName,
        actionId: event.data.actionId || event.data.toolUseId,
        actionResult,
    };
}
export function formatSdkOutputEvent(event) {
    if (event.type === "ai_request") {
        const model = event.data.model ? ` ${event.data.model}` : "";
        const preview = event.data.lastUserMessagePreview
            ? `：${event.data.lastUserMessagePreview}`
            : "";
        return `\r\n[ACode] 正在请求模型${model}${preview}\r\n`;
    }
    if (event.type === "text_delta") {
        return event.data.text || undefined;
    }
    if (event.type === "thinking") {
        return event.data.text || undefined;
    }
    if (event.type === "command_output") {
        return event.data.text || undefined;
    }
    if (event.type === "error") {
        return event.data.text
            ? `\r\n[SDK Error] ${event.data.text}\r\n`
            : undefined;
    }
    return undefined;
}
export function buildSdkOutputMetadata(event) {
    const logMessage = formatSdkLogMessage(event);
    return {
        sourceEventType: event.type,
        logCategory: "acode",
        ...buildCommandOutputMetadata(event),
        ...(logMessage
            ? {
                logLevel: resolveSdkLogLevel(event),
                logMessage,
            }
            : {}),
    };
}
function resolveSdkLogLevel(event) {
    if (event.type === "error") {
        return "error";
    }
    if (event.type === "command_output" && event.data.stream === "stderr") {
        return "warn";
    }
    return "info";
}
function buildCommandOutputMetadata(event) {
    if (event.type !== "command_output") {
        return {};
    }
    return {
        ...(typeof event.data.stream === "string"
            ? { stream: event.data.stream }
            : {}),
        ...(typeof event.data.sequence === "number"
            ? { sequence: event.data.sequence }
            : {}),
        ...(typeof event.data.cwd === "string" ? { cwd: event.data.cwd } : {}),
        ...(typeof event.data.commandFilePath === "string"
            ? { commandFilePath: event.data.commandFilePath }
            : {}),
        ...(typeof event.data.commandLineCount === "number"
            ? { commandLineCount: event.data.commandLineCount }
            : {}),
    };
}
function formatSdkLogMessage(event) {
    if (event.type === "ai_request") {
        const model = event.data.model || "unknown-model";
        const endpoint = event.data.endpoint ? ` @ ${event.data.endpoint}` : "";
        const toolCount = typeof event.data.toolCount === "number"
            ? `, tools=${event.data.toolCount}`
            : "";
        return `AI request started: ${model}${endpoint}${toolCount}`;
    }
    if (event.type === "tool_use_start") {
        return formatSdkToolLogMessage(event, "开始");
    }
    if (event.type === "tool_use_end") {
        return formatSdkToolLogMessage(event, "完成");
    }
    if (event.type === "command_output") {
        return formatCommandOutputLogMessage(event);
    }
    if (event.type === "turn_complete") {
        const finishReason = event.data.finishReason
            ? `, finish=${event.data.finishReason}`
            : "";
        return `Turn completed${finishReason}`;
    }
    if (event.type === "session_complete") {
        return `Session completed: exitCode=${event.data.exitCode ?? 0}`;
    }
    if (event.type === "error") {
        return event.data.text || "ACode runtime error";
    }
    return undefined;
}
function formatCommandOutputLogMessage(event) {
    const stream = typeof event.data.stream === "string" ? event.data.stream : "mixed";
    const sequence = typeof event.data.sequence === "number" ? ` #${event.data.sequence}` : "";
    const normalized = String(event.data.text || "")
        .replace(/\s+/g, " ")
        .trim();
    const preview = normalized.length > 240 ? `${normalized.slice(0, 240)}...` : normalized;
    return `命令输出(${stream}${sequence}): ${preview || "(empty)"}`;
}
function formatSdkToolLogMessage(event, phaseLabel) {
    const actionInfo = getToolActionInfo(event.data.toolName, event.data.toolInput);
    const actionLabel = event.data.actionLabel || actionInfo.actionLabel || "调用工具";
    const detail = resolveSdkToolLogDetail(event, actionInfo);
    return detail
        ? `${phaseLabel}${actionLabel}: ${detail}`
        : `${phaseLabel}${actionLabel}: ${event.data.actionDetail || event.data.toolName || "tool"}`;
}
function resolveSdkToolLogDetail(event, actionInfo) {
    const input = event.data.toolInput;
    if (actionInfo.actionType === "bash") {
        return (getCommandDisplayDetail(input) ||
            event.data.actionDetail ||
            actionInfo.actionDetail);
    }
    if (actionInfo.actionType === "read_file" ||
        actionInfo.actionType === "edit_file" ||
        actionInfo.actionType === "write_file") {
        return (getFileDisplayDetail(input) ||
            event.data.actionDetail ||
            actionInfo.actionDetail);
    }
    return event.data.actionDetail || actionInfo.actionDetail;
}
function getCommandDisplayDetail(input) {
    const command = firstDisplayString(input?.command, input?.cmd);
    if (command)
        return command;
    if (Array.isArray(input?.argv)) {
        const argv = input.argv
            .map((item) => String(item ?? "").trim())
            .filter(Boolean)
            .join(" ");
        return argv ? redactTransientClipboardPastePaths(argv) : undefined;
    }
    return firstDisplayString(input?.args);
}
function getFileDisplayDetail(input) {
    const paths = displayStringList(input?.paths);
    if (paths.length > 0) {
        return paths.length === 1
            ? paths[0]
            : `${paths.length}个文件: ${paths.join(", ")}`;
    }
    return firstDisplayString(input?.file_path, input?.filePath, input?.path, input?.target_file, input?.targetFile);
}
function firstDisplayString(...values) {
    for (const value of values) {
        if (typeof value === "string" && value.trim()) {
            return redactTransientClipboardPastePaths(value.trim());
        }
        if (Array.isArray(value)) {
            const first = displayStringList(value)[0];
            if (first)
                return first;
        }
    }
    return undefined;
}
function displayStringList(value) {
    if (!Array.isArray(value))
        return [];
    return value
        .map((item) => (typeof item === "string" ? item.trim() : ""))
        .filter(Boolean)
        .map((item) => redactTransientClipboardPastePaths(item));
}
function formatAiRequestDetail(data) {
    const lines = [
        data.model ? `模型: ${data.model}` : undefined,
        data.endpoint || data.requestUrl
            ? `接口: ${data.endpoint || data.requestUrl}`
            : undefined,
        data.requestMethod ? `方法: ${data.requestMethod}` : undefined,
        typeof data.stream === "boolean"
            ? `流式: ${data.stream ? "是" : "否"}`
            : undefined,
        typeof data.messageCount === "number"
            ? `上下文消息: ${data.messageCount}`
            : undefined,
        typeof data.toolCount === "number"
            ? `可用工具: ${data.toolCount}`
            : undefined,
        data.lastUserMessagePreview
            ? `最后请求: ${data.lastUserMessagePreview}`
            : undefined,
    ].filter((line) => Boolean(line));
    const requestHeaderLines = formatAiRequestHeaders(data.requestHeaders);
    const base = lines.join("\n") || "ACode 正在发送 AI 请求";
    const headerBlock = requestHeaderLines.length > 0
        ? `${AI_REQUEST_HEADERS_MARKER}${requestHeaderLines.join("\n")}`
        : "";
    const bodyBlock = data.requestBody
        ? `${AI_REQUEST_BODY_MARKER}${data.requestBody}`
        : "";
    return `${base}${headerBlock}${bodyBlock}`;
}
function formatAiRequestHeaders(headers) {
    if (!headers)
        return [];
    return Object.entries(headers)
        .map(([name, value]) => `${name}: ${value}`)
        .filter((line) => line.trim().length > 0);
}
/**
 * 主干流程：把 provider 事件转换为面板运行状态。
 *
 * text_delta 表示助手正在生成；thinking 表示推理/内部过程。
 * tool_use_start 表示正在执行工具；turn_complete 才表示真正空闲。
 */
function isWaitingInputProgressEvent(event, currentRuntimeStatus) {
    const normalizedStatus = String(currentRuntimeStatus || "")
        .trim()
        .toLowerCase();
    if (normalizedStatus !== "waiting_input") {
        return false;
    }
    return event.type === "thinking" || event.type === "text_delta";
}
export function resolveRuntimeStatusFromProviderEvent(event, currentRuntimeStatus) {
    if (isWaitingInputProgressEvent(event, currentRuntimeStatus)) {
        return undefined;
    }
    if (event.type === "ai_request") {
        return {
            status: "thinking",
            actionInfo: {
                actionType: "ai_request",
                actionLabel: "AI请求",
                actionDetail: formatAiRequestDetail(event.data),
            },
        };
    }
    if (event.type === "thinking" || event.type === "text_delta") {
        return { status: "thinking" };
    }
    if (event.type === "tool_use_start") {
        const inferredAction = getToolActionInfo(event.data.toolName, event.data.toolInput);
        const actionDetail = resolveSdkToolLogDetail(event, inferredAction);
        if (event.data.actionType === "idle" ||
            inferredAction.actionType === "idle") {
            return {
                status: "waiting_input",
                actionInfo: {
                    actionType: event.data.actionType || inferredAction.actionType,
                    actionLabel: event.data.actionLabel || inferredAction.actionLabel,
                    actionDetail: actionDetail ||
                        event.data.actionDetail ||
                        inferredAction.actionDetail ||
                        event.data.toolName,
                    actionId: event.data.actionId || event.data.toolUseId,
                },
            };
        }
        return {
            status: "tool_using",
            actionInfo: {
                actionType: event.data.actionType || inferredAction.actionType,
                actionLabel: event.data.actionLabel || inferredAction.actionLabel,
                actionDetail: actionDetail ||
                    event.data.actionDetail ||
                    inferredAction.actionDetail ||
                    event.data.toolName,
                actionId: event.data.actionId || event.data.toolUseId,
            },
        };
    }
    if (event.type === "turn_complete") {
        const responseActionInfo = event.data
            .responseBody
            ? {
                actionType: "ai_request",
                actionLabel: "AI响应",
                actionResult: event.data.responseBody,
                usage: event.data.usage,
            }
            : undefined;
        return {
            status: "waiting_input",
            actionInfo: responseActionInfo ||
                (event.data.usage ? { usage: event.data.usage } : undefined),
        };
    }
    if (event.type === "error") {
        return { status: "error" };
    }
    return undefined;
}
function forwardSdkOutputEvent(event, entry) {
    const output = formatSdkOutputEvent(event);
    if (!output) {
        return;
    }
    entry.seq += 1;
    void sendOutput(entry.agentId, output, event.sessionId, entry.seq, buildSdkOutputMetadata(event), {
        runtimeAccessToken: entry.runtimeAccessToken,
        schedulerBaseUrl: entry.schedulerBaseUrl,
    });
}
export function resolveSdkProviderId(command) {
    return resolveSdkProviderIdByCommand(command);
}
// ============ 初始化 Adapter ============
let adapterInitialized = false;
function ensureAdapterInitialized() {
    if (adapterInitialized)
        return;
    registerSdkProviders(adapterRegistry, wireSdkAdapterEvents);
    adapterInitialized = true;
}
function wireSdkAdapterEvents(adapter, definition) {
    adapter.on("event", (event) => {
        const entry = sdkSessions.get(event.sessionId);
        if (entry) {
            console.log(`[${definition.displayName} Adapter] Event: ${event.type} for session ${event.sessionId}`);
            const runtimeStatus = resolveRuntimeStatusFromProviderEvent(event, entry.currentRuntimeStatus);
            if (runtimeStatus) {
                void sendStatus(entry.agentId, event.sessionId, runtimeStatus.status, runtimeStatus.actionInfo, resolveStatusChangeUsage(runtimeStatus.actionInfo), {
                    runtimeAccessToken: entry.runtimeAccessToken,
                    schedulerBaseUrl: entry.schedulerBaseUrl,
                });
            }
            forwardSdkOutputEvent(event, entry);
            const toolResultActionInfo = buildToolResultTimelineActionInfo(event);
            if (toolResultActionInfo) {
                void sendStatus(entry.agentId, event.sessionId, "tool_result", toolResultActionInfo, undefined, {
                    runtimeAccessToken: entry.runtimeAccessToken,
                    schedulerBaseUrl: entry.schedulerBaseUrl,
                });
            }
        }
    });
    adapter.on("status-change", (sessionId, status, actionInfo) => {
        const entry = sdkSessions.get(sessionId);
        if (entry) {
            const runtimeState = resolveStatusChangeRuntimeState(status, actionInfo);
            entry.currentRuntimeStatus = runtimeState.status;
            entry.currentRuntimeActionType = runtimeState.actionInfo?.actionType;
            entry.currentRuntimeActionLabel = runtimeState.actionInfo?.actionLabel;
            entry.currentRuntimeActionDetail =
                runtimeState.actionInfo?.actionDetail;
            console.log(`[${definition.displayName} Adapter] Status changed: ${runtimeState.status} for agent ${entry.agentId}`, runtimeState.actionInfo || "");
            void updatePersistedSessionRuntimeState(sessionId, {
                runtimeStatus: runtimeState.status,
                runtimeActionType: runtimeState.actionInfo?.actionType,
                runtimeActionLabel: runtimeState.actionInfo?.actionLabel,
                runtimeActionDetail: runtimeState.actionInfo?.actionDetail,
            });
            void sendStatus(entry.agentId, sessionId, runtimeState.status, runtimeState.actionInfo, resolveStatusChangeUsage(runtimeState.actionInfo), {
                runtimeAccessToken: entry.runtimeAccessToken,
                schedulerBaseUrl: entry.schedulerBaseUrl,
            });
        }
    });
    adapter.on("ask-user-question", (data) => {
        const entry = sdkSessions.get(data.sessionId);
        if (entry) {
            console.log(`[${definition.displayName} Adapter] AskUserQuestion for agent ${entry.agentId}`);
            void sendQuestionRequest(entry.agentId, data.sessionId, Array.isArray(data.questions) ? data.questions : [], {
                runtimeAccessToken: entry.runtimeAccessToken,
                schedulerBaseUrl: entry.schedulerBaseUrl,
            });
        }
    });
    adapter.on("permission-request", (data) => {
        const entry = sdkSessions.get(data.sessionId);
        if (entry) {
            console.log(`[${definition.displayName} Adapter] Permission request: ${data.toolName} for agent ${entry.agentId}`);
        }
    });
}
/**
 * 启动 SDK Agent 会话
 * POST /runtime/start
 */
terminalRouter.post("/start", validateToken, async (req, res) => {
    const { agentId, workspacePath, mode = "sdk" } = req.body || {};
    if (!agentId || !workspacePath) {
        res.status(400).json({ error: "agentId and workspacePath are required" });
        return;
    }
    enqueueMcpLaunchBinding({
        agentId: String(agentId),
        workspacePath: String(workspacePath),
        serverUrl: req.body?.serverUrl || req.body?.schedulerBaseUrl,
        runtimeAccessToken: req.body?.runtimeAccessToken,
        userId: req.body?.userId,
        schedulerBaseUrl: req.body?.schedulerBaseUrl,
    });
    if (mode && mode !== "sdk") {
        console.warn(`[Runtime] Unsupported runtime mode "${mode}" requested for agent ${agentId}; starting SDK session instead.`);
    }
    await startSdkSession(req, res);
});
/**
 * 启动 SDK 会话（两阶段架构）
 *
 * 主流程：
 * 1. Phase 1（同步，快）：校验 → 创建会话记录 → 立即响应（status=starting）
 * 2. Phase 2（后台异步，慢）：SDK 进程启动 → PID 探测 → 持久化 → 通知 server 会话就绪
 *
 * 此架构确保 bridge HTTP 响应不等待 SDK 子进程初始化，
 * 前端可在数百毫秒内收到响应，实时展示启动进度。
 */
async function startSdkSession(req, res) {
    const { agentId, workspacePath, command, autoAccept = true, initialPrompt, 
    // MCP 配置 - 加载 aws-client-agent-mcp
    mcpConfigPath, extraMcpServers, envOverrides, 
    // 空闲命令配置
    idleInputAutoCommand, nonInputAutoCommand, } = req.body || {};
    let sessionId = null;
    const normalizedAgentId = String(agentId || "").trim();
    const runtimeAccessToken = String(req.body?.runtimeAccessToken || "").trim() || undefined;
    const requestedSchedulerBaseUrl = String(req.body?.schedulerBaseUrl || "").trim() || undefined;
    try {
        ensureAdapterInitialized();
        if (!normalizedAgentId) {
            res.status(400).json({ error: "agentId is required" });
            return;
        }
        const activeSession = findActiveSdkSessionByAgentId(normalizedAgentId);
        if (activeSession) {
            res.json(await buildExistingSessionResponse(activeSession));
            return;
        }
        const persistedSession = await findPersistedSessionByAgentId(normalizedAgentId);
        if (persistedSession) {
            const reuseDecision = evaluatePersistedSessionReuse(persistedSession);
            if (!reuseDecision.reusable) {
                console.warn(`[Runtime] Ignoring stale persisted SDK session for agentId=${normalizedAgentId}, ` +
                    `sessionId=${persistedSession.sessionId}, reason=${reuseDecision.reason}`);
                await removePersistedSession(persistedSession.sessionId);
            }
            else {
                res.json({
                    sessionId: persistedSession.sessionId,
                    status: persistedSession.status,
                    agentId: persistedSession.agentId,
                    workspacePath: persistedSession.workspacePath,
                    command: persistedSession.command,
                    mode: persistedSession.mode || "sdk",
                    pid: persistedSession.pid,
                    reused: true,
                    persistedOnly: true,
                });
                return;
            }
        }
        if (startingAgents.has(normalizedAgentId)) {
            res
                .status(409)
                .json({ error: "SDK session is already starting for this agent" });
            return;
        }
        startingAgents.add(normalizedAgentId);
        // 根据命令选择对应的 adapter
        const normalizedCommand = (command || "acode").toLowerCase();
        const providerId = resolveSdkProviderId(command);
        console.log(`[Runtime] ★★★ SDK启动 ★★★ command="${command}", normalizedCommand="${normalizedCommand}", providerId="${providerId}"`);
        const adapter = adapterRegistry.get(providerId);
        sessionId = uuidv4();
        const savedClaudeConfig = providerId === "claude-code"
            ? await readSavedClaudeCodeLaunchConfig()
            : null;
        const claudeLaunchConfig = providerId === "claude-code"
            ? buildClaudeCodeLaunchConfig(savedClaudeConfig, envOverrides)
            : buildClaudeCodeLaunchConfig(null, envOverrides);
        // 构建SDK配置
        const config = {
            command: command || "acode",
            workingDirectory: workspacePath,
            autoAccept: Boolean(autoAccept),
            initialPrompt,
            model: claudeLaunchConfig.model,
            // MCP 配置：不再默认注入 aws-mcp；由用户在面板中安装/配置
            mcpConfigPath,
            extraMcpServers: extraMcpServers || {},
            envOverrides: buildRuntimeEnv(String(agentId), String(workspacePath), process.env, claudeLaunchConfig.envOverrides),
        };
        // 存储会话信息
        sdkSessions.set(sessionId, {
            agentId: normalizedAgentId,
            workspacePath,
            sessionId,
            config,
            providerId, // 记录使用的 provider
            seq: 0,
            runtimeAccessToken,
            schedulerBaseUrl: requestedSchedulerBaseUrl,
        });
        // ★ Phase 1：立即响应（不等待 SDK 进程启动）
        res.json({
            sessionId,
            status: "starting",
            runtimeStatus: "starting",
            agentId: normalizedAgentId,
            workspacePath,
            command: config.command,
            mode: "sdk",
        });
        // ★ Phase 2：后台异步启动 SDK 进程 + 后处理
        startSdkSessionBg({
            sessionId,
            agentId: normalizedAgentId,
            config,
            adapter,
            providerId,
            idleInputAutoCommand,
            nonInputAutoCommand,
            runtimeAccessToken,
            requestedSchedulerBaseUrl,
        });
    }
    catch (error) {
        // 同步错误处理（校验失败等）
        if (sessionId) {
            sdkSessions.delete(sessionId);
        }
        if (normalizedAgentId) {
            startingAgents.delete(normalizedAgentId);
        }
        if (!res.headersSent) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            res
                .status(500)
                .json({ error: `Failed to start SDK session: ${errorMessage}` });
        }
    }
}
/**
 * Phase 2：后台异步启动 SDK 进程，完成后通知 server。
 *
 * 主流程：adapter.startSession（SDK 进程启动）→ 空闲命令注入 → PID 探测
 * → 持久化会话 → 注册进程管理 → 通知 server 状态为 running
 * → finally 清理 startingAgents 锁
 */
async function startSdkSessionBg(params) {
    const { sessionId, agentId, config, adapter, providerId, idleInputAutoCommand, nonInputAutoCommand, runtimeAccessToken, requestedSchedulerBaseUrl, } = params;
    try {
        // 启动 SDK 会话（真正的耗时操作）
        await adapter.startSession(sessionId, config);
        // 尽早注入空闲命令
        if (idleInputAutoCommand || nonInputAutoCommand) {
            adapter.setIdleCommands?.(sessionId, {
                idleInputCommand: idleInputAutoCommand || "",
                nonInputCommand: nonInputAutoCommand || "",
            });
        }
        // ★ 尝试获取 SDK 进程的 PID（用于孤儿进程检测）
        let sdkPid;
        if (shouldProbeSdkProcessPid(providerId)) {
            try {
                await new Promise((resolve) => setTimeout(resolve, 500));
                sdkPid = adapter.getSessionPid?.(sessionId);
                if (!sdkPid) {
                    const processes = await findClaudeCodeProcesses();
                    const matched = processes.find((p) => p.agentId === agentId ||
                        p.cwd === config.workingDirectory ||
                        p.command.includes(config.workingDirectory));
                    if (matched) {
                        sdkPid = matched.pid;
                        console.log(`[Runtime] Found SDK process PID: ${sdkPid} for agentId=${agentId}`);
                    }
                }
            }
            catch (pidError) {
                console.warn(`[Runtime] Failed to get SDK PID for agentId=${agentId}:`, pidError);
            }
        }
        // ★ 持久化会话信息（包含 PID）
        const startedStatus = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(sessionId), sdkSessions.get(sessionId)?.currentRuntimeStatus, sdkSessions.get(sessionId)?.currentRuntimeActionType);
        await upsertPersistedSession({
            sessionId,
            agentId,
            workspacePath: config.workingDirectory,
            command: config.command,
            startedAt: new Date().toISOString(),
            status: "running",
            runtimeStatus: startedStatus,
            mode: "sdk",
            pid: sdkPid,
            providerSessionId: adapter.getProviderSessionId(sessionId),
        });
        // ★ 注册到进程管理器（如果有 PID）
        if (sdkPid) {
            const processManager = getAgentProcessManager();
            await processManager.startProcess({
                agentId,
                sessionId,
                pid: sdkPid,
                mode: "sdk",
                workspacePath: config.workingDirectory,
                command: config.command,
            });
        }
        // ★ 通知 server 会话已就绪
        await notifyServerSessionReady({
            agentId,
            sessionId,
            sdkPid,
            status: startedStatus,
            schedulerBaseUrl: requestedSchedulerBaseUrl,
            runtimeAccessToken,
        });
    }
    catch (error) {
        // 后台启动失败：清理本地状态 + 通知 server
        sdkSessions.delete(sessionId);
        await removePersistedSession(sessionId);
        const errorMessage = error instanceof Error ? error.message : String(error);
        console.error(`[Runtime] Background SDK session startup failed for agentId=${agentId}: ${errorMessage}`);
        await notifyServerSessionFailed({
            agentId,
            sessionId,
            schedulerBaseUrl: requestedSchedulerBaseUrl,
            runtimeAccessToken,
            errorMessage,
        });
    }
    finally {
        startingAgents.delete(agentId);
    }
}
/**
 * 通知调度中心 SDK 会话已完全就绪。
 * 主流程：Phase 2 完成后调用，复用 /callback/status 端点更新状态为 running。
 * Fire-and-forget 风格，失败不影响 bridge 自身状态。
 */
async function notifyServerSessionReady(params) {
    const { agentId, sessionId, sdkPid, status, schedulerBaseUrl, runtimeAccessToken } = params;
    if (!schedulerBaseUrl || !runtimeAccessToken) {
        log.debug(`[Runtime] notifyServerSessionReady skipped: missing schedulerBaseUrl or runtimeAccessToken for agentId=${agentId}`);
        return;
    }
    const url = `${schedulerBaseUrl.replace(/\/+$/, "")}/api/runtime/callback/status`;
    try {
        await axios.post(url, { agentId, sessionId, status, pid: sdkPid }, {
            headers: {
                "X-Runtime-Token": runtimeAccessToken,
                "Content-Type": "application/json",
            },
            timeout: 5000,
        });
        log.debug(`[Runtime] Notified server session ready: agentId=${agentId}, sessionId=${sessionId}, status=${status}`);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        log.warn(`[Runtime] Failed to notify server session ready: agentId=${agentId}, error=${message}`);
    }
}
/**
 * 通知调度中心 SDK 会话启动失败。
 * 主流程：Phase 2 catch 块调用，复用 /callback/status 端点更新状态为 stopped。
 */
async function notifyServerSessionFailed(params) {
    const { agentId, sessionId, schedulerBaseUrl, runtimeAccessToken } = params;
    if (!schedulerBaseUrl || !runtimeAccessToken) {
        return;
    }
    const url = `${schedulerBaseUrl.replace(/\/+$/, "")}/api/runtime/callback/status`;
    try {
        await axios.post(url, { agentId, sessionId, status: "stopped" }, {
            headers: {
                "X-Runtime-Token": runtimeAccessToken,
                "Content-Type": "application/json",
            },
            timeout: 5000,
        });
        log.debug(`[Runtime] Notified server session failed: agentId=${agentId}, sessionId=${sessionId}`);
    }
    catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        log.warn(`[Runtime] Failed to notify server session failed: agentId=${agentId}, error=${message}`);
    }
}
// ============ SDK 专用路由 ============
/**
 * 发送消息到 SDK 会话
 * POST /runtime/sdk/message
 */
terminalRouter.post("/sdk/message", validateToken, async (req, res) => {
    const { sessionId, message } = req.body || {};
    if (!sessionId || !message) {
        res.status(400).json({ error: "sessionId and message are required" });
        return;
    }
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    try {
        // 根据 providerId 获取对应的 adapter
        const providerId = entry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        await adapter.sendMessage(sessionId, message);
        res.json({ ok: true });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
/**
 * 回答 AskUserQuestion
 * POST /runtime/sdk/answer
 */
terminalRouter.post("/sdk/answer", validateToken, async (req, res) => {
    const { sessionId, answers } = req.body || {};
    if (!sessionId || !answers) {
        res.status(400).json({ error: "sessionId and answers are required" });
        return;
    }
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    try {
        // 根据 providerId 获取对应的 adapter
        const providerId = entry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        if (adapter.sendQuestionAnswer) {
            await adapter.sendQuestionAnswer(sessionId, answers);
        }
        res.json({ ok: true });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
/**
 * 确认权限请求
 * POST /runtime/sdk/confirm
 */
terminalRouter.post("/sdk/confirm", validateToken, async (req, res) => {
    const { sessionId, accept } = req.body || {};
    if (!sessionId) {
        res.status(400).json({ error: "sessionId is required" });
        return;
    }
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    try {
        // 根据 providerId 获取对应的 adapter
        const providerId = entry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        await adapter.sendConfirmation(sessionId, Boolean(accept));
        res.json({ ok: true });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
/**
 * 中止当前轮次
 * POST /runtime/sdk/abort
 */
terminalRouter.post("/sdk/abort", validateToken, async (req, res) => {
    const { sessionId } = req.body || {};
    if (!sessionId) {
        res.status(400).json({ error: "sessionId is required" });
        return;
    }
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    try {
        // 根据 providerId 获取对应的 adapter
        const providerId = entry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        await adapter.abortCurrentTurn(sessionId);
        res.json({ ok: true });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
/**
 * 停止 SDK 会话
 * POST /runtime/sdk/stop
 */
terminalRouter.post("/sdk/stop", validateToken, async (req, res) => {
    const { sessionId } = req.body || {};
    if (!sessionId) {
        res.status(400).json({ error: "sessionId is required" });
        return;
    }
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        res.json({ ok: true, status: "stopped" });
        return;
    }
    try {
        // 根据 providerId 获取对应的 adapter
        const providerId = entry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        // 如果有活跃 AI 请求，先立即中止（fire-and-forget），再清理资源
        adapter.abortCurrentTurn(sessionId).catch(() => { });
        await adapter.terminateSession(sessionId);
        sdkSessions.delete(sessionId);
        stopTerminalCommandProcess(sessionId);
        await removePersistedSession(sessionId);
        // ★ 从进程管理器中移除
        const processManager = getAgentProcessManager();
        await processManager.removeProcess(entry.agentId);
        await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
            runtimeAccessToken: entry.runtimeAccessToken,
            schedulerBaseUrl: entry.schedulerBaseUrl,
        });
        res.json({ ok: true, status: "stopped" });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
/**
 * 获取 SDK 会话状态
 * GET /runtime/sdk/status/:sessionId
 */
terminalRouter.get("/sdk/status/:sessionId", validateToken, (req, res) => {
    const sessionId = req.params.sessionId;
    if (!sessionId) {
        res.status(400).json({ error: "sessionId is required" });
        return;
    }
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    try {
        // 根据 providerId 获取对应的 adapter
        const providerId = entry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        const status = adapter.getSessionStatus(sessionId);
        const conversation = adapter.getConversation(sessionId);
        res.json({
            sessionId,
            status,
            agentId: entry.agentId,
            workspacePath: entry.workspacePath,
            messageCount: conversation.length,
        });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
/**
 * 执行当前 Agent 工作目录下的终端命令。
 *
 * 主流程：校验 session/command -> 读取会话 workspacePath -> 在该 cwd 下启动 shell 命令 -> 通过终端输出回调流式返回。
 * POST /runtime/command
 */
terminalRouter.post("/command", validateToken, async (req, res) => {
    const { sessionId, command } = req.body || {};
    if (!sessionId || command === undefined || command === null) {
        res.status(400).json({ error: "sessionId and command are required" });
        return;
    }
    try {
        const result = await executeTerminalCommand(String(sessionId), command);
        res.json({ ok: true, mode: "terminal", ...result });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
// ============ 兼容终端 UI 的 SDK 路由 ============
/**
 * 发送消息到 SDK 会话。
 *
 * 主流程：校验 session/input -> 定位 SDK 会话 -> 通过 provider adapter 发送用户消息。
 * POST /runtime/input
 */
terminalRouter.post("/input", validateToken, async (req, res) => {
    const { sessionId, input, mode } = req.body || {};
    if (!sessionId || input === undefined || input === null) {
        res.status(400).json({ error: "sessionId and input are required" });
        return;
    }
    const entry = sdkSessions.get(sessionId);
    if (!entry) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    try {
        const providerId = entry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        if (mode === "shortcut") {
            if (!adapter.sendShortcutInput) {
                res.status(400).json({
                    error: "shortcut input is not supported by this SDK provider",
                });
                return;
            }
            await adapter.sendShortcutInput(sessionId, String(input));
            res.json({ ok: true, mode: "shortcut" });
            return;
        }
        await adapter.sendMessage(sessionId, await resolveSdkInputMessage(input));
        res.json({ ok: true, mode: "sdk" });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ error: errorMessage });
    }
});
/**
 * 热更新运行中 SDK 会话的自动命令配置。
 *
 * 主流程：按 agentId 定位 SDK 会话 -> 更新 adapter 内存配置 -> 同步持久化会话字段。
 * POST /runtime/update-auto-commands
 */
terminalRouter.post("/update-auto-commands", validateToken, async (req, res) => {
    const { agentId, idleInputAutoCommand = "", nonInputAutoCommand = "", } = req.body || {};
    const normalizedAgentId = String(agentId || "").trim();
    if (!normalizedAgentId) {
        res.status(400).json({ error: "agentId is required" });
        return;
    }
    const entry = [...sdkSessions.values()].find((session) => session.agentId === normalizedAgentId);
    const commands = {
        idleInputCommand: String(idleInputAutoCommand || ""),
        nonInputCommand: String(nonInputAutoCommand || ""),
    };
    try {
        let sessionUpdated = false;
        if (entry) {
            const adapter = adapterRegistry.get(entry.providerId || "claude-code");
            adapter.setIdleCommands?.(entry.sessionId, commands);
            sessionUpdated = true;
        }
        const persistedUpdated = await updatePersistedSessionAutoCommands(normalizedAgentId, {
            idleInputAutoCommand: commands.idleInputCommand,
            nonInputAutoCommand: commands.nonInputCommand,
        });
        res.json({ ok: true, mode: "sdk", sessionUpdated, persistedUpdated });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({ ok: false, mode: "sdk", error: errorMessage });
    }
});
/**
 * 兼容终端尺寸调整 API。
 *
 * SDK 会话没有终端尺寸概念；保留该端点用于前端 xterm fit 回调，返回 no-op。
 * POST /runtime/resize
 */
terminalRouter.post("/resize", validateToken, (req, res) => {
    const { sessionId, cols, rows } = req.body || {};
    if (!sessionId || !cols || !rows) {
        res.status(400).json({ error: "sessionId, cols, rows are required" });
        return;
    }
    if (!sdkSessions.has(sessionId)) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    res.json({
        ok: true,
        mode: "sdk",
        noop: true,
        cols: Number(cols),
        rows: Number(rows),
    });
});
terminalRouter.post("/reset-context", validateToken, async (req, res) => {
    const { sessionId, reason } = req.body || {};
    if (!sessionId) {
        res.status(400).json({ error: "sessionId is required" });
        return;
    }
    const sdkEntry = sdkSessions.get(sessionId);
    if (!sdkEntry) {
        res.status(404).json({ error: "SDK session not found" });
        return;
    }
    const providerId = sdkEntry.providerId || "claude-code";
    const adapter = adapterRegistry.get(providerId);
    const resetContext = adapter.resetContext?.bind(adapter);
    const support = resolveRuntimeContextResetSupport(providerId, Boolean(resetContext));
    if (!support.supported) {
        res.status(support.statusCode).json({ error: support.error });
        return;
    }
    if (!resetContext) {
        res
            .status(500)
            .json({ error: "ACode adapter does not expose resetContext" });
        return;
    }
    try {
        await resetContext(String(sessionId));
        const status = resolveSdkSessionDisplayStatus(adapter.getSessionStatus(String(sessionId)), sdkEntry.currentRuntimeStatus, sdkEntry.currentRuntimeActionType);
        res.json({
            ok: true,
            contextReset: true,
            sessionId,
            status,
            runtimeStatus: status,
            agentId: sdkEntry.agentId,
            mode: "sdk",
            providerId,
            reason: typeof reason === "string" && reason.trim()
                ? reason.trim()
                : undefined,
        });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        res.status(500).json({
            ok: false,
            contextReset: false,
            mode: "sdk",
            error: errorMessage,
        });
    }
});
/**
 * 停止 SDK 会话。
 * POST /runtime/stop
 */
terminalRouter.post("/stop", validateToken, async (req, res) => {
    const { sessionId } = req.body || {};
    const stopOptions = parseRuntimeStopOptions(req.body);
    if (!sessionId) {
        res.status(400).json({ error: "sessionId is required" });
        return;
    }
    const sdkEntry = sdkSessions.get(sessionId);
    if (!sdkEntry) {
        await removePersistedSession(sessionId);
        res.json({
            ok: true,
            status: "stopped",
            mode: "none",
            statusNotified: false,
            reason: stopOptions.reason,
        });
        return;
    }
    const agentId = sdkEntry.agentId;
    try {
        console.log(`[Runtime] Stopping SDK session ${sessionId}; suppressStatus=${!stopOptions.statusNotified}, reason=${stopOptions.reason || "unspecified"}`);
        const providerId = sdkEntry.providerId || "claude-code";
        const adapter = adapterRegistry.get(providerId);
        // 如果有活跃 AI 请求，先立即中止（fire-and-forget），再清理资源
        adapter.abortCurrentTurn(sessionId).catch(() => { });
        await adapter.terminateSession(sessionId);
        sdkSessions.delete(sessionId);
        stopTerminalCommandProcess(sessionId);
        await removePersistedSession(sessionId);
        const processManager = getAgentProcessManager();
        await processManager.removeProcess(agentId);
        if (stopOptions.statusNotified) {
            await sendStatus(agentId, null, "stopped", undefined, undefined, {
                runtimeAccessToken: sdkEntry.runtimeAccessToken,
                schedulerBaseUrl: sdkEntry.schedulerBaseUrl,
            });
        }
        else {
            console.log(`[Runtime] Stopped status notification suppressed for SDK session ${sessionId}; reason=${stopOptions.reason || "unspecified"}`);
        }
        res.json({
            ok: true,
            status: "stopped",
            mode: "sdk",
            statusNotified: stopOptions.statusNotified,
            reason: stopOptions.reason,
        });
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        console.error(`[Runtime] Failed to terminate SDK session ${sessionId}:`, errorMessage);
        res.status(500).json({
            ok: false,
            status: "stop_failed",
            mode: "sdk",
            error: errorMessage,
        });
    }
});
/**
 * 停止所有 SDK 会话
 * POST /runtime/stop-all
 * 用于 aws-mcp-server 重启时清理所有 SDK Agent 会话
 */
terminalRouter.post("/stop-all", validateToken, async (_req, res) => {
    const stoppedSdk = [];
    const errors = [];
    // 1. 停止所有 SDK 会话
    for (const [sessionId, entry] of sdkSessions.entries()) {
        try {
            // 根据 providerId 获取对应的 adapter
            const providerId = entry.providerId || "claude-code";
            const adapter = adapterRegistry.get(providerId);
            // 如果有活跃 AI 请求，先立即中止（fire-and-forget），再清理资源
            adapter.abortCurrentTurn(sessionId).catch(() => { });
            await adapter.terminateSession(sessionId);
            sdkSessions.delete(sessionId);
            stopTerminalCommandProcess(sessionId);
            stoppedSdk.push(sessionId);
            await sendStatus(entry.agentId, null, "stopped", undefined, undefined, {
                runtimeAccessToken: entry.runtimeAccessToken,
                schedulerBaseUrl: entry.schedulerBaseUrl,
            });
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            errors.push(`SDK ${sessionId}: ${errorMessage}`);
        }
    }
    // 2. 清理持久化状态，避免 stop-all 后还能被恢复
    // 直接清空整个持久化文件，确保不会有残留
    try {
        await savePersistedSessions([]);
        console.log("[Runtime] 已清空所有持久化会话");
    }
    catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        errors.push(`Persistence: ${errorMessage}`);
    }
    res.json({
        ok: true,
        stopped: {
            sdk: stoppedSdk.length,
        },
        errors: errors.length > 0 ? errors : undefined,
    });
});
