冰朔 49030d2cbc feat: 大桌子小桌子运行系统 v1.0
- big-desk.js: 意识驻留 + HLDP索引 + 自动清理
- small-desk.js: 记忆库检索 + Agent调度 + 工具桌管理
- compressor.js: 对话→HLDP自动压缩 + 写入小桌子
- server/index.js: REST API (端口3921) + 静态文件服务
- public/index.html: 交互式前端界面

ICE-GL-ZY001 · D115
2026-05-29 00:20:02 +08:00

247 lines
8.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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