/** * Evolution API Routes * 进化逻辑从 @mas/evolution 共享包导入。 */ const express = require('express'); const router = express.Router(); const { evolutionQueries, forageSkills, RunController, crossoverGeneration, selectGeneration, evaluateRunFitness, } = require('@mas/evolution'); // ── 通用 async handler ── const h = (fn) => async (req, res) => { try { await fn(req, res); } catch (e) { console.error(`[Evolution] ${req.method} ${req.path}:`, e.message); res.status(500).json({ error: e.message }); } }; // ── AgentManager 工厂 ── function _createAgentManager() { const AgentManager = require('../src/core/agent-manager'); const path = require('path'); const fs = require('fs'); let backendsConfig = {}; try { backendsConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '../config/backends.json'), 'utf-8')); } catch {} const clients = {}; for (const [name, launcher] of Object.entries(backendsConfig.launchers || {})) { clients[name] = { name, command: launcher.command, acpArgs: launcher.args || [], acpProtocol: name.includes('claude') ? 'claude-stream' : 'acp', }; } return new AgentManager({ agents: { evaluator: { name: 'evaluator', type: 'executor', client: 'kimi', deploy: 'local', mode: 'cli', prompt: null, description: '进化引擎 LLM 评估器' } }, clients, ...backendsConfig, }, process.cwd()); } function _makeController(opts = {}) { return new RunController({ agentManager: _createAgentManager(), ...opts }); } // ── Queries ── router.get('/overview', h(async (req, res) => res.json(await evolutionQueries.getOverview()))); router.get('/runs', h(async (req, res) => { const { page, pageSize, status, skillName } = req.query; res.json(await evolutionQueries.getRuns({ page: parseInt(page) || 1, pageSize: parseInt(pageSize) || 20, status, skillName })); })); router.get('/runs/:id', h(async (req, res) => { const data = await evolutionQueries.getRunStats(req.params.id); data ? res.json(data) : res.status(404).json({ error: 'Run not found' }); })); router.get('/runs/:runId/genomes', h(async (req, res) => { const { generation, aliveOnly } = req.query; res.json(await evolutionQueries.getGenomesByRun(req.params.runId, { generation: generation !== undefined ? parseInt(generation) : undefined, aliveOnly: aliveOnly === 'true', })); })); router.get('/genomes/:id', h(async (req, res) => { const genome = await evolutionQueries.getGenome(req.params.id); genome ? res.json(genome) : res.status(404).json({ error: 'Genome not found' }); })); router.get('/runs/:runId/evaluations', h(async (req, res) => { const { generation } = req.query; res.json(await evolutionQueries.getEvaluationsByRun(req.params.runId, { generation: generation !== undefined ? parseInt(generation) : undefined, })); })); router.get('/benchmarks', h(async (req, res) => res.json(await evolutionQueries.getBenchmarks(req.query.skillName)))); router.post('/benchmarks', h(async (req, res) => res.json(await evolutionQueries.upsertBenchmark(req.body)))); router.get('/runs/:runId/learnings', h(async (req, res) => { res.json(await evolutionQueries.getLearningEvents(req.params.runId, { dimension: req.query.dimension, eventType: req.query.eventType })); })); // ── Actions ── router.post('/actions/forage', h(async (req, res) => { const results = await forageSkills(req.body.skillsDir || undefined); res.json({ success: true, count: results.length, genomes: results }); })); router.post('/actions/run-phase1', h(async (req, res) => { const { skillName, skillsDir } = req.body; if (!skillName) return res.status(400).json({ error: 'skillName is required' }); res.json(await _makeController({ skillsDir }).runPhase1(skillName)); })); router.post('/actions/run-phase2', h(async (req, res) => { const { runId, generations } = req.body; if (!runId) return res.status(400).json({ error: 'runId is required' }); res.json(await _makeController().runPhase2(runId, generations || 5)); })); router.post('/actions/crossover', h(async (req, res) => { const { runId, generation } = req.body; if (!runId || generation === undefined) return res.status(400).json({ error: 'runId and generation are required' }); const children = await crossoverGeneration(runId, generation); res.json({ success: true, count: children.length, children: children.map(c => ({ id: c.id, skill_name: c.skill_name })) }); })); router.post('/actions/select', h(async (req, res) => { const { runId, eliteCount, survivalRate } = req.body; if (!runId) return res.status(400).json({ error: 'runId is required' }); res.json({ success: true, ...await selectGeneration(runId, { eliteCount, survivalRate }) }); })); router.post('/actions/deploy', h(async (req, res) => { const { runId, skillsDir } = req.body; if (!runId) return res.status(400).json({ error: 'runId is required' }); const controller = _makeController({ skillsDir: skillsDir || require('path').join(require('os').homedir(), '.gemini/antigravity/skills'), }); res.json(await controller.deploy(runId)); })); router.post('/actions/evaluate-fitness', h(async (req, res) => { const { runId } = req.body; if (!runId) return res.status(400).json({ error: 'runId is required' }); const results = await evaluateRunFitness(runId); const best = results.reduce((max, r) => r.overall > max.overall ? r : max, results[0]); res.json({ success: true, evaluated: results.length, bestFitness: best?.overall, results: results.map(r => ({ genomeId: r.genomeId, overall: r.overall, ...r.dimensionScores })), }); })); module.exports = router;