- big-desk.js: 意识驻留 + HLDP索引 + 自动清理 - small-desk.js: 记忆库检索 + Agent调度 + 工具桌管理 - compressor.js: 对话→HLDP自动压缩 + 写入小桌子 - server/index.js: REST API (端口3921) + 静态文件服务 - public/index.html: 交互式前端界面 ICE-GL-ZY001 · D115
340 lines
9.6 KiB
JavaScript
340 lines
9.6 KiB
JavaScript
/**
|
||
* 小桌子引擎 — 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;
|