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:
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* MetonaAI Desktop — Electron 主进程入口
|
||||
*
|
||||
* 启动流程(按架构规范强制顺序):
|
||||
* 1. 初始化日志系统
|
||||
* 2. 选择/创建工作空间 → 校验必需文件(缺失自动创建)
|
||||
* 3. 连接 SQLite → 执行 schema 迁移 → 加载配置
|
||||
* 4. 初始化 Provider Adapter
|
||||
* 5. 加载 4 个磁盘文件 → 构建 System Prompt
|
||||
* 6. 注册内置工具 + 连接 MCP Servers
|
||||
* 7. 启动 React UI → Agent 就绪
|
||||
*
|
||||
* @see docs/MetonaAI-Desktop 架构与交互设计.html — 启动流程
|
||||
* @see docs/MetonaAI-Desktop UI UX 设计集成方案.html — 窗口管理
|
||||
*/
|
||||
|
||||
import { app, shell, Menu } from 'electron';
|
||||
import { join } from 'path';
|
||||
import { electronApp, optimizer } from '@electron-toolkit/utils';
|
||||
import log from 'electron-log';
|
||||
import { DatabaseService } from './services/database.service';
|
||||
import { SessionService } from './services/session.service';
|
||||
import { ConfigService } from './services/config.service';
|
||||
import { WorkspaceService } from './services/workspace.service';
|
||||
import { AuditService } from './services/audit.service';
|
||||
import { SessionRecorder } from './services/session-recorder.service';
|
||||
import { TrayManager } from './services/tray-manager.service';
|
||||
import { WindowManager } from './services/window-manager.service';
|
||||
import { MCPManager } from './services/mcp-manager.service';
|
||||
import { ContextBuilder } from './harness/prompts/context-builder';
|
||||
import { MemoryManager } from './harness/memory/manager';
|
||||
import { registerAllIPCHandlers } from './ipc/handlers';
|
||||
import { AgentLoopEngine } from './harness/agent-loop';
|
||||
import { ToolRegistry } from './harness/tools/registry';
|
||||
import { DeepSeekAdapter } from './harness/adapters/deepseek.adapter';
|
||||
import { AgnesAdapter } from './harness/adapters/agnes-ai.adapter';
|
||||
import { OllamaAdapter } from './harness/adapters/ollama.adapter';
|
||||
import {
|
||||
ReadFileTool, WriteFileTool, ListDirectoryTool, SearchFilesTool,
|
||||
WebSearchTool, WebExtractTool,
|
||||
MemoryStoreTool, MemorySearchTool,
|
||||
RunCommandTool,
|
||||
} from './harness/tools/built-in';
|
||||
import { AuditLogHook, MemoryTriggerHook, PermissionCheckHook, RateLimitHook } from './harness/hooks';
|
||||
import { PolicyEngine } from './harness/sandbox/permissions';
|
||||
import { SandboxManager } from './harness/sandbox/sandbox';
|
||||
import { PromptInjectionDefender } from './harness/security/prompt-injection-defense';
|
||||
import { OutputValidator } from './harness/verification/output-validator';
|
||||
|
||||
// ===== 步骤 1: 初始化日志系统(SYS 层)=====
|
||||
log.transports.file.level = 'info';
|
||||
log.transports.console.level = 'debug';
|
||||
|
||||
let databaseService: DatabaseService | null = null;
|
||||
let trayManager: TrayManager | null = null;
|
||||
let windowManager: WindowManager | null = null;
|
||||
|
||||
async function initialize(): Promise<void> {
|
||||
log.info('MetonaAI Desktop starting...');
|
||||
electronApp.setAppUserModelId('com.metona.ai-desktop');
|
||||
|
||||
// 移除原生菜单栏
|
||||
Menu.setApplicationMenu(null);
|
||||
|
||||
app.on('browser-window-created', (_, window) => {
|
||||
optimizer.watchWindowShortcuts(window);
|
||||
});
|
||||
|
||||
// ===== 步骤 2: 工作空间 =====
|
||||
const workspaceService = new WorkspaceService();
|
||||
const workspaceInfo = workspaceService.initialize();
|
||||
log.info(`Workspace: ${workspaceInfo.path} (missing: ${workspaceInfo.missingFiles.join(', ') || 'none'})`);
|
||||
|
||||
// ===== 步骤 3: SQLite =====
|
||||
databaseService = new DatabaseService(workspaceInfo.path);
|
||||
databaseService.initialize();
|
||||
const db = databaseService.getDB();
|
||||
|
||||
const sessionService = new SessionService(() => db);
|
||||
const configService = new ConfigService(() => db);
|
||||
|
||||
// ===== 日志服务 =====
|
||||
const auditService = new AuditService(() => db);
|
||||
const sessionRecorder = new SessionRecorder(workspaceInfo.path);
|
||||
|
||||
// ===== 记忆系统 =====
|
||||
const memoryManager = new MemoryManager(() => db);
|
||||
memoryManager.initialize();
|
||||
|
||||
// ===== 步骤 4: Provider Adapter =====
|
||||
const provider = configService.get<string>('llm.provider') ?? '';
|
||||
const model = configService.get<string>('llm.model') ?? '';
|
||||
const apiKey = configService.get<string>('llm.apiKey') ?? '';
|
||||
const baseURL = configService.get<string>('llm.baseURL') ?? '';
|
||||
|
||||
if (!provider || !baseURL || !model) {
|
||||
log.warn('LLM not configured. Please set provider, baseURL, and model in Settings.');
|
||||
}
|
||||
|
||||
const adapterConfig = { provider, baseURL, apiKey, defaultModel: model };
|
||||
let adapter;
|
||||
switch (provider) {
|
||||
case 'agnes': adapter = new AgnesAdapter(adapterConfig); break;
|
||||
case 'ollama': adapter = new OllamaAdapter(adapterConfig); break;
|
||||
default: adapter = new DeepSeekAdapter(adapterConfig); break;
|
||||
}
|
||||
|
||||
// ===== 步骤 5: 工作空间文件 + System Prompt =====
|
||||
const contextBuilder = new ContextBuilder();
|
||||
|
||||
// ===== 步骤 6: 注册 9 个内置工具 =====
|
||||
const toolRegistry = new ToolRegistry();
|
||||
toolRegistry.registerBuiltin(new ReadFileTool());
|
||||
toolRegistry.registerBuiltin(new WriteFileTool());
|
||||
toolRegistry.registerBuiltin(new ListDirectoryTool());
|
||||
toolRegistry.registerBuiltin(new SearchFilesTool());
|
||||
toolRegistry.registerBuiltin(new WebSearchTool());
|
||||
toolRegistry.registerBuiltin(new WebExtractTool());
|
||||
toolRegistry.registerBuiltin(new MemoryStoreTool(memoryManager));
|
||||
toolRegistry.registerBuiltin(new MemorySearchTool(memoryManager));
|
||||
toolRegistry.registerBuiltin(new RunCommandTool());
|
||||
log.info(`Registered ${toolRegistry.size} built-in tools`);
|
||||
|
||||
// ===== MCP Manager =====
|
||||
const mcpManager = new MCPManager(() => db, toolRegistry);
|
||||
mcpManager.initialize().catch((err) => {
|
||||
log.warn('MCP Manager initialization error:', err);
|
||||
});
|
||||
|
||||
// ===== 安全模块 =====
|
||||
const policyEngine = new PolicyEngine();
|
||||
const sandboxManager = new SandboxManager({
|
||||
allowedPaths: [workspaceInfo.path],
|
||||
networkPolicy: 'allowlist',
|
||||
});
|
||||
const promptDefender = new PromptInjectionDefender();
|
||||
const outputValidator = new OutputValidator();
|
||||
|
||||
// ===== Hooks =====
|
||||
const preToolHooks = [
|
||||
new PermissionCheckHook(policyEngine),
|
||||
new RateLimitHook(20),
|
||||
];
|
||||
const postToolHooks = [
|
||||
new AuditLogHook(auditService),
|
||||
new MemoryTriggerHook(memoryManager),
|
||||
];
|
||||
|
||||
// ===== Agent Loop =====
|
||||
const agentLoop = new AgentLoopEngine({}, adapter, toolRegistry, preToolHooks, postToolHooks);
|
||||
agentLoop.setTools(toolRegistry.listTools());
|
||||
agentLoop.setWorkspacePath(workspaceInfo.path);
|
||||
|
||||
// ===== 窗口管理 =====
|
||||
windowManager = new WindowManager();
|
||||
const mainWindow = windowManager.createWindow({
|
||||
id: 'main',
|
||||
workspacePath: workspaceInfo.path,
|
||||
title: 'MetonaAI Desktop',
|
||||
});
|
||||
|
||||
// ===== 系统托盘 =====
|
||||
const resourcesPath = join(__dirname, '../../assets');
|
||||
trayManager = new TrayManager(resourcesPath);
|
||||
trayManager.initialize(mainWindow);
|
||||
|
||||
// ===== 全局快捷键 =====
|
||||
windowManager.registerGlobalShortcuts();
|
||||
|
||||
// ===== Agent 状态同步到托盘 =====
|
||||
agentLoop.on('stateChange', (data: { previous: string; current: string }) => {
|
||||
const statusMap: Record<string, 'idle' | 'thinking' | 'executing' | 'error'> = {
|
||||
INIT: 'idle', THINKING: 'thinking', PARSING: 'thinking',
|
||||
EXECUTING: 'executing', OBSERVING: 'executing', REFLECTING: 'thinking',
|
||||
COMPRESSING: 'thinking', TERMINATED: 'idle',
|
||||
};
|
||||
trayManager?.setStatus(statusMap[data.current] ?? 'idle');
|
||||
});
|
||||
|
||||
// ===== Agent 完成时发送系统通知 =====
|
||||
agentLoop.on('complete', (data: { sessionId: string; durationMs: number }) => {
|
||||
trayManager?.sendNotification(
|
||||
'MetonaAI — 任务完成',
|
||||
`Agent 已完成任务 (${(data.durationMs / 1000).toFixed(1)}s)`,
|
||||
() => windowManager?.focusWindow(),
|
||||
);
|
||||
});
|
||||
|
||||
// ===== 注册 IPC =====
|
||||
registerAllIPCHandlers(
|
||||
mainWindow, sessionService, configService, workspaceService,
|
||||
contextBuilder, agentLoop, toolRegistry, auditService,
|
||||
sessionRecorder, memoryManager, mcpManager,
|
||||
);
|
||||
|
||||
// ===== 应用生命周期 =====
|
||||
app.on('window-all-closed', () => {
|
||||
// macOS: 保持应用运行(托盘模式)
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (windowManager && windowManager.count === 0) {
|
||||
windowManager.createWindow({ id: 'main', workspacePath: workspaceInfo.path });
|
||||
} else {
|
||||
windowManager?.focusWindow();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
// 标记为正在退出,允许窗口关闭
|
||||
(global as Record<string, unknown>).isQuitting = true;
|
||||
windowManager?.unregisterGlobalShortcuts();
|
||||
trayManager?.destroy();
|
||||
windowManager?.closeAll();
|
||||
mcpManager.shutdown().catch(() => {});
|
||||
if (databaseService) { databaseService.close(); databaseService = null; }
|
||||
});
|
||||
|
||||
log.info(`MetonaAI Desktop initialized [Provider: ${provider}, Model: ${model}, Tools: ${toolRegistry.size}]`);
|
||||
}
|
||||
|
||||
app.whenReady().then(initialize);
|
||||
|
||||
app.on('web-contents-created', (_, contents) => {
|
||||
contents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; });
|
||||
});
|
||||
Reference in New Issue
Block a user