fix(hololake): ship dynamic persona routes and coherent workspace
This commit is contained in:
parent
5105ec5e32
commit
787ba2ad3f
21 changed files with 1111 additions and 326 deletions
|
|
@ -8,9 +8,11 @@
|
|||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import { GitEngine } from './git-engine.js';
|
||||
import { PersonaAgent, createDefaultPersona } from './persona-agent.js';
|
||||
import { PersonaAgent, createDefaultPersona, type Message } from './persona-agent.js';
|
||||
import { projectHumanKnowledgeTree } from './language-protocol-kernel.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import crypto from 'crypto';
|
||||
|
||||
// __dirname 兼容 CJS 和 ESM
|
||||
const _dirname = typeof __dirname !== 'undefined' ? __dirname : process.cwd();
|
||||
|
|
@ -19,6 +21,7 @@ const _dirname = typeof __dirname !== 'undefined' ? __dirname : process.cwd();
|
|||
|
||||
const PORT = parseInt(process.env.KB_PORT || '3890', 10);
|
||||
const REPO_PATH = process.env.KB_REPO_PATH || path.resolve(_dirname, '../kb-data');
|
||||
const AGENT_STATE_PATH = process.env.HOLOLAKE_AGENT_STATE_PATH || path.resolve(REPO_PATH, '../agent-conversations.json');
|
||||
|
||||
// ─── 初始化 ───
|
||||
|
||||
|
|
@ -242,19 +245,84 @@ app.post('/api/forgejo/push', async (req, res) => {
|
|||
|
||||
// ─── Agent(人格体) ───
|
||||
|
||||
let personaAgent: PersonaAgent | null = null;
|
||||
interface ConversationRecord {
|
||||
id: string;
|
||||
title: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
agent: PersonaAgent;
|
||||
}
|
||||
|
||||
function getAgent(): PersonaAgent {
|
||||
if (!personaAgent) {
|
||||
personaAgent = createDefaultPersona(engine);
|
||||
console.log('人格体已创建:', personaAgent.getDefinition().name);
|
||||
const conversations = new Map<string, ConversationRecord>();
|
||||
let activeConversationId = '';
|
||||
|
||||
function persistConversations(): void {
|
||||
fs.mkdirSync(path.dirname(AGENT_STATE_PATH), { recursive: true });
|
||||
const records = Array.from(conversations.values()).map(record => ({
|
||||
id: record.id,
|
||||
title: record.title,
|
||||
createdAt: record.createdAt,
|
||||
updatedAt: record.updatedAt,
|
||||
messages: record.agent.getConversation(),
|
||||
}));
|
||||
fs.writeFileSync(AGENT_STATE_PATH, JSON.stringify({ activeConversationId, conversations: records }, null, 2), { mode: 0o600 });
|
||||
}
|
||||
|
||||
function createConversation(title = '新对话', restored?: { id: string; createdAt: string; updatedAt: string; messages: Message[] }): ConversationRecord {
|
||||
const timestamp = new Date().toISOString();
|
||||
const agent = createDefaultPersona(engine);
|
||||
if (restored?.messages) agent.restoreConversation(restored.messages);
|
||||
const record: ConversationRecord = {
|
||||
id: restored?.id || `conversation-${crypto.randomUUID()}`,
|
||||
title,
|
||||
createdAt: restored?.createdAt || timestamp,
|
||||
updatedAt: restored?.updatedAt || timestamp,
|
||||
agent,
|
||||
};
|
||||
conversations.set(record.id, record);
|
||||
activeConversationId = record.id;
|
||||
return record;
|
||||
}
|
||||
|
||||
function initializeConversations(): void {
|
||||
if (conversations.size) return;
|
||||
try {
|
||||
const stored = JSON.parse(fs.readFileSync(AGENT_STATE_PATH, 'utf8')) as any;
|
||||
for (const item of Array.isArray(stored.conversations) ? stored.conversations : []) {
|
||||
if (!item?.id || !Array.isArray(item.messages)) continue;
|
||||
createConversation(String(item.title || '历史对话'), {
|
||||
id: String(item.id),
|
||||
createdAt: String(item.createdAt || new Date().toISOString()),
|
||||
updatedAt: String(item.updatedAt || new Date().toISOString()),
|
||||
messages: item.messages,
|
||||
});
|
||||
}
|
||||
if (stored.activeConversationId && conversations.has(stored.activeConversationId)) {
|
||||
activeConversationId = stored.activeConversationId;
|
||||
}
|
||||
} catch {
|
||||
// 首次启动没有会话文件。
|
||||
}
|
||||
return personaAgent;
|
||||
if (!conversations.size) createConversation();
|
||||
}
|
||||
|
||||
function getConversationRecord(requestedId?: unknown): ConversationRecord {
|
||||
initializeConversations();
|
||||
const id = typeof requestedId === 'string' && requestedId ? requestedId : activeConversationId;
|
||||
const record = conversations.get(id);
|
||||
if (!record) throw new Error('对话不存在或已经删除');
|
||||
activeConversationId = record.id;
|
||||
return record;
|
||||
}
|
||||
|
||||
function getAgent(requestedId?: unknown): PersonaAgent {
|
||||
return getConversationRecord(requestedId).agent;
|
||||
}
|
||||
|
||||
// Agent 状态
|
||||
app.get('/api/agent/status', (_req, res) => {
|
||||
const agent = getAgent();
|
||||
const record = getConversationRecord(_req.query.conversationId);
|
||||
const agent = record.agent;
|
||||
const def = agent.getDefinition();
|
||||
const runtime = agent.getRuntimeStatus();
|
||||
res.json({
|
||||
|
|
@ -272,9 +340,37 @@ app.get('/api/agent/status', (_req, res) => {
|
|||
tools: runtime.toolNames,
|
||||
engine: 'git',
|
||||
repo: REPO_PATH,
|
||||
conversationId: record.id,
|
||||
conversationTitle: record.title,
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/agent/conversations', (_req, res) => {
|
||||
initializeConversations();
|
||||
res.json({
|
||||
ok: true,
|
||||
activeConversationId,
|
||||
conversations: Array.from(conversations.values())
|
||||
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
|
||||
.map(record => ({ id: record.id, title: record.title, createdAt: record.createdAt, updatedAt: record.updatedAt, messageCount: record.agent.getConversation().length })),
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/api/agent/conversations', (_req, res) => {
|
||||
const record = createConversation();
|
||||
persistConversations();
|
||||
res.status(201).json({ ok: true, conversation: { id: record.id, title: record.title, createdAt: record.createdAt, updatedAt: record.updatedAt, messageCount: 0 } });
|
||||
});
|
||||
|
||||
app.delete('/api/agent/conversations/:conversationId', (req, res) => {
|
||||
initializeConversations();
|
||||
if (!conversations.delete(req.params.conversationId)) return res.status(404).json({ ok: false, error: '对话不存在' });
|
||||
if (!conversations.size) createConversation();
|
||||
if (!conversations.has(activeConversationId)) activeConversationId = Array.from(conversations.keys())[0];
|
||||
persistConversations();
|
||||
res.json({ ok: true, activeConversationId });
|
||||
});
|
||||
|
||||
// Agent 对话
|
||||
app.post('/api/agent/chat', async (req, res) => {
|
||||
try {
|
||||
|
|
@ -282,13 +378,18 @@ app.post('/api/agent/chat', async (req, res) => {
|
|||
if (!message || typeof message !== 'string') {
|
||||
return res.status(400).json({ ok: false, error: 'message 必填且为字符串' });
|
||||
}
|
||||
const agent = getAgent();
|
||||
const reply = await agent.chat(message);
|
||||
const record = getConversationRecord(req.body?.conversationId);
|
||||
const turn = await record.agent.chat(message);
|
||||
if (record.title === '新对话') record.title = message.trim().slice(0, 28) || '新对话';
|
||||
record.updatedAt = new Date().toISOString();
|
||||
persistConversations();
|
||||
res.json({
|
||||
ok: true,
|
||||
reply,
|
||||
conversationLength: agent.getConversation().length,
|
||||
pendingActions: agent.getPendingActions(),
|
||||
...turn,
|
||||
conversationId: record.id,
|
||||
conversationTitle: record.title,
|
||||
conversationLength: record.agent.getConversation().length,
|
||||
pendingActions: record.agent.getPendingActions(),
|
||||
});
|
||||
} catch (err: any) {
|
||||
res.status(500).json({ ok: false, error: err.message });
|
||||
|
|
@ -296,13 +397,16 @@ app.post('/api/agent/chat', async (req, res) => {
|
|||
});
|
||||
|
||||
app.get('/api/agent/actions', (_req, res) => {
|
||||
res.json({ ok: true, actions: getAgent().getPendingActions() });
|
||||
res.json({ ok: true, actions: getAgent(_req.query.conversationId).getPendingActions() });
|
||||
});
|
||||
|
||||
app.post('/api/agent/actions/:actionId/confirm', async (req, res) => {
|
||||
try {
|
||||
const result = await getAgent().confirmAction(req.params.actionId);
|
||||
res.json({ ok: true, result, actions: getAgent().getPendingActions() });
|
||||
const record = getConversationRecord(req.body?.conversationId);
|
||||
const result = await record.agent.confirmAction(req.params.actionId);
|
||||
record.updatedAt = new Date().toISOString();
|
||||
persistConversations();
|
||||
res.json({ ok: true, result, messages: record.agent.getConversation(), actions: record.agent.getPendingActions() });
|
||||
} catch (err: any) {
|
||||
res.status(409).json({ ok: false, error: err.message });
|
||||
}
|
||||
|
|
@ -310,8 +414,11 @@ app.post('/api/agent/actions/:actionId/confirm', async (req, res) => {
|
|||
|
||||
app.post('/api/agent/actions/:actionId/reject', (req, res) => {
|
||||
try {
|
||||
getAgent().rejectAction(req.params.actionId);
|
||||
res.json({ ok: true, actions: getAgent().getPendingActions() });
|
||||
const record = getConversationRecord(req.body?.conversationId);
|
||||
record.agent.rejectAction(req.params.actionId);
|
||||
record.updatedAt = new Date().toISOString();
|
||||
persistConversations();
|
||||
res.json({ ok: true, messages: record.agent.getConversation(), actions: record.agent.getPendingActions() });
|
||||
} catch (err: any) {
|
||||
res.status(409).json({ ok: false, error: err.message });
|
||||
}
|
||||
|
|
@ -319,14 +426,17 @@ app.post('/api/agent/actions/:actionId/reject', (req, res) => {
|
|||
|
||||
// Agent 对话历史
|
||||
app.get('/api/agent/conversation', (_req, res) => {
|
||||
const agent = getAgent();
|
||||
res.json({ ok: true, messages: agent.getConversation() });
|
||||
const record = getConversationRecord(_req.query.conversationId);
|
||||
res.json({ ok: true, conversationId: record.id, title: record.title, messages: record.agent.getConversation() });
|
||||
});
|
||||
|
||||
// 清空对话
|
||||
app.post('/api/agent/clear', (_req, res) => {
|
||||
const agent = getAgent();
|
||||
agent.clearConversation();
|
||||
const record = getConversationRecord(_req.body?.conversationId);
|
||||
record.agent.clearConversation();
|
||||
record.title = '新对话';
|
||||
record.updatedAt = new Date().toISOString();
|
||||
persistConversations();
|
||||
res.json({ ok: true, message: '对话已清空' });
|
||||
});
|
||||
|
||||
|
|
@ -335,10 +445,10 @@ app.post('/api/agent/clear', (_req, res) => {
|
|||
async function start() {
|
||||
await engine.init();
|
||||
// 初始化人格体
|
||||
getAgent();
|
||||
initializeConversations();
|
||||
app.listen(PORT, '127.0.0.1', () => {
|
||||
console.log(`光湖知识库 API 已启动: http://localhost:${PORT}`);
|
||||
const runtime = personaAgent?.getRuntimeStatus();
|
||||
const runtime = getAgent().getRuntimeStatus();
|
||||
console.log(`人格体运行状态: ${runtime?.operational ? '已接入模型' : '等待模型配置'}`);
|
||||
console.log(`仓库路径: ${REPO_PATH}`);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue