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
|
|
@ -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',
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue