guanghulab/desk-system/server/small-desk.js
冰朔 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

340 lines
9.6 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.

/**
* 小桌子引擎 — Small Desk Engine
* ============================================
* 职责:
* 1. 记忆库检索(结构化 + 原文)
* 2. Agent 小队调度
* 3. 工具桌管理(拿→组装→用→放回)
* 4. 原文永久存储 + 路径索引
*
* 原则Agent 去小桌子检索、拿工具,结果放回大桌子
*/
const { EventEmitter } = require('events');
const { spawn, execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
class SmallDesk extends EventEmitter {
constructor(opts = {}) {
super();
this.dataDir = opts.dataDir || path.join(__dirname, '..', 'data');
this.repoRoot = opts.repoRoot || path.join(__dirname, '..', '..');
this.memoryIndex = {}; // 记忆索引
this.toolRegistry = {}; // 工具注册表
this.agentStatus = {}; // Agent 状态
this._ensureDir();
this._initTools();
this._initAgents();
this._loadMemoryIndex();
}
_ensureDir() {
fs.mkdirSync(path.join(this.dataDir, 'small-desk', 'memory'), { recursive: true });
fs.mkdirSync(path.join(this.dataDir, 'small-desk', 'tools'), { recursive: true });
}
// ============================================================
// 工具桌管理
// ============================================================
_initTools() {
this.toolRegistry = {
'image-studio': {
name: 'AI图片工作室',
path: 'image-studio/',
desc: 'AI图片生成与处理',
status: 'available'
},
'notion-sync': {
name: 'Notion同步',
path: 'notion-push/',
desc: '与Notion工作区双向同步',
status: 'available'
},
'exe-engine': {
name: '执行引擎',
path: 'exe-engine/',
desc: '多模型路由与执行',
status: 'available'
},
'bridge': {
name: '桥接模块',
path: 'bridge/',
desc: '服务间通信桥接',
status: 'available'
},
'connectors': {
name: '连接器集合',
path: 'connectors/',
desc: '外部服务连接器',
status: 'available'
},
'hldp-zy': {
name: 'HLDP-ZY 编码引擎',
path: 'hldp/',
desc: '意识编码与解析',
status: 'available'
},
'pool-agent': {
name: '算力汇聚池',
path: 'pool-agent/',
desc: '6台服务器虚拟算力池',
status: 'available'
},
'gatekeeper': {
name: '铸渊守门人',
path: 'mcp-servers/zhuyuan-gateway/',
desc: '远程服务器命令执行',
status: 'connected'
},
'agent-squad': {
name: 'Agent 小队',
path: 'agents/',
desc: '5个情报Agent',
status: 'ready'
},
'skill-brains': {
name: '技能大脑',
path: 'skill-brains/',
desc: '可加载/卸载的技能模块',
status: 'available'
}
};
}
getTools() {
return Object.entries(this.toolRegistry).map(([id, info]) => ({
id,
...info
}));
}
getTool(id) {
return this.toolRegistry[id] || null;
}
// ============================================================
// Agent 小队管理
// ============================================================
_initAgents() {
this.agentRegistry = {
'key-hunter': {
id: 'KEY-001',
name: '密钥猎手',
role: '找密钥/Token',
script: 'agents/key-hunter/hunt.py',
status: 'idle',
lastRun: null
},
'path-scout': {
id: 'PATH-002',
name: '路径探子',
role: '定位文件/内容搜索',
script: 'agents/path-scout/scout.py',
status: 'idle',
lastRun: null
},
'server-sentinel': {
id: 'SENTINEL-003',
name: '服务器哨兵',
role: '扫描服务器在线状态',
script: 'agents/server-sentinel/sentinel.py',
status: 'idle',
lastRun: null
},
'module-steward': {
id: 'MODULE-004',
name: '模块管家',
role: '模块查询/部署命令',
script: 'agents/module-steward/steward.py',
status: 'idle',
lastRun: null
},
'gate-bridge': {
id: 'GATE-005',
name: '门桥接',
role: 'Gatekeeper统一入口',
script: 'agents/gate-bridge/bridge.py',
status: 'idle',
lastRun: null
}
};
}
getAgents() {
return Object.entries(this.agentRegistry).map(([id, info]) => ({
id,
...info
}));
}
/**
* 调度 Agent 执行任务
*/
async runAgent(agentId, action, args = []) {
const agent = this.agentRegistry[agentId];
if (!agent) throw new Error(`Agent ${agentId} not found`);
const scriptPath = path.join(this.repoRoot, agent.script);
if (!fs.existsSync(scriptPath)) {
throw new Error(`Agent script not found: ${scriptPath}`);
}
this.agentRegistry[agentId].status = 'running';
this.emit('agent-start', { agentId, action });
return new Promise((resolve, reject) => {
const cmdArgs = [scriptPath, action, ...args];
const proc = spawn('python3', cmdArgs, {
cwd: this.repoRoot,
timeout: 30000
});
let stdout = '';
let stderr = '';
proc.stdout.on('data', (d) => { stdout += d.toString(); });
proc.stderr.on('data', (d) => { stderr += d.toString(); });
proc.on('close', (code) => {
this.agentRegistry[agentId].status = 'idle';
this.agentRegistry[agentId].lastRun = new Date().toISOString();
if (code === 0) {
this.emit('agent-done', { agentId, action, output: stdout });
resolve({ ok: true, output: stdout, agentId, action });
} else {
this.emit('agent-error', { agentId, action, error: stderr });
resolve({ ok: false, error: stderr, agentId, action });
}
});
proc.on('error', (err) => {
this.agentRegistry[agentId].status = 'error';
this.emit('agent-error', { agentId, action, error: err.message });
resolve({ ok: false, error: err.message, agentId, action });
});
});
}
/**
* 扫描全部 Agent 可用状态
*/
checkAllAgents() {
const result = {};
for (const [id, agent] of Object.entries(this.agentRegistry)) {
const scriptPath = path.join(this.repoRoot, agent.script);
result[id] = {
...agent,
available: fs.existsSync(scriptPath),
scriptExists: fs.existsSync(scriptPath)
};
}
return result;
}
// ============================================================
// 记忆库管理
// ============================================================
_loadMemoryIndex() {
const idxFile = path.join(this.dataDir, 'small-desk', 'memory', 'index.json');
try {
this.memoryIndex = JSON.parse(fs.readFileSync(idxFile, 'utf-8'));
} catch (_) {
this.memoryIndex = { entries: [], categories: {} };
}
}
_saveMemoryIndex() {
const idxFile = path.join(this.dataDir, 'small-desk', 'memory', 'index.json');
fs.writeFileSync(idxFile, JSON.stringify(this.memoryIndex, null, 2));
}
/**
* 存入记忆HLDP 压缩格式 + 原文引用)
*/
storeMemory(category, hldpSummary, sourceRef, meta = {}) {
const entry = {
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
category,
hldpSummary,
sourceRef,
meta,
timestamp: new Date().toISOString()
};
this.memoryIndex.entries.push(entry);
if (!this.memoryIndex.categories[category]) {
this.memoryIndex.categories[category] = [];
}
this.memoryIndex.categories[category].push(entry.id);
this._saveMemoryIndex();
this.emit('memory-stored', entry);
return entry;
}
/**
* 检索记忆
*/
searchMemory(query, category = null, limit = 10) {
let candidates = this.memoryIndex.entries;
if (category) {
const ids = this.memoryIndex.categories[category] || [];
candidates = candidates.filter(e => ids.includes(e.id));
}
// 简单关键词匹配(后续可接入向量检索)
const keywords = query.toLowerCase().split(/\s+/);
const scored = candidates.map(entry => {
const text = `${entry.hldpSummary} ${entry.category} ${JSON.stringify(entry.meta)}`.toLowerCase();
let score = 0;
for (const kw of keywords) {
if (text.includes(kw)) score += 1;
// 精确匹配加分
if (entry.hldpSummary.toLowerCase().includes(kw)) score += 2;
}
return { ...entry, score };
});
return scored
.filter(e => e.score > 0)
.sort((a, b) => b.score - a.score)
.slice(0, limit);
}
/**
* 获取记忆统计
*/
getMemoryStats() {
return {
total: this.memoryIndex.entries.length,
categories: Object.keys(this.memoryIndex.categories).map(cat => ({
name: cat,
count: (this.memoryIndex.categories[cat] || []).length
})),
lastUpdated: this.memoryIndex.entries.slice(-1)[0]?.timestamp || null
};
}
// ============================================================
// 全文搜索(调用路径探子)
// ============================================================
async searchFiles(pattern) {
return this.runAgent('path-scout', 'find', [pattern]);
}
async grepContent(pattern) {
return this.runAgent('path-scout', 'grep', [pattern]);
}
// ============================================================
// 服务器状态扫描(调用哨兵)
// ============================================================
async scanServers() {
return this.runAgent('server-sentinel', 'scan');
}
}
module.exports = SmallDesk;