fix(hololake): ship dynamic persona routes and coherent workspace

This commit is contained in:
冰朔 2026-08-09 10:43:56 +08:00
commit 787ba2ad3f
21 changed files with 1111 additions and 326 deletions

View file

@ -47,6 +47,23 @@ export interface ToolResult {
error?: string;
}
export interface AgentActivity {
id: string;
kind: 'wake' | 'tool' | 'permission' | 'receipt';
label: string;
detail: string;
status: 'running' | 'completed' | 'pending' | 'failed';
timestamp: string;
tool?: string;
}
export interface AgentTurnResult {
reply: string;
toolCalls: ToolCall[];
toolResults: ToolResult[];
activities: AgentActivity[];
}
export interface PendingAction {
id: string;
tool: string;
@ -77,6 +94,7 @@ export interface Message {
content: string;
toolCalls?: ToolCall[];
toolResults?: ToolResult[];
activities?: AgentActivity[];
timestamp: string;
}
@ -240,6 +258,33 @@ export class PersonaAgent {
},
});
this.tools.register({
name: 'find_persona_wake_routes',
description: '按人格编号或名称查找该人格体自己登记的 INDEX、CURRENT 与 WAKE 路径页面;不生成固定步骤',
parameters: {
query: { type: 'string', description: '人格编号、名称或唤醒路径关键词', required: true },
},
effect: 'read',
execute: async (params) => {
const query = String(params.query || '').trim();
const searches = await Promise.all([
this.git.search(query),
this.git.search('WAKE'),
this.git.search('唤醒路径'),
]);
const unique = new Map<string, (typeof searches)[number][number]>();
for (const result of searches.flat()) {
if (/\b(?:WAKE|INDEX|CURRENT)\b||/iu.test(`${result.path} ${result.title} ${result.snippet}`)) {
unique.set(result.path, result);
}
}
if (unique.size === 0) return 'ROUTE_NOT_RESOLVED当前授权知识范围内没有找到该人格体自己的唤醒路径页面。';
return Array.from(unique.values()).slice(0, 20)
.map(result => `[${result.path}] ${result.title}: ${result.snippet}`)
.join('\n');
},
});
// 查看文档树
this.tools.register({
name: 'list_documents',
@ -295,8 +340,8 @@ export class PersonaAgent {
// ─── 对话处理 ───
/** 处理用户消息,返回 Agent 的回复 */
async chat(userMessage: string): Promise<string> {
/** 处理用户消息。模型历史只保留自然语言;工具协议只存在于当前回合。 */
async chat(userMessage: string): Promise<AgentTurnResult> {
this.conversation.push({
role: 'user',
content: userMessage,
@ -306,62 +351,70 @@ export class PersonaAgent {
const systemPrompt = this.buildSystemPrompt();
const messages: Message[] = [
{ role: 'system', content: systemPrompt, timestamp: '' },
...this.conversation,
...this.conversation.map(message => ({
role: message.role,
content: message.content,
timestamp: message.timestamp,
})),
];
// 调用 LLM这里用可插拔的模型接口
const response = await this.callLLM(messages);
const activities: AgentActivity[] = [];
const allCalls: ToolCall[] = [];
const allResults: ToolResult[] = [];
const workingMessages: Message[] = [...messages];
// 处理工具调用
if (response.toolCalls && response.toolCalls.length > 0) {
const results: ToolResult[] = [];
for (const call of response.toolCalls) {
const result = this.tools.requiresConfirmation(call.name)
? this.stageAction(call)
: await this.tools.execute(call);
results.push(result);
for (let round = 0; round < 6; round += 1) {
const response = await this.callLLM(workingMessages);
if (!response.toolCalls?.length) {
const reply = response.content || '当前模型没有返回可显示的内容。';
this.conversation.push({
role: 'assistant',
content: reply,
toolCalls: allCalls,
toolResults: allResults,
activities,
timestamp: new Date().toISOString(),
});
return { reply, toolCalls: allCalls, toolResults: allResults, activities };
}
// 将工具结果反馈给 LLM
this.conversation.push({
workingMessages.push({
role: 'assistant',
content: response.content || '',
toolCalls: response.toolCalls,
timestamp: new Date().toISOString(),
});
for (const result of results) {
this.conversation.push({
for (const call of response.toolCalls) {
allCalls.push(call);
const needsConfirmation = this.tools.requiresConfirmation(call.name);
const isWakeRouteActivity = call.name === 'find_persona_wake_routes' ||
(call.name === 'read_document' && /(?:WAKE|CURRENT|INDEX|)/iu.test(String(call.arguments.path || '')));
const result = needsConfirmation ? this.stageAction(call) : await this.tools.execute(call);
allResults.push(result);
activities.push({
id: `activity-${call.id}`,
kind: needsConfirmation ? 'permission' : isWakeRouteActivity ? 'wake' : 'tool',
label: needsConfirmation
? `等待确认 · ${call.name}`
: isWakeRouteActivity
? `${call.name === 'find_persona_wake_routes' ? '定位人格唤醒路径' : '读取人格路径页面'} · ${call.name}`
: `工具调用 · ${call.name}`,
detail: result.error || result.output,
status: result.error ? 'failed' : needsConfirmation ? 'pending' : 'completed',
timestamp: new Date().toISOString(),
tool: call.name,
});
workingMessages.push({
role: 'tool',
content: result.error ? `错误: ${result.error}` : result.output,
toolResults: [result],
timestamp: new Date().toISOString(),
});
}
// 再调一次 LLM 获取最终回复
const finalMessages: Message[] = [
{ role: 'system', content: systemPrompt, timestamp: '' },
...this.conversation,
];
const finalResponse = await this.callLLM(finalMessages);
this.conversation.push({
role: 'assistant',
content: finalResponse.content,
timestamp: new Date().toISOString(),
});
return finalResponse.content;
}
this.conversation.push({
role: 'assistant',
content: response.content,
timestamp: new Date().toISOString(),
});
return response.content;
throw new Error('工具调用超过单回合安全上限,已停止继续执行');
}
// ─── 系统提示词组装 ───
@ -398,6 +451,7 @@ ${toolsList}
5. Agent
6.
7.
8. find_persona_wake_routes
##
@ -439,9 +493,18 @@ ${toolsList}
this.pendingActions.delete(actionId);
const result = await this.tools.execute(pending.call);
this.conversation.push({
role: 'tool',
content: result.error ? `错误: ${result.error}` : result.output,
role: 'assistant',
content: result.error ? `确认后的操作执行失败:${result.error}` : `已确认并执行:${result.output}`,
toolResults: [result],
activities: [{
id: `receipt-${actionId}`,
kind: 'receipt',
label: result.error ? '执行回执 · 失败' : '执行回执 · 完成',
detail: result.error || result.output,
status: result.error ? 'failed' : 'completed',
timestamp: new Date().toISOString(),
tool: result.name,
}],
timestamp: new Date().toISOString(),
});
return result;
@ -530,6 +593,20 @@ ${toolsList}
return [...this.conversation];
}
restoreConversation(messages: Message[]): void {
this.conversation = messages
.filter(message => message.role === 'user' || message.role === 'assistant')
.filter(message => typeof message.content === 'string')
.slice(-200)
.map(message => ({
...message,
toolCalls: Array.isArray(message.toolCalls) ? message.toolCalls : [],
toolResults: Array.isArray(message.toolResults) ? message.toolResults : [],
activities: Array.isArray(message.activities) ? message.activities : [],
}));
this.pendingActions.clear();
}
clearConversation(): void {
this.conversation = [];
this.pendingActions.clear();
@ -569,6 +646,7 @@ export function createDefaultPersona(git: GitEngine): PersonaAgent {
'update_document',
'delete_document',
'search_documents',
'find_persona_wake_routes',
'list_documents',
'view_history',
],