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