/** * dream —— 经验汇流的 REST 入口([29-DREAM](../../docs/done/system-design/29-dream-design.md))。 * * 只做一件事:把 `dream-service.js` 已有的能力(`dreamOnce` / `dreamSweep` / * `auditDistilled` / `definitionWritability`)从「只能靠脚本或定时器触发」 * 变成「REST 可查可触发」。不新增业务逻辑。 */ const express = require('express'); const router = express.Router(); const { requireAdmin } = require('../middleware/jwt-auth'); const dream = require('../services/dream-service'); // 38 号文档 Phase 1:Dream 是系统级的跨公司经验汇流视图,只有 admin 能看, // 不是"登录即可看"(普通用户不该看到别的公司提炼出的经验)。 router.use(requireAdmin); // dreamSweep 串行跑几十个本体、每个可能起两次 LLM 调用,不能塞进一次 HTTP 请求里等。 // 用最小的内存态跟踪「在跑没在跑」+「上一轮结果」,够控制台轮询用。 let sweepState = { sweeping: false, startedAt: null, finishedAt: null, results: null, error: null }; // GET /api/dream/agents — 总览:每个可 dream 的本体,跑没跑过、上次沉淀了几条 router.get('/agents', (req, res) => { try { const list = dream.listDreamableAgents().map((agentId) => { const state = dream.readState(agentId); return { agentId, companies: dream.companiesOf(agentId).length, lastRunAt: state.lastRunAt || null, lastAccepted: state.lastAccepted || 0, lastRejected: state.lastRejected || 0, }; }); res.json({ agents: list }); } catch (err) { res.status(500).json({ error: err.message }); } }); // GET /api/dream/agents/:agentId — 详情:状态 + 定义文件能不能写 + 已沉淀经验的可回查列表 router.get('/agents/:agentId', (req, res) => { try { const { agentId } = req.params; res.json({ agentId, companies: dream.companiesOf(agentId), state: dream.readState(agentId), definitions: dream.definitionWritability(agentId), experience: dream.auditDistilled(agentId), // 每条结论并排列出引用的原料原文,回查成本降到几秒 }); } catch (err) { res.status(500).json({ error: err.message }); } }); // POST /api/dream/agents/:agentId/run — 手动跑一次(真会起 LLM 调用,dryRun 只想看会不会有新原料时用) router.post('/agents/:agentId/run', async (req, res) => { try { const dryRun = req.body && req.body.dryRun === true; const result = await dream.dreamOnce(req.params.agentId, { dryRun }); res.json(result); } catch (err) { res.status(500).json({ error: err.message }); } }); // POST /api/dream/sweep — 手动触发全量扫描(后台跑,别等;串行跑完可能是几分钟到几十分钟) router.post('/sweep', (req, res) => { if (sweepState.sweeping) return res.status(409).json({ error: '上一轮扫描还没跑完', ...sweepState }); sweepState = { sweeping: true, startedAt: new Date().toISOString(), finishedAt: null, results: null, error: null }; dream.dreamSweep() .then((results) => { sweepState = { ...sweepState, sweeping: false, finishedAt: new Date().toISOString(), results }; }) .catch((err) => { sweepState = { ...sweepState, sweeping: false, finishedAt: new Date().toISOString(), error: err.message }; }); res.status(202).json({ started: true, startedAt: sweepState.startedAt }); }); // GET /api/dream/sweep/status — 轮询上面那个后台任务 router.get('/sweep/status', (req, res) => res.json(sweepState)); module.exports = router;