index.js 5.29 KB
/**
 * ArenaSK Server — 主入口
 *
 * 启动: node index.js | node --watch index.js (开发)
 */
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const path = require('path');
const http = require('http');
const WebSocket = require('ws');
const config = require('./config');
const { ensureDataDirs } = require('./services/skill-dir-manager');
const { PluginManager } = require('./src/plugins/plugin-manager');
const { initClaudeChatWSServer } = require('./routes/claude-chat-ws');

const app = express();
const server = http.createServer(app);
const pluginManager = new PluginManager();

// ─── 中间件 ───
app.use(cors(config.cors));
app.use(cookieParser());
app.use(express.json());

// ─── API 路由 ───
app.use('/api/claude-chat', require('./routes/claude-chat'));
app.use('/api/config',      require('./routes/config'));
app.use('/api/evolution',   require('./routes/evolution'));
app.use('/api/innovator',   require('./routes/innovator'));
app.use('/api/master',      require('./routes/master-chat'));
app.use('/api/open',        require('./routes/open-api'));
app.use('/api/arena',       require('./routes/arena'));
app.use('/api/auth',        require('./routes/auth'));
app.use('/api/whatsapp',    require('./routes/whatsapp-auth'));
app.use('/api/users',       require('./routes/users'));
app.use('/api/agent',        require('./routes/agent-chat'));
app.use('/api/goals',        require('./routes/goals'));

// ─── 系统端点 ───
app.get('/api/health', (req, res) => res.json({
    status: 'ok', timestamp: new Date().toISOString(), plugins: pluginManager.summary(),
}));
app.get('/api/plugins', (req, res) => res.json(pluginManager.summary()));

// ─── 静态文件 (生产环境) ───
if (process.env.NODE_ENV === 'production') {
    app.use(express.static(path.join(__dirname, '../web/dist')));
    app.get('*', (req, res) => res.sendFile(path.join(__dirname, '../web/dist/index.html')));
}

app.use((err, req, res, next) => {
    console.error('Server error:', err);
    res.status(500).json({ error: err.message });
});

// ─── 启动 ───
async function start() {
    try {
        ensureDataDirs();

        const host = config.host || '0.0.0.0';
        server.listen(Number(config.port), host, () => {
            console.log(`✅ 服务启动: http://${host}:${config.port}`);
        });

        const wss = new WebSocket.Server({ server, path: '/ws/claude' });
        initClaudeChatWSServer(wss);

        // 插件系统(异步,不阻塞 HTTP)
        initPlugins().catch(err => console.error('插件启动失败:', err.message));

        // 恢复中断任务并起搏 Agent (延迟 5s,等 HTTP 服务完全就绪)
        setTimeout(() => {
            resumeIncompleteTasks().catch(err => console.error('[Startup] 恢复任务失败:', err.message));
            const heartbeatService = require('./services/heartbeat-service');
            heartbeatService.start();
        }, 5000);
    } catch (error) {
        console.error('Failed to start server:', error);
        process.exit(1);
    }
}

async function resumeIncompleteTasks() {
    const taskMgr = require('./services/skill-task-manager');
    const skillsDir = config.skillsCodeDir || path.resolve(__dirname, '../skills');
    const incomplete = taskMgr.findIncompleteTasks(skillsDir);

    if (incomplete.length === 0) return console.log('[Startup] ✅ 无中断任务');

    console.log(`[Startup] 🔄 恢复 ${incomplete.length} 个中断任务...`);
    const { _jobManager, _runSkillCreation, _runFeedbackIteration } = require('./routes/open-api');

    for (const item of incomplete) {
        try {
            const job = _jobManager.createJob({
                domain: item.task.domain || '', scope: item.task.scope || '',
                description: item.task.description || '', keywords: item.task.keywords || [],
            });
            job.skillName = item.skillName;

            if (item.task.pipelineType === 'feedback' && _runFeedbackIteration) {
                await _runFeedbackIteration(job, item.task.feedback || '', {
                    skillName: item.skillName, domain: item.task.domain || '',
                    scope: item.task.scope || '', keywords: item.task.keywords || [],
                });
            } else if (_runSkillCreation) {
                await _runSkillCreation(job, item.nextStep);
            }
        } catch (err) {
            console.error(`[Startup] 恢复 ${item.skillName} 失败:`, err.message);
        }
    }
}

async function initPlugins() {
    await pluginManager.init();
    if (!pluginManager.enabled) return console.log('ℹ️  插件: 未配置 channel,跳过');

    const { runOrchestration, runMasterSession } = require('./src/standalone-entry');
    await pluginManager.start({
        runOrchestration: ({ task }) => runOrchestration(task),
        runMasterSession: ({ message }) => runMasterSession(message),
    });
    console.log(`✅ 插件就绪: channels=[${pluginManager.channels.map(c => c.id)}], tools=${pluginManager.tools.length}`);
}

// ─── 优雅关闭 ───
async function shutdown() {
    console.log('\n正在关闭...');
    await pluginManager.shutdown();
    process.exit(0);
}
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

start();
module.exports = app;