feat: 升级至 v0.3.7 — 前后端状态同步与错误处理全量修复

核心引擎修复:
- CE-1: 上下文压缩摘要 role 从 system 改为 user,避免被 adapter 过滤
- CE-2: 工具失败时优先使用 error 字段(engine/openai-format/ollama 三处)
- P0-1: DeadLoopError 终止时正确传 error 参数,前端可见 ERROR 事件
- MT-1: 新增 waitForAbort 方法,abortSession 等待 run 结束再返回
- MT-2: TERMINATED 状态到达时标记步骤完成,避免 Trace Viewer 转圈
- MT-3: 压缩边界检测孤立 tool 消息,避免 API 400 错误
- isRetryableError 与 catch 分支统一 toLowerCase

IPC 与主进程修复:
- P0-2: createAdapter 配置缺失返回 null,FALLBACK_ADAPTER 兜底
- P0-3: reloadAdapter 失败返回 success:false 通知前端
- P1-5: 校验失败发 ERROR+DONE 流事件,防止 isStreaming 卡死
- P1-6: configLoaded 标志,配置加载前禁用发送按钮
- P1-7: MCP initialize 移到 agentLoop 后,完成后同步工具
- P2-11: beforeLoad 在 loadURL 前注册 IPC handler
- P2-12: provider 切换竞态保护

前端状态同步修复:
- clearSessions 后同步清空前端会话与消息状态
- clearMemories 通过 memoryVersion 触发 MemoryViewer 重新加载
- ContextMenu 4 个 session 操作补全 IPC 调用与 try/catch
- useConfig 配置保存失败回滚 UI 并提示
- handleToggle 工具切换失败回滚单个工具状态

错误处理全量补全:
- 所有 await window.metona 调用补全 try/catch 与 toast 反馈
- MCP addServer/toggleServer/removeServer 检查返回值
- showItemInFolder 检查返回值(handleOpen/handleOpenInFolder)
- sse-stream/ollama NDJSON 解析失败改为 log.warn
- adapter throwHttpError 带 status 属性供 isRetryableError 判断
This commit is contained in:
thzxx
2026-07-16 22:40:32 +08:00
parent 656c6b7af1
commit ceb8ee644d
28 changed files with 617 additions and 100 deletions
+47 -18
View File
@@ -38,6 +38,7 @@ import { DeepSeekAdapter } from './harness/adapters/deepseek.adapter';
import { AgnesAdapter } from './harness/adapters/agnes-ai.adapter';
import { MimoAdapter } from './harness/adapters/mimo.adapter';
import { OllamaAdapter } from './harness/adapters/ollama.adapter';
import type { IMetonaProviderAdapter } from './harness/types/metona-adapter';
import {
ReadFileTool, WriteFileTool, ListDirectoryTool, SearchFilesTool,
WebSearchTool, WebFetchTool,
@@ -140,18 +141,22 @@ async function initialize(): Promise<void> {
memoryManager.initialize();
// ===== 步骤 4: Provider Adapter 工厂 =====
const createAdapter = () => {
// P0-2 修复: 配置缺失时返回 null 而非抛异常,让应用能启动到 Onboarding
const createAdapter = (): IMetonaProviderAdapter | null => {
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') ?? '';
// 配置不完整时返回 null(不抛异常,让应用能启动到 Onboarding
if (!provider || !baseURL || !model) {
log.warn('LLM not configured. Please set provider, baseURL, and model in Settings.');
log.warn('LLM not fully configured. Please set provider, baseURL, and model in Settings.');
return null;
}
if (!apiKey && provider !== 'ollama') {
throw new Error(`API key is required for provider "${provider || 'unknown'}". Please set it in Settings.`);
log.warn(`API key is required for provider "${provider}". Please set it in Settings.`);
return null;
}
// v0.3.1: 读取 Provider 对应的 contextWindow 配置(Ollama 不使用此字段)
@@ -168,7 +173,12 @@ async function initialize(): Promise<void> {
}
};
const adapter = createAdapter();
// 配置未就绪时的 fallback adapter — getContextWindow 返回安全值,send/sendStream 会报错但 P0-1 修复后前端可见
const FALLBACK_ADAPTER = new DeepSeekAdapter({
provider: '', baseURL: '', apiKey: '', defaultModel: '', contextWindow: 4096,
});
const adapter = createAdapter() ?? FALLBACK_ADAPTER;
// ===== 步骤 5: 工作空间文件 + System Prompt =====
const contextBuilder = new ContextBuilder();
@@ -242,9 +252,7 @@ async function initialize(): Promise<void> {
// ===== MCP Manager =====
const mcpManager = new MCPManager(() => db, toolRegistry);
mcpManager.initialize().catch((err) => {
log.warn('MCP Manager initialization error:', err);
});
// P1-7 修复: initialize() 移到 agentLoop 创建之后,完成后重新同步工具
// ===== Hooks =====
// v0.2.0: ConfirmationHook 注入到 preToolHooks 管道
@@ -285,6 +293,14 @@ async function initialize(): Promise<void> {
agentLoop.setTools(toolRegistry.listTools());
agentLoop.setWorkspacePath(workspaceInfo.path);
// P1-7 修复: MCP 初始化在 agentLoop 创建后执行,完成后重新同步工具到 AgentLoop
mcpManager.initialize().then(() => {
agentLoop.setTools(toolRegistry.listTools());
log.info('[MCP] Tools registered and synced to AgentLoop');
}).catch((err) => {
log.warn('MCP Manager initialization error:', err);
});
// ===== Memory Consolidator(会话结束 AI 提取重要记忆到 MEMORY.md=====
const memoryConsolidator = new MemoryConsolidator(adapter, workspaceService);
@@ -343,6 +359,17 @@ async function initialize(): Promise<void> {
// 配置变化 — 重建 adapter
const newAdapter = createAdapter();
if (!newAdapter) {
log.warn('[CONFIG] Cannot create adapter: LLM config incomplete (provider/apiKey/baseURL/model)');
// 不更新 lastConfigSig,下次还会尝试
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('toast:show', {
type: 'warning',
message: 'LLM 配置不完整,请在设置中补全 Provider、API Key、Base URL 和 Model',
});
}
return false;
}
agentLoop.setAdapter(newAdapter);
memoryConsolidator.setAdapter(newAdapter);
// Provider 切换时同步 contextLength 和 contextWindow
@@ -399,10 +426,21 @@ async function initialize(): Promise<void> {
id: 'main',
workspacePath: workspaceInfo.path,
title: 'MetonaAI Desktop',
// P2-11 修复: 在 loadURL 之前注册 IPC handler,消除渲染进程加载与 IPC 注册的时序窗口
beforeLoad: (win) => {
confirmationHook.setMainWindow(win);
registerAllIPCHandlers(
win, sessionService, configService, workspaceService,
contextBuilder, agentLoop, toolRegistry, auditService,
sessionRecorder, memoryManager, mcpManager, reloadAdapter,
promptDefender, outputValidator,
confirmationHook, memoryConsolidator, orchestrator,
);
},
});
// v0.2.0: 为 ConfirmationHook 注入主窗口(用于向渲染进程发送确认请求)
confirmationHook.setMainWindow(mainWindow);
// P2-11 修复: ConfirmationHook.setMainWindow 已在 beforeLoad 中调用,此处无需重复
// beforeLoad 在 loadURL 之前执行,确保 IPC handler 注册时 mainWindow 已注入)
// ===== 系统托盘 =====
const resourcesPath = join(__dirname, '../../assets');
@@ -432,15 +470,6 @@ async function initialize(): Promise<void> {
);
});
// ===== 注册 IPC =====
registerAllIPCHandlers(
mainWindow, sessionService, configService, workspaceService,
contextBuilder, agentLoop, toolRegistry, auditService,
sessionRecorder, memoryManager, mcpManager, reloadAdapter,
promptDefender, outputValidator,
confirmationHook, memoryConsolidator, orchestrator,
);
// TODO: Initialize UpdateService for auto-update functionality
// const updateService = new UpdateService();
// updateService.initialize(mainWindow);