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
+24 -1
View File
@@ -59,12 +59,31 @@ export function registerAllIPCHandlers(
ipcMain.handle('agent:sendMessage', async (_event, userMessage: MetonaMessage, sessionId: string) => {
// M-33 修复: 参数校验,防止 undefined/非字符串导致下游异常
// P1-5 修复: 校验失败时也发 ERROR+DONE 流事件,防止 isStreaming 永久卡死
if (!sessionId || typeof sessionId !== 'string') {
log.warn('[AGENT] sendMessage rejected: invalid sessionId');
if (!mainWindow.isDestroyed()) {
const errorEvent: MetonaStreamEvent = {
type: MetonaStreamEventType.ERROR,
requestId: '', sessionId: sessionId ?? '', iteration: 0, seq: 0, timestamp: Date.now(),
error: { code: MetonaErrorCode.UNKNOWN, message: '无效的会话 ID', retryable: false },
};
mainWindow.webContents.send('agent:streamEvent', errorEvent);
mainWindow.webContents.send('agent:streamEvent', { ...errorEvent, type: MetonaStreamEventType.DONE });
}
return { success: false, error: 'Invalid sessionId' };
}
if (!userMessage || typeof userMessage !== 'object' || typeof userMessage.content !== 'string') {
log.warn('[AGENT] sendMessage rejected: invalid userMessage');
if (!mainWindow.isDestroyed()) {
const errorEvent: MetonaStreamEvent = {
type: MetonaStreamEventType.ERROR,
requestId: '', sessionId, iteration: 0, seq: 0, timestamp: Date.now(),
error: { code: MetonaErrorCode.UNKNOWN, message: '无效的消息格式', retryable: false },
};
mainWindow.webContents.send('agent:streamEvent', errorEvent);
mainWindow.webContents.send('agent:streamEvent', { ...errorEvent, type: MetonaStreamEventType.DONE });
}
return { success: false, error: 'Invalid message format' };
}
log.info('[AGENT] sendMessage:', sessionId, (userMessage.content ?? '').slice(0, 80));
@@ -404,6 +423,9 @@ export function registerAllIPCHandlers(
ipcMain.handle('agent:abortSession', async (_event, sessionId) => {
log.info('[AGENT] Abort:', sessionId);
agentLoop.abort();
// MT-1 修复: 等待当前 run 完全结束再返回,防止用户立即重发时新消息卡在
// runStream 的 currentRunPromise 等待中(最长卡 120s 工具超时)
await agentLoop.waitForAbort();
// v0.3.0 修复: 清理所有等待中的工具确认,避免定时器泄漏和超时 toast 在新会话中弹出
confirmationHook.clearPending();
@@ -670,10 +692,11 @@ export function registerAllIPCHandlers(
// v0.3.1: 加入 deepseek.contextWindow / agnes.contextWindow / mimo.contextWindow,使上下文窗口配置变化也触发热重载
if (['llm.provider', 'llm.model', 'llm.apiKey', 'llm.baseURL', 'ollama.numCtx',
'deepseek.contextWindow', 'agnes.contextWindow', 'mimo.contextWindow'].includes(key)) {
// C-1 修复: reloadAdapter 返回 false 时表示加载失败,需要通知前端
// P0-3 修复: reloadAdapter 失败时返回 success:false,让前端知道配置不完整
const reloadSuccess = reloadAdapter();
if (!reloadSuccess) {
log.warn(`[CONFIG] Adapter reload failed after ${key} change`);
return { success: false, error: 'LLM 配置不完整,请检查 Provider、API Key、Base URL 和 Model 是否都已填写' };
} else {
log.info(`[CONFIG] LLM config changed (${key}), adapter reloaded`);
}