feat: MetonaAI Desktop 初始项目

- Electron + React + TypeScript 架构
- 三栏布局: Sidebar | ChatPanel | DetailPanel
- 9 个内置工具 (文件系统/网络/记忆/命令)
- SQLite 持久化 (better-sqlite3)
- MUI 暗色/亮色主题系统
- Agent Loop ReAct 状态机引擎
- DeepSeek / Agnes AI / Ollama Provider 适配器
- MCP 协议集成
- 系统托盘 + 全局快捷键
- Tailwind CSS v4 + Tailwind Merge
- 修复: Sidebar 缺失 TextField 导入导致黑屏
This commit is contained in:
thzxx
2026-06-27 21:33:27 +08:00
commit 1d185db6b3
109 changed files with 39155 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
export type { PreToolHook, HookResult } from './pre-tool';
export { PermissionCheckHook, RateLimitHook } from './pre-tool';
export type { PostToolHook } from './post-tool';
export { AuditLogHook, MemoryTriggerHook } from './post-tool';
+53
View File
@@ -0,0 +1,53 @@
/**
* 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';
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> {
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,
});
}
}
/** 记忆触发钩子 */
export class MemoryTriggerHook implements PostToolHook {
private memorableTools = ['web_search', 'read_file', 'memory_search'];
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);
this.memoryManager.store({
type: 'episodic',
content: `Tool ${toolCall.name} returned: ${content.slice(0, 500)}`,
source: 'tool_result',
sessionId,
importance: 0.6,
});
}
}
}
+53
View File
@@ -0,0 +1,53 @@
/**
* Pre-Tool Hooks — 工具执行前钩子
*
* @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第五章
*/
import type { MetonaToolCall } from '../types';
import type { PolicyEngine } from '../sandbox/permissions';
export interface HookResult {
blocked: boolean;
reason?: string;
modifiedArgs?: Record<string, unknown>;
}
export interface PreToolHook {
beforeExecute(toolCall: MetonaToolCall, sessionId: string): Promise<HookResult>;
}
/** 权限校验钩子 — 集成 PolicyEngine */
export class PermissionCheckHook implements PreToolHook {
constructor(private policyEngine: PolicyEngine) {}
async beforeExecute(toolCall: MetonaToolCall, _sessionId: string): Promise<HookResult> {
const result = this.policyEngine.checkAuthorization(toolCall.name, toolCall.args);
if (!result.authorized) {
return { blocked: true, reason: result.reason };
}
return { blocked: false };
}
}
/** 速率限制钩子 */
export class RateLimitHook implements PreToolHook {
private callCounts = new Map<string, { count: number; resetTime: number }>();
constructor(private maxCallsPerMinute: number = 20) {}
async beforeExecute(toolCall: MetonaToolCall, _sessionId: string): Promise<HookResult> {
const key = toolCall.name;
const now = Date.now();
const entry = this.callCounts.get(key);
if (entry && entry.resetTime > now) {
if (entry.count >= this.maxCallsPerMinute) {
return { blocked: true, reason: `Rate limit exceeded for tool "${toolCall.name}"` };
}
entry.count++;
} else {
this.callCounts.set(key, { count: 1, resetTime: now + 60_000 });
}
return { blocked: false };
}
}