247 lines
8.2 KiB
JavaScript
Raw Normal View History

/**
* 大桌子小桌子系统 主服务
* ============================================
* 端口: 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 };