/**
 * OpenCode SDK Adapter
 *
 * 主干流程：
 * 1. spawn `opencode serve --port <port>` 进程（使用 shell: true）
 * 2. 创建 SDK 客户端连接到该端口
 * 3. 通过 client.session.create() 创建 OpenCode 会话
 * 4. 订阅 SSE 事件流并映射到统一 ProviderEvent
 * 5. 通过 client.session.prompt() 发送用户消息
 *
 * 参考：spectrai-community/src/main/adapter/OpenCodeSdkAdapter.ts
 */
import { spawn } from 'node:child_process';
import { EventEmitter } from 'node:events';
import * as fs from 'node:fs';
import * as net from 'node:net';
import * as os from 'node:os';
import * as path from 'node:path';
import { v4 as uuidv4 } from 'uuid';
import { BRIDGE_PACKAGE_ROOT, importBridgeSdkPackage } from '../utils/sdk-package-loader.js';
import { IdlePollLoop } from './idle-poll-loop.js';
import { getToolActionInfo, redactTransientClipboardPastePaths, redactTransientClipboardPastePathsInValue, } from './types.js';
// ============ 可执行文件查找 ============
/**
 * 查找 opencode 可执行文件
 * 优先级：用户配置的绝对路径 > Windows Volta exe > npm 全局目录 > 回退到 'opencode'
 */
function findOpenCodeExecutable(configCommand) {
    if (configCommand && path.isAbsolute(configCommand) && fs.existsSync(configCommand)) {
        return configCommand;
    }
    const fallback = configCommand?.trim() || 'opencode';
    if (process.platform === 'win32') {
        const home = process.env.USERPROFILE || process.env.HOME || os.homedir();
        const localAppData = process.env.LOCALAPPDATA || '';
        // 1. Volta 安装的 opencode.exe（优先，不需要 shell）
        const voltaExeDirs = [
            path.join(localAppData, 'Volta', 'tools', 'shared', 'opencode-ai', 'node_modules', 'opencode-windows-x64', 'bin'),
            path.join(localAppData, 'Volta', 'tools', 'shared', 'opencode-ai', 'node_modules', 'opencode-windows-x64-baseline', 'bin'),
        ];
        for (const dir of voltaExeDirs) {
            const exePath = path.join(dir, 'opencode.exe');
            if (fs.existsSync(exePath)) {
                console.log(`[OpencodeSdkAdapter] Found opencode.exe: ${exePath}`);
                return exePath;
            }
        }
        // 2. npm 全局安装的 opencode.cmd
        const npmGlobalDirs = [
            path.join(home, 'AppData', 'Roaming', 'npm'),
            path.join(home, 'AppData', 'Local', 'Volta', 'bin'),
        ];
        for (const dir of npmGlobalDirs) {
            for (const name of ['opencode.cmd', 'opencode.exe', 'opencode']) {
                const candidate = path.join(dir, name);
                if (fs.existsSync(candidate)) {
                    console.log(`[OpencodeSdkAdapter] Found ${name}: ${candidate}`);
                    return candidate;
                }
            }
        }
    }
    return fallback;
}
// ============ 端口检测 ============
async function findAvailablePort(startPort = 14096) {
    for (let port = startPort; port < startPort + 100; port++) {
        const available = await new Promise((resolve) => {
            const server = net.createServer();
            server.once('error', () => resolve(false));
            server.once('listening', () => {
                server.close(() => resolve(true));
            });
            server.listen(port, '127.0.0.1');
        });
        if (available)
            return port;
    }
    throw new Error(`No available port found in range ${startPort}–${startPort + 99}`);
}
function hasErrorCode(error) {
    return error instanceof Error && 'code' in error && typeof error.code === 'string';
}
function isMissingPackageError(error, packageName) {
    return hasErrorCode(error) && error.code === 'ERR_MODULE_NOT_FOUND' && error.message.includes(packageName);
}
// ============ Adapter ============
export class OpencodeSdkAdapter extends EventEmitter {
    constructor() {
        super(...arguments);
        this.providerId = 'opencode';
        this.displayName = 'OpenCode';
        this.sessions = new Map();
    }
    async startSession(sessionId, config) {
        console.log(`[OpencodeSdkAdapter] ★★★ 启动 OpenCode SDK 会话 ★★★ sessionId="${sessionId}", workingDirectory="${config.workingDirectory}"`);
        const session = {
            adapterSession: {
                sessionId,
                status: 'starting',
                messages: [],
                createdAt: new Date().toISOString(),
                totalUsage: { inputTokens: 0, outputTokens: 0 },
            },
            config,
            sseActive: false,
            pendingPermissions: new Map(),
            emittedToolStarts: new Set(),
            workingDirectory: config.workingDirectory,
            userMessageIds: new Set(),
            currentAssistantText: '',
            seenTextPartIds: new Set(),
        };
        this.sessions.set(sessionId, session);
        this.emit('status-change', sessionId, 'starting');
        try {
            // 步骤 1：找空闲端口
            const port = await findAvailablePort();
            session.port = port;
            console.log(`[OpencodeSdkAdapter] ★★★ 使用端口 ★★★ port=${port}`);
            // 步骤 2：启动 opencode serve 进程
            const openCodeCommand = findOpenCodeExecutable(config.command);
            console.log(`[OpencodeSdkAdapter] ★★★ 启动命令 ★★★ command="${openCodeCommand}"`);
            // ★ 关键：Windows 上 .cmd 必须使用 shell: true
            const useShell = openCodeCommand.endsWith('.cmd') ||
                openCodeCommand.endsWith('.CMD') ||
                (process.platform === 'win32' && !path.isAbsolute(openCodeCommand));
            console.log(`[OpencodeSdkAdapter] shell=${useShell}`);
            const env = { ...process.env, ...config.envOverrides };
            const proc = spawn(openCodeCommand, ['serve', '--port', String(port)], {
                cwd: config.workingDirectory,
                stdio: ['ignore', 'pipe', 'pipe'],
                env,
                shell: useShell,
            });
            session.serverProcess = proc;
            session.startupOutput = [];
            proc.stdout?.on('data', (d) => {
                const text = d.toString();
                this.recordStartupOutput(session, text);
                console.debug(`[OpencodeSdkAdapter] serve stdout: ${text.slice(0, 200)}`);
            });
            proc.stderr?.on('data', (d) => {
                const text = d.toString();
                this.recordStartupOutput(session, text);
                console.debug(`[OpencodeSdkAdapter] serve stderr: ${text.slice(0, 200)}`);
            });
            proc.on('error', (err) => {
                console.error(`[OpencodeSdkAdapter] Server process error:`, err);
                if (session.adapterSession.status !== 'terminated') {
                    session.adapterSession.status = 'error';
                    this.emit('status-change', sessionId, 'error');
                    this.emitEvent({
                        type: 'error',
                        sessionId,
                        timestamp: new Date().toISOString(),
                        data: { text: `OpenCode server error: ${err.message}` },
                    });
                }
            });
            proc.on('exit', (code) => {
                console.log(`[OpencodeSdkAdapter] Server exited with code ${code}`);
                session.sseActive = false;
            });
            const exitDuringStartup = new Promise((resolve) => {
                proc.once('exit', (code, signal) => resolve({ code, signal }));
            });
            // 步骤 3：创建 SDK 客户端
            const sdk = await this.loadSdk();
            const client = sdk.createOpencodeClient({
                baseUrl: `http://127.0.0.1:${port}`,
            });
            session.client = client;
            // 步骤 4：等待服务器就绪
            await Promise.race([
                this.waitForServer(client, config.workingDirectory, 10_000, port),
                exitDuringStartup.then((exitInfo) => {
                    throw new Error(this.formatStartupExitMessage(exitInfo, session.startupOutput));
                }),
            ]);
            console.log(`[OpencodeSdkAdapter] ★★★ OpenCode server ready ★★★ port=${port}`);
            // 步骤 5：创建 OpenCode 会话
            const createResult = await client.session.create({
                body: { title: `aws-bridge-${sessionId.slice(0, 8)}` },
                query: { directory: config.workingDirectory },
            });
            const ocSession = createResult.data;
            if (!ocSession?.id) {
                throw new Error('OpenCode session.create() did not return an ID');
            }
            session.opencodeSessionId = ocSession.id;
            session.adapterSession.providerSessionId = ocSession.id;
            console.log(`[OpencodeSdkAdapter] ★★★ OpenCode 会话创建成功 ★★★ sessionId="${ocSession.id}"`);
            // 步骤 6：后台启动 SSE 事件订阅循环
            this.startSseLoop(sessionId, session);
            session.adapterSession.status = 'waiting_input';
            this.emit('status-change', sessionId, 'waiting_input', { usage: session.adapterSession.totalUsage });
            // 步骤 7：发送初始 Prompt
            if (config.initialPrompt) {
                this.dispatchInitialPrompt(sessionId, config.initialPrompt);
            }
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            console.error(`[OpencodeSdkAdapter] startSession failed:`, err);
            session.adapterSession.status = 'error';
            this.emit('status-change', sessionId, 'error');
            this.emitEvent({
                type: 'error',
                sessionId,
                timestamp: new Date().toISOString(),
                data: { text: `OpenCode start failed: ${msg}` },
            });
            throw err;
        }
    }
    async sendMessage(sessionId, message, options) {
        const session = this.sessions.get(sessionId);
        if (!session)
            throw new Error(`Session ${sessionId} not found`);
        if (!session.client || !session.opencodeSessionId) {
            throw new Error(`Session ${sessionId} not ready`);
        }
        const userMsg = {
            id: uuidv4(),
            sessionId,
            role: 'user',
            content: message,
            timestamp: new Date().toISOString(),
        };
        session.adapterSession.messages.push(userMsg);
        this.emit('conversation-message', sessionId, userMsg);
        if (!options?.keepWaitingStatus) {
            session.adapterSession.status = 'thinking';
            this.emit('status-change', sessionId, 'thinking');
            this.stopIdleDetection(sessionId);
        }
        try {
            await session.client.session.prompt({
                path: { id: session.opencodeSessionId },
                body: {
                    parts: [{ type: 'text', text: message }],
                },
                query: { directory: session.workingDirectory },
            });
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            this.emitEvent({
                type: 'error',
                sessionId,
                timestamp: new Date().toISOString(),
                data: { text: msg },
            });
        }
    }
    async sendConfirmation(sessionId, accept) {
        const session = this.sessions.get(sessionId);
        if (!session?.client || !session.opencodeSessionId)
            return;
        const response = accept ? 'once' : 'reject';
        const ocId = session.opencodeSessionId;
        for (const [permissionId] of session.pendingPermissions) {
            try {
                await session.client.postSessionIdPermissionsPermissionId({
                    path: { id: ocId, permissionID: permissionId },
                    body: { response },
                    query: { directory: session.workingDirectory },
                });
            }
            catch (err) {
                console.warn(`[OpencodeSdkAdapter] Permission response failed:`, err);
            }
        }
        session.pendingPermissions.clear();
    }
    async sendQuestionAnswer(_sessionId, _answers) {
        // OpenCode 的 AskUserQuestion 通过 permission 机制处理
    }
    setIdleCommands(sessionId, commands) {
        const session = this.sessions.get(sessionId);
        if (session) {
            session.idleCommands = commands;
            if (session.adapterSession.status === 'waiting_input' && (commands.idleInputCommand || commands.nonInputCommand)) {
                this.startIdleDetection(sessionId);
            }
        }
    }
    async abortCurrentTurn(sessionId) {
        const session = this.sessions.get(sessionId);
        if (!session?.client || !session.opencodeSessionId)
            return;
        try {
            await session.client.session.abort({
                path: { id: session.opencodeSessionId },
                query: { directory: session.workingDirectory },
            });
        }
        catch (err) {
            console.warn(`[OpencodeSdkAdapter] Abort failed:`, err);
        }
    }
    async terminateSession(sessionId) {
        const session = this.sessions.get(sessionId);
        if (!session)
            return;
        this.stopIdleDetection(sessionId);
        session.sseActive = false;
        session.sseAbortController?.abort();
        if (session.serverProcess) {
            try {
                session.serverProcess.kill();
            }
            catch {
                // ignore
            }
        }
        session.adapterSession.status = 'terminated';
        this.emit('status-change', sessionId, 'terminated');
        this.emitEvent({
            type: 'session_complete',
            sessionId,
            timestamp: new Date().toISOString(),
            data: { exitCode: 0 },
        });
        this.sessions.delete(sessionId);
    }
    getConversation(sessionId) {
        return this.sessions.get(sessionId)?.adapterSession.messages ?? [];
    }
    hasSession(sessionId) {
        return this.sessions.has(sessionId);
    }
    getProviderSessionId(sessionId) {
        return this.sessions.get(sessionId)?.opencodeSessionId;
    }
    getSessionStatus(sessionId) {
        return this.sessions.get(sessionId)?.adapterSession.status;
    }
    getSessionPid(sessionId) {
        const session = this.sessions.get(sessionId);
        if (!session)
            return undefined;
        // OpenCode SDK 模式下 serverProcess 是 spawn 出来的子进程
        return session.serverProcess?.pid;
    }
    cleanup() {
        for (const [id] of this.sessions) {
            try {
                this.terminateSession(id);
            }
            catch {
                // ignore
            }
        }
        this.sessions.clear();
    }
    // ============ 私有方法 ============
    /**
     * 按需加载 OpenCode SDK：只有启动 OpenCode provider 时才解析依赖，
     * 避免 bridge 基础安装默认携带所有可选 runtime SDK。
     */
    async loadSdk() {
        if (!this.sdkModule) {
            try {
                this.sdkModule = await importBridgeSdkPackage('@opencode-ai/sdk');
            }
            catch (error) {
                if (isMissingPackageError(error, '@opencode-ai/sdk')) {
                    throw new Error([
                        'OpenCode SDK provider is not installed.',
                        '',
                        'aws-runtime-bridge keeps Codex/OpenCode provider SDKs optional to reduce default install size.',
                        'To use SDK mode with command "opencode", install the SDK into the aws-runtime-bridge package root:',
                        '',
                        `  npm install --prefix "${BRIDGE_PACKAGE_ROOT}" @opencode-ai/sdk@latest opencode-ai@latest`,
                        '',
                        'If aws-runtime-bridge is installed locally, run the same command with that local package path.',
                        '',
                        `Original error: ${error instanceof Error ? error.message : String(error)}`,
                    ].join('\n'));
                }
                throw error;
            }
        }
        return this.sdkModule;
    }
    async waitForServer(client, directory, timeoutMs, port) {
        const deadline = Date.now() + timeoutMs;
        while (Date.now() < deadline) {
            try {
                const result = await client.session.list({ query: { directory } });
                if (result.data !== undefined)
                    return;
            }
            catch {
                // 服务器尚未就绪
            }
            await new Promise((r) => setTimeout(r, 300));
        }
        throw new Error(`OpenCode server (port ${port}) did not start within ${timeoutMs}ms`);
    }
    /**
     * 记录 OpenCode serve 启动阶段输出，用于进程提前退出时返回可诊断错误。
     */
    recordStartupOutput(session, text) {
        const normalized = text.trim();
        if (!normalized)
            return;
        session.startupOutput = [...(session.startupOutput ?? []), normalized].slice(-8);
    }
    /**
     * 将 OpenCode serve 提前退出信息转成前端可直接展示的错误文本。
     */
    formatStartupExitMessage(exitInfo, startupOutput) {
        const exitReason = exitInfo.signal
            ? `signal ${exitInfo.signal}`
            : `code ${exitInfo.code ?? 'unknown'}`;
        const output = (startupOutput ?? []).join('\n').trim();
        if (!output) {
            return `OpenCode server exited during startup (${exitReason}). Please check whether opencode serve can run in this workspace.`;
        }
        return `OpenCode server exited during startup (${exitReason}): ${output}`;
    }
    /**
     * 异步投递启动提示，避免首轮 OpenCode 处理耗时导致 /runtime/start 被前端误判为启动失败。
     */
    dispatchInitialPrompt(sessionId, initialPrompt) {
        void this.sendMessage(sessionId, initialPrompt).catch((err) => {
            const msg = err instanceof Error ? err.message : String(err);
            this.emitEvent({
                type: 'error',
                sessionId,
                timestamp: new Date().toISOString(),
                data: { text: `OpenCode initial prompt failed: ${msg}` },
            });
        });
    }
    startSseLoop(sessionId, session) {
        session.sseActive = true;
        const ac = new AbortController();
        session.sseAbortController = ac;
        this.runSseLoop(sessionId, session, ac).catch((err) => {
            if (!ac.signal.aborted) {
                console.error(`[OpencodeSdkAdapter] SSE loop error:`, err);
            }
        });
    }
    async runSseLoop(sessionId, session, ac) {
        if (!session.client)
            return;
        try {
            const sseResult = await session.client.event.subscribe({
                query: { directory: session.workingDirectory },
                signal: ac.signal,
            });
            for await (const event of sseResult.stream) {
                if (!session.sseActive || ac.signal.aborted)
                    break;
                this.handleOpenCodeEvent(sessionId, session, event);
            }
        }
        catch (err) {
            if (ac.signal.aborted)
                return;
            console.error(`[OpencodeSdkAdapter] SSE stream ended:`, err);
        }
    }
    handleOpenCodeEvent(sessionId, session, event) {
        const ts = new Date().toISOString();
        const ocId = session.opencodeSessionId;
        switch (event.type) {
            case 'message.updated': {
                const msg = event.properties.info;
                if (msg.sessionID !== ocId)
                    break;
                if (msg.role === 'user') {
                    session.userMessageIds.add(msg.id);
                }
                else if (msg.role === 'assistant') {
                    const tokens = msg.tokens;
                    if (tokens) {
                        session.adapterSession.totalUsage.inputTokens = Math.max(session.adapterSession.totalUsage.inputTokens, tokens.input);
                        session.adapterSession.totalUsage.outputTokens = Math.max(session.adapterSession.totalUsage.outputTokens, tokens.output);
                    }
                }
                break;
            }
            case 'message.part.updated': {
                const { part, delta } = event.properties;
                if (part.sessionID !== ocId)
                    break;
                if (session.userMessageIds.has(part.messageID))
                    break;
                this.handlePartUpdate(sessionId, session, part, delta, ts);
                break;
            }
            case 'session.idle': {
                if (event.properties.sessionID !== ocId)
                    break;
                if (session.currentAssistantText) {
                    const assistantMsg = {
                        id: uuidv4(),
                        sessionId,
                        role: 'assistant',
                        content: session.currentAssistantText,
                        timestamp: ts,
                    };
                    session.adapterSession.messages.push(assistantMsg);
                    session.currentAssistantText = '';
                }
                session.seenTextPartIds.clear();
                this.emitEvent({
                    type: 'turn_complete',
                    sessionId,
                    timestamp: ts,
                    data: { usage: session.adapterSession.totalUsage },
                });
                session.adapterSession.status = 'waiting_input';
                this.emit('status-change', sessionId, 'waiting_input');
                this.startIdleDetection(sessionId);
                break;
            }
            case 'permission.updated': {
                const perm = event.properties;
                if (perm.sessionID !== ocId)
                    break;
                const permId = perm.id;
                session.pendingPermissions.set(permId, perm);
                if (session.config.autoAccept && session.client && ocId) {
                    session.client
                        .postSessionIdPermissionsPermissionId({
                        path: { id: ocId, permissionID: permId },
                        body: { response: 'once' },
                        query: { directory: session.workingDirectory },
                    })
                        .then(() => {
                        session.pendingPermissions.delete(permId);
                    })
                        .catch((err) => {
                        console.warn(`[OpencodeSdkAdapter] Auto-accept permission failed:`, err);
                    });
                }
                else {
                    this.emitEvent({
                        type: 'permission_request',
                        sessionId,
                        timestamp: ts,
                        data: {
                            permissionPrompt: perm.title,
                            toolName: perm.type,
                        },
                    });
                }
                break;
            }
            case 'session.error': {
                const { sessionID, error } = event.properties;
                if (sessionID && sessionID !== ocId)
                    break;
                let errMsg = 'OpenCode session error';
                if (error) {
                    const data = error.data;
                    if (typeof data?.message === 'string') {
                        errMsg = `${error.name}: ${data.message}`;
                    }
                    else {
                        errMsg = error.name;
                    }
                }
                this.emitEvent({
                    type: 'error',
                    sessionId,
                    timestamp: ts,
                    data: { text: errMsg },
                });
                break;
            }
        }
    }
    handlePartUpdate(sessionId, session, part, delta, ts) {
        switch (part.type) {
            case 'text': {
                let text;
                if (delta !== undefined) {
                    text = delta;
                    session.seenTextPartIds.add(part.id);
                }
                else if (!session.seenTextPartIds.has(part.id)) {
                    text = part.text;
                    if (text)
                        session.seenTextPartIds.add(part.id);
                }
                else {
                    break;
                }
                if (!text)
                    break;
                session.currentAssistantText += text;
                this.emitEvent({
                    type: 'text_delta',
                    sessionId,
                    timestamp: ts,
                    data: { text },
                });
                break;
            }
            case 'reasoning': {
                const text = delta ?? part.text;
                if (!text)
                    break;
                this.emitEvent({
                    type: 'thinking',
                    sessionId,
                    timestamp: ts,
                    data: { text },
                });
                break;
            }
            case 'tool': {
                const toolUseId = part.callID;
                const toolName = part.tool;
                const state = part.state;
                const toolInput = state.input;
                // 从 ToolPart.metadata 中提取 MCP server 信息（注意：metadata 在 part 上，不在 state 上）
                const metadata = part.metadata;
                // 调试日志：查看 metadata 内容
                if (metadata && Object.keys(metadata).length > 0) {
                    console.log(`[OpencodeSdkAdapter] Tool "${toolName}" metadata:`, JSON.stringify(redactTransientClipboardPastePathsInValue(metadata)));
                }
                const displayToolInputValue = redactTransientClipboardPastePathsInValue(toolInput);
                const displayToolInput = displayToolInputValue && typeof displayToolInputValue === 'object' && !Array.isArray(displayToolInputValue)
                    ? displayToolInputValue
                    : {};
                const emitToolStart = () => {
                    if (session.emittedToolStarts.has(toolUseId))
                        return;
                    session.emittedToolStarts.add(toolUseId);
                    const actionInfo = { ...getToolActionInfo(toolName, toolInput, metadata), actionId: toolUseId };
                    this.emitEvent({
                        type: 'tool_use_start',
                        sessionId,
                        timestamp: ts,
                        data: { toolName, toolInput: displayToolInput, toolUseId, ...actionInfo },
                    });
                    session.adapterSession.messages.push({
                        id: uuidv4(),
                        sessionId,
                        role: 'tool_use',
                        content: `${toolName}: ${JSON.stringify(displayToolInput).slice(0, 100)}`,
                        timestamp: ts,
                        toolName,
                        toolInput: displayToolInput,
                        toolUseId,
                    });
                };
                if (state.status === 'running') {
                    emitToolStart();
                    const actionInfo = { ...getToolActionInfo(toolName, toolInput, metadata), actionId: toolUseId };
                    this.emit('status-change', sessionId, 'tool_using', { ...actionInfo, usage: session.adapterSession.totalUsage });
                }
                else if (state.status === 'completed') {
                    emitToolStart();
                    const output = redactTransientClipboardPastePaths(state.output);
                    this.emitEvent({
                        type: 'tool_use_end',
                        sessionId,
                        timestamp: ts,
                        data: { toolName, toolResult: output, isError: false, toolUseId },
                    });
                    session.emittedToolStarts.delete(toolUseId);
                }
                else if (state.status === 'error') {
                    emitToolStart();
                    const errorStr = redactTransientClipboardPastePaths(state.error);
                    this.emitEvent({
                        type: 'tool_use_end',
                        sessionId,
                        timestamp: ts,
                        data: { toolName, toolResult: errorStr, isError: true, toolUseId },
                    });
                    session.emittedToolStarts.delete(toolUseId);
                }
                break;
            }
        }
    }
    emitEvent(event) {
        this.emit('event', event);
    }
    startIdleDetection(sessionId) {
        this.stopIdleDetection(sessionId);
        const session = this.sessions.get(sessionId);
        const idleInputCommand = session?.idleCommands?.idleInputCommand;
        if (!session || !idleInputCommand)
            return;
        if (this.isMessagePollingIdleCommand(idleInputCommand)) {
            if (!session.idlePollLoop) {
                session.idlePollLoop = new IdlePollLoop({
                    getStatus: () => this.sessions.get(sessionId)?.adapterSession.status,
                    getCommand: () => this.sessions.get(sessionId)?.idleCommands?.idleInputCommand,
                    isPollingCommand: (command) => this.isMessagePollingIdleCommand(command),
                    dispatchPollingPrompt: (command) => this.sendMessage(sessionId, this.toIdlePrompt(command), { keepWaitingStatus: true }),
                    dispatchRegularIdleCommand: (command) => this.sendMessage(sessionId, this.toIdlePrompt(command)),
                    pollDelayMs: 1500,
                });
            }
            session.idlePollLoop.start();
            return;
        }
        if (!session.idlePollLoop) {
            session.idlePollLoop = new IdlePollLoop({
                getStatus: () => this.sessions.get(sessionId)?.adapterSession.status,
                getCommand: () => this.sessions.get(sessionId)?.idleCommands?.idleInputCommand,
                isPollingCommand: (command) => this.isMessagePollingIdleCommand(command),
                dispatchPollingPrompt: (command) => this.sendMessage(sessionId, this.toIdlePrompt(command), { keepWaitingStatus: true }),
                dispatchRegularIdleCommand: (command) => this.sendMessage(sessionId, this.toIdlePrompt(command)),
                pollDelayMs: 1500,
            });
        }
        const timer = setInterval(() => {
            this.checkIdleState(sessionId);
        }, 500);
        session.idleTimer = timer;
    }
    stopIdleDetection(sessionId) {
        const session = this.sessions.get(sessionId);
        session?.idlePollLoop?.stop();
        if (session?.idleTimer) {
            clearInterval(session.idleTimer);
            session.idleTimer = undefined;
        }
    }
    checkIdleState(sessionId) {
        const session = this.sessions.get(sessionId);
        if (!session || session.adapterSession.status !== 'waiting_input')
            return;
        if (!session.idleCommands?.idleInputCommand)
            return;
        void this.sendMessage(sessionId, this.toIdlePrompt(session.idleCommands.idleInputCommand));
        this.stopIdleDetection(sessionId);
    }
    toIdlePrompt(command) {
        const trimmed = command.trim();
        if (this.isMessagePollingIdleCommand(trimmed)) {
            return '系统提醒：当前工作已完成或运行时已进入空闲状态。请立即调用 poll_message 工具阻塞等待新消息和流程线任务提醒；收到流程任务提醒后调用 my_task 查看已由后台静默抢占的任务详情并直接处理；完成任务后调用 submit_task_result 提交结果，然后继续 poll_message。';
        }
        return trimmed;
    }
    isMessagePollingIdleCommand(command) {
        const normalized = command.trim();
        if (!normalized)
            return false;
        const lower = normalized.toLowerCase();
        return lower.includes('poll_message') || lower.includes('get_message') || normalized.includes('取消息');
    }
}
