"use strict";
// =============================================================================
// Pure Agent - Gateway Runner
// Ported from Hermes gateway/run.py (GatewayRunner)
// =============================================================================
Object.defineProperty(exports, "__esModule", { value: true });
exports.GatewayRunner = void 0;
exports.startGateway = startGateway;
const events_1 = require("events");
const config_1 = require("./config");
const platform_registry_1 = require("./platform-registry");
const session_1 = require("./session");
const delivery_1 = require("./delivery");
// Import platform adapters to trigger auto-registration
require("./platforms/webhook");
require("./platforms/telegram");
require("./platforms/feishu");
require("./platforms/dingtalk");
require("./platforms/wecom");
require("./platforms/qqbot");
const pure_agent_1 = require("../pure-agent");
const logger_1 = require("../logger");
const plugin_system_1 = require("../plugin-system");
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const AGENT_CACHE_MAX_SIZE = 32;
const AGENT_CACHE_IDLE_TTL_MS = 3600_000; // 1 hour
const DEFAULT_CONNECT_TIMEOUT = 30_000;
// ---------------------------------------------------------------------------
// GatewayRunner
// ---------------------------------------------------------------------------
class GatewayRunner extends events_1.EventEmitter {
    _config;
    _adapters = new Map();
    _sessionStore;
    _deliveryRouter;
    _agentCache = new Map();
    _running = false;
    _agentConfig;
    constructor(config, agentConfig) {
        super();
        this._config = config;
        this._agentConfig = agentConfig;
        this._sessionStore = new session_1.SessionStore(config);
        this._deliveryRouter = new delivery_1.DeliveryRouter(config, this._adapters);
    }
    get running() { return this._running; }
    get adapters() { return this._adapters; }
    get sessionStore() { return this._sessionStore; }
    // -------------------------------------------------------------------------
    // Lifecycle
    // -------------------------------------------------------------------------
    async start() {
        if (this._running)
            return true;
        (0, logger_1.getLogger)().info('Gateway starting...');
        this._running = true;
        // Discover and load plugins
        try {
            const pluginSystem = (0, plugin_system_1.getPluginSystem)();
            pluginSystem.discover();
            const loaded = await pluginSystem.loadAll();
            if (loaded.length > 0) {
                (0, logger_1.getLogger)().info(`Gateway: ${loaded.length} plugin(s) loaded`);
            }
        }
        catch (err) {
            (0, logger_1.getLogger)().warn(`Gateway: plugin loading error: ${err.message}`);
        }
        // Connect all enabled platforms
        const platforms = (0, config_1.getConnectedPlatforms)(this._config);
        let connected = 0;
        for (const platform of platforms) {
            const cfg = this._config.platforms[platform];
            if (!cfg)
                continue;
            const adapter = platform_registry_1.platformRegistry.createAdapter(platform, cfg);
            if (!adapter)
                continue;
            // Set message handler
            adapter.setMessageHandler?.((event) => this._handleMessage(event));
            try {
                const ok = await Promise.race([
                    adapter.connect(),
                    new Promise((_, reject) => setTimeout(() => reject(new Error('Connect timeout')), DEFAULT_CONNECT_TIMEOUT)),
                ]);
                if (ok) {
                    this._adapters.set(platform, adapter);
                    connected++;
                    (0, logger_1.getLogger)().info(`Gateway: ${platform} connected`);
                }
            }
            catch (err) {
                (0, logger_1.getLogger)().error(`Gateway: ${platform} failed to connect: ${err.message}`);
            }
        }
        if (connected === 0) {
            (0, logger_1.getLogger)().warn('Gateway: no platforms connected');
        }
        else {
            (0, logger_1.getLogger)().info(`Gateway: ${connected} platform(s) connected`);
        }
        // Start agent cache sweeper
        this._startCacheSweeper();
        return connected > 0;
    }
    async stop() {
        if (!this._running)
            return;
        this._running = false;
        (0, logger_1.getLogger)().info('Gateway stopping...');
        // Disconnect all adapters
        for (const [platform, adapter] of this._adapters) {
            try {
                await adapter.disconnect();
                (0, logger_1.getLogger)().info(`Gateway: ${platform} disconnected`);
            }
            catch (err) {
                (0, logger_1.getLogger)().error(`Gateway: ${platform} disconnect error: ${err.message}`);
            }
        }
        this._adapters.clear();
        // Close all cached agents
        for (const [, cached] of this._agentCache) {
            cached.agent.close();
        }
        this._agentCache.clear();
        (0, logger_1.getLogger)().info('Gateway stopped');
    }
    // -------------------------------------------------------------------------
    // Slash command handlers
    // ---------------------------------------------------------------------------
    _slashCommands = {
        '/help': async () => `**PureAgent Gateway Commands:**\n\n` +
            `**/help** - Show this help\n` +
            `**/new** - Start a new conversation\n` +
            `**/model <name>** - Switch model\n` +
            `**/status** - Show gateway status\n` +
            `**/stop** - Stop current agent task\n` +
            `**/reset** - Reset current session`,
        '/new': async (event) => {
            const session = this._sessionStore.getOrCreateSession({
                platform: event.platform, chatId: event.chatId, chatType: event.chatType, userId: event.userId,
            }, true); // forceNew
            // Clear agent cache for this session
            const key = (0, session_1.buildSessionKey)({ platform: event.platform, chatId: event.chatId, chatType: event.chatType, userId: event.userId }, this._config.groupSessionsPerUser, this._config.threadSessionsPerUser);
            const cached = this._agentCache.get(key);
            if (cached) {
                cached.agent.close();
                this._agentCache.delete(key);
            }
            return 'New conversation started.';
        },
        '/model': async (_event, args) => {
            if (!args)
                return `Current model: ${this._agentConfig.model || 'default'}\nUsage: /model <model-name>`;
            this._agentConfig.model = args.trim();
            return `Model switched to: ${args.trim()}`;
        },
        '/status': async () => {
            const sessions = this._sessionStore.listSessions();
            const activeAgents = this._agentCache.size;
            const platforms = Array.from(this._adapters.keys());
            return [
                '**Gateway Status:**',
                `- Running: ${this._running}`,
                `- Platforms: ${platforms.join(', ') || 'none'}`,
                `- Active agents: ${activeAgents}`,
                `- Sessions: ${sessions.length}`,
                `- Model: ${this._agentConfig.model}`,
            ].join('\n');
        },
        '/stop': async (event) => {
            const key = (0, session_1.buildSessionKey)({ platform: event.platform, chatId: event.chatId, chatType: event.chatType, userId: event.userId }, this._config.groupSessionsPerUser, this._config.threadSessionsPerUser);
            const cached = this._agentCache.get(key);
            if (cached) {
                cached.agent.interrupt();
                return 'Agent interrupted.';
            }
            return 'No active agent to stop.';
        },
        '/reset': async (event) => {
            const key = (0, session_1.buildSessionKey)({ platform: event.platform, chatId: event.chatId, chatType: event.chatType, userId: event.userId }, this._config.groupSessionsPerUser, this._config.threadSessionsPerUser);
            this._sessionStore.resetSession(key);
            const cached = this._agentCache.get(key);
            if (cached) {
                cached.agent.close();
                this._agentCache.delete(key);
            }
            return 'Session reset.';
        },
    };
    // -------------------------------------------------------------------------
    // Message handling
    // -------------------------------------------------------------------------
    async _handleMessage(event) {
        const logger = (0, logger_1.getLogger)();
        const text = event.text.trim();
        // Check for slash commands
        if (text.startsWith('/')) {
            const spaceIdx = text.indexOf(' ');
            const cmd = spaceIdx > 0 ? text.slice(0, spaceIdx) : text;
            const args = spaceIdx > 0 ? text.slice(spaceIdx + 1).trim() : '';
            const handler = this._slashCommands[cmd.toLowerCase()];
            if (handler) {
                try {
                    return await handler(event, args);
                }
                catch (err) {
                    return `Command error: ${err.message}`;
                }
            }
        }
        const sessionKey = (0, session_1.buildSessionKey)({ platform: event.platform, chatId: event.chatId, chatType: event.chatType, userId: event.userId }, this._config.groupSessionsPerUser, this._config.threadSessionsPerUser);
        logger.info(`Gateway message: ${event.platform}:${event.chatId} "${text.slice(0, 80)}"`);
        // Get or create session
        const session = this._sessionStore.getOrCreateSession({
            platform: event.platform,
            chatId: event.chatId,
            chatType: event.chatType,
            userId: event.userId,
            userName: event.userName,
        });
        // Get or create agent for this session
        const agent = this._getOrCreateAgent(sessionKey, session.sessionId);
        // Show typing indicator
        const adapter = this._adapters.get(event.platform);
        try {
            // Plugin hook: on_message_received
            await (0, plugin_system_1.invokeHook)('on_message_received', {
                platform: event.platform,
                chatId: event.chatId,
                userId: event.userId,
                text: event.text,
                sessionKey,
            });
            // Streaming mode: use edit_message for incremental updates
            const useStreaming = this._config.streaming.enabled && adapter?.editMessage;
            let sentMessageId;
            let response = '';
            let lastEditTime = 0;
            const editInterval = this._config.streaming.editInterval * 1000;
            for await (const sseEvent of agent.runConversation(text)) {
                if (sseEvent.event === 'content' && sseEvent.data.delta) {
                    response += sseEvent.data.delta;
                    // Streaming: send initial message then edit
                    if (useStreaming) {
                        const now = Date.now();
                        if (!sentMessageId) {
                            // Send initial message
                            const result = await adapter.send(event.chatId, response + this._config.streaming.cursor);
                            if (result.success)
                                sentMessageId = result.messageId;
                            lastEditTime = now;
                        }
                        else if (now - lastEditTime >= editInterval) {
                            // Edit with accumulated content
                            await adapter.editMessage(event.chatId, sentMessageId, response + this._config.streaming.cursor);
                            lastEditTime = now;
                        }
                    }
                }
                if (sseEvent.event === 'done' || sseEvent.event === 'error')
                    break;
            }
            // Finalize: send or edit the final response
            if (useStreaming && sentMessageId && adapter?.editMessage) {
                // Edit to final content (no cursor)
                await adapter.editMessage(event.chatId, sentMessageId, response);
            }
            else if (response) {
                // Non-streaming: send the complete response
                await adapter?.send(event.chatId, response, event.messageId);
            }
            // Update session
            this._sessionStore.updateSession(sessionKey, {});
            // Plugin hook: on_message_sent
            await (0, plugin_system_1.invokeHook)('on_message_sent', {
                platform: event.platform,
                chatId: event.chatId,
                response,
                sessionKey,
            });
            return null; // Already sent via adapter
        }
        catch (err) {
            logger.error(`Gateway agent error: ${err.message}`);
            return `Error: ${err.message}`;
        }
    }
    // -------------------------------------------------------------------------
    // Agent cache
    // -------------------------------------------------------------------------
    _getOrCreateAgent(sessionKey, sessionId) {
        const cached = this._agentCache.get(sessionKey);
        if (cached) {
            cached.lastUsed = Date.now();
            return cached.agent;
        }
        // Evict if cache full
        if (this._agentCache.size >= AGENT_CACHE_MAX_SIZE) {
            this._evictOldestAgent();
        }
        const agent = new pure_agent_1.PureAgent({
            ...this._agentConfig,
            sessionId,
        });
        this._agentCache.set(sessionKey, {
            agent,
            lastUsed: Date.now(),
            sessionKey,
        });
        return agent;
    }
    _evictOldestAgent() {
        let oldest = null;
        for (const [, cached] of this._agentCache) {
            if (!oldest || cached.lastUsed < oldest.lastUsed) {
                oldest = cached;
            }
        }
        if (oldest) {
            oldest.agent.close();
            this._agentCache.delete(oldest.sessionKey);
        }
    }
    _startCacheSweeper() {
        const sweep = () => {
            if (!this._running)
                return;
            const now = Date.now();
            for (const [key, cached] of this._agentCache) {
                if (now - cached.lastUsed > AGENT_CACHE_IDLE_TTL_MS) {
                    cached.agent.close();
                    this._agentCache.delete(key);
                }
            }
        };
        setInterval(sweep, 300_000); // Every 5 minutes
    }
    // -------------------------------------------------------------------------
    // Session management
    // -------------------------------------------------------------------------
    listSessions() {
        return this._sessionStore.listSessions();
    }
    resetSession(sessionKey) {
        const entry = this._sessionStore.resetSession(sessionKey);
        if (entry) {
            const cached = this._agentCache.get(sessionKey);
            if (cached) {
                cached.agent.close();
                this._agentCache.delete(sessionKey);
            }
        }
        return entry;
    }
}
exports.GatewayRunner = GatewayRunner;
// ---------------------------------------------------------------------------
// Convenience: start gateway
// ---------------------------------------------------------------------------
async function startGateway(agentConfig, config) {
    const gatewayConfig = config || (0, config_1.loadGatewayConfig)();
    const runner = new GatewayRunner(gatewayConfig, agentConfig);
    await runner.start();
    return runner;
}
//# sourceMappingURL=gateway-runner.js.map