guanghulab/desk-system/server/small-desk.js

340 lines
9.6 KiB
JavaScript
Raw Normal View History

/**
* 小桌子引擎 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;