Files
metona-ai-desktop/electron/harness/hooks/post-tool.ts
T

72 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Post-Tool Hooks — 工具执行后钩子
*
* 用途:结果验证、审计日志写入、记忆触发、错误恢复建议。
*
* @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第五章
*/
import type { MetonaToolCall, MetonaToolResult } from '../types';
import type { AuditService } from '../../services/audit.service';
import type { MemoryManager } from '../memory/manager';
import log from 'electron-log';
export interface PostToolHook {
afterExecute(toolCall: MetonaToolCall, result: MetonaToolResult, sessionId: string): Promise<void>;
}
/** 审计日志钩子 */
export class AuditLogHook implements PostToolHook {
constructor(private auditService: AuditService) {}
async afterExecute(toolCall: MetonaToolCall, result: MetonaToolResult, sessionId: string): Promise<void> {
// #17 修复: AuditHook 应为 "fire and forget"hook 失败不应影响工具执行链
// 虽然 AuditService.log() 内部已 try-catch,但 hook 层再加一层防御,
// 确保任何意外异常(如 getDB 抛错、JSON.stringify 失败)都不会冒泡到 ToolRegistry
try {
this.auditService.logToolCall({
sessionId,
iteration: toolCall.iteration,
toolName: toolCall.name,
args: toolCall.args,
outcome: result.success ? 'success' : 'error',
result: result.result,
error: result.error,
durationMs: result.durationMs,
});
} catch (err) {
log.error('[AuditLogHook] Failed to log audit:', err);
// 不抛出,让工具执行链继续
}
}
}
/** 记忆触发钩子 */
export class MemoryTriggerHook implements PostToolHook {
/** 仅对搜索类工具触发记忆,避免每次 read_file 都产生低价值记忆条目 */
private memorableTools = ['web_search', 'memory_search'];
/** 单次工具结果存储上限(字符),防止过大内容淹没记忆系统 */
private readonly MAX_MEMORY_CONTENT = 500;
constructor(private memoryManager: MemoryManager) {}
async afterExecute(toolCall: MetonaToolCall, result: MetonaToolResult, sessionId: string): Promise<void> {
if (this.memorableTools.includes(toolCall.name) && result.success) {
const content = typeof result.result === 'string' ? result.result : JSON.stringify(result.result);
try {
this.memoryManager.store({
type: 'episodic',
content: `Tool ${toolCall.name} returned: ${content.slice(0, this.MAX_MEMORY_CONTENT)}`,
source: 'tool_result',
sessionId,
importance: 0.6,
});
} catch (error) {
// 记忆存储失败不应影响工具执行结果
log.error('[MemoryTriggerHook] Failed to store episodic memory:', error);
}
}
}
}