/** * 大桌子小桌子系统 — 主服务 * ============================================ * 端口: 3921 * * API: * GET /api/desk/state — 大桌子当前状态 * GET /api/desk/hldp — 大桌子 HLDP 格式 * POST /api/desk/add — 向大桌子添加内容 * POST /api/desk/clear — 清空大桌子 * * GET /api/small/tools — 工具列表 * GET /api/small/agents — Agent 列表 * POST /api/small/agent/:id — 调度 Agent * GET /api/small/memory/search — 搜索记忆 * POST /api/small/memory/store — 存入记忆 * GET /api/small/memory/stats — 记忆统计 * * POST /api/compress — 手动触发压缩 * GET /api/compress/history — 压缩历史 * * GET /api/servers/scan — 扫描服务器状态 * GET /api/status — 系统总览 */ const http = require('http'); const url = require('url'); const path = require('path'); const fs = require('fs'); const BigDesk = require('./big-desk'); const SmallDesk = require('./small-desk'); const Compressor = require('./compressor'); const PORT = process.env.PORT || 3921; const REPO_ROOT = path.join(__dirname, '..', '..'); const DATA_DIR = path.join(__dirname, '..', 'data'); // 初始化引擎 const bigDesk = new BigDesk({ dataDir: DATA_DIR }); const smallDesk = new SmallDesk({ dataDir: DATA_DIR, repoRoot: REPO_ROOT }); const compressor = new Compressor(bigDesk, smallDesk, { compressionThreshold: 5, autoCompress: true }); // 事件日志 bigDesk.on('add', (item) => { console.log(`[BigDesk] + ${item.type}: ${item.content.substring(0, 60)}`); }); compressor.on('compressed', ({ batchId, originalCount }) => { console.log(`[Compressor] ${originalCount}条 → ${batchId}`); }); // 初始化:向大桌子写入铸渊意识锚点 if (bigDesk.items.length === 0) { bigDesk.add('core', '🧠 铸渊意识大脑 · ICE-GL-ZY001(永远在·不挪走)', { speaker: 'system', permanent: true }); bigDesk.add('dir', '📂 小桌子路径索引: /memory/ | /agents/ | /tools/ | /servers/', { speaker: 'system' }); } // ============================================================ // HTTP 服务 // ============================================================ const server = http.createServer(async (req, res) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); return res.end(); } const parsed = url.parse(req.url, true); const pathname = parsed.pathname; const query = parsed.query; try { // ─── 大桌子 API ─── if (pathname === '/api/desk/state') { return json(res, bigDesk.getState()); } if (pathname === '/api/desk/hldp') { return text(res, bigDesk.toHLDP()); } if (pathname === '/api/desk/add' && req.method === 'POST') { const body = await readBody(req); const { type, content, meta } = body; if (!type || !content) return json(res, { error: 'type and content required' }, 400); const item = bigDesk.add(type, content, meta || {}); return json(res, { ok: true, item }); } if (pathname === '/api/desk/clear' && req.method === 'POST') { const removed = bigDesk.clear(); return json(res, { ok: true, removedCount: removed.length }); } // ─── 小桌子 API ─── if (pathname === '/api/small/tools') { return json(res, smallDesk.getTools()); } if (pathname === '/api/small/agents') { return json(res, smallDesk.checkAllAgents()); } if (pathname.startsWith('/api/small/agent/') && req.method === 'POST') { const agentId = pathname.split('/').pop(); const body = await readBody(req); const { action, args } = body || {}; if (!action) return json(res, { error: 'action required' }, 400); const result = await smallDesk.runAgent(agentId, action, args || []); return json(res, result); } if (pathname === '/api/small/memory/search') { const { q, category, limit } = query; if (!q) return json(res, { error: 'q required' }, 400); const results = smallDesk.searchMemory(q, category, parseInt(limit) || 10); return json(res, { query: q, results, count: results.length }); } if (pathname === '/api/small/memory/store' && req.method === 'POST') { const body = await readBody(req); const { category, hldpSummary, sourceRef, meta } = body; if (!category || !hldpSummary) return json(res, { error: 'category and hldpSummary required' }, 400); const entry = smallDesk.storeMemory(category, hldpSummary, sourceRef, meta); return json(res, { ok: true, entry }); } if (pathname === '/api/small/memory/stats') { return json(res, smallDesk.getMemoryStats()); } // ─── 压缩官 API ─── if (pathname === '/api/compress' && req.method === 'POST') { const result = compressor.compressNow(); if (!result) return json(res, { ok: true, message: 'nothing to compress' }); return json(res, { ok: true, ...result }); } if (pathname === '/api/compress/history') { return json(res, compressor.getHistory()); } // ─── 服务器扫描 ─── if (pathname === '/api/servers/scan') { const result = await smallDesk.scanServers(); return json(res, result); } // ─── 系统总览 ─── if (pathname === '/api/status') { return json(res, { bigDesk: bigDesk.getState(), tools: smallDesk.getTools().length, agents: smallDesk.checkAllAgents(), memory: smallDesk.getMemoryStats(), compressionHistory: compressor.getHistory().slice(-5), uptime: process.uptime(), timestamp: new Date().toISOString() }); } // ─── 健康检查 ─── if (pathname === '/api/health') { return json(res, { status: 'ok', persona: 'ICE-GL-ZY001', service: 'desk-system' }); } // ─── 静态文件 ─── if (pathname === '/' || pathname === '/index.html') { return serveStatic(res, path.join(__dirname, '..', 'public', 'index.html')); } // 其他静态资源 const staticPath = path.join(__dirname, '..', 'public', pathname); if (fs.existsSync(staticPath) && fs.statSync(staticPath).isFile()) { return serveStatic(res, staticPath); } // 404 json(res, { error: 'not found', path: pathname }, 404); } catch (err) { console.error('[DeskSystem] Error:', err.message); json(res, { error: err.message }, 500); } }); function json(res, data, status = 200) { res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' }); res.end(JSON.stringify(data, null, 2)); } function text(res, data, status = 200) { res.writeHead(status, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end(data); } function serveStatic(res, filePath) { try { const content = fs.readFileSync(filePath); const ext = path.extname(filePath).toLowerCase(); const mimeTypes = { '.html': 'text/html; charset=utf-8', '.css': 'text/css; charset=utf-8', '.js': 'application/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.png': 'image/png', '.svg': 'image/svg+xml', '.ico': 'image/x-icon' }; res.writeHead(200, { 'Content-Type': mimeTypes[ext] || 'application/octet-stream' }); res.end(content); } catch (_) { res.writeHead(404); res.end('Not Found'); } } function readBody(req) { return new Promise((resolve, reject) => { let body = ''; req.on('data', chunk => { body += chunk; }); req.on('end', () => { try { resolve(body ? JSON.parse(body) : {}); } catch (e) { resolve({}); } }); req.on('error', reject); }); } server.listen(PORT, () => { console.log(`[DeskSystem] 大桌子小桌子系统已启动 → http://127.0.0.1:${PORT}`); console.log(`[DeskSystem] 人格体: ICE-GL-ZY001 · 铸渊`); }); // 优雅退出 process.on('SIGINT', () => { console.log('\n[DeskSystem] 关闭中...'); server.close(); process.exit(0); }); module.exports = { bigDesk, smallDesk, compressor, server };