/** * IPC Session Handlers — 会话管理域(P2-9 从 handlers.ts 拆分) * * 会话 CRUD、消息加载/删除/清空、Trace 持久化。 * P2-11: 新增 sessions:truncateAfter(编辑重发/重新生成的消息截断)。 */ import { ipcMain } from 'electron'; import type { IPCContext } from './context'; /** M-34 修复: 统一 sessionId 校验辅助函数 */ const isValidSessionId = (id: unknown): id is string => typeof id === 'string' && id.length > 0 && id.length <= 200; export function registerSessionHandlers(ctx: IPCContext): void { const { sessionService } = ctx; ipcMain.handle('sessions:list', async () => { return sessionService.list(); }); ipcMain.handle('sessions:create', async (_event, title?: string) => { return sessionService.create(title); }); ipcMain.handle('sessions:rename', async (_event, sessionId: unknown, title: unknown) => { // M-34 修复: 校验 sessionId 和 title if (!isValidSessionId(sessionId)) return { success: false, error: 'Invalid sessionId' }; if (typeof title !== 'string' || !title.trim()) return { success: false, error: 'Invalid title' }; return { success: sessionService.rename(sessionId, title) }; }); ipcMain.handle('sessions:delete', async (_event, sessionId: unknown) => { // M-34 修复: 校验 sessionId if (!isValidSessionId(sessionId)) return { success: false, error: 'Invalid sessionId' }; return { success: sessionService.delete(sessionId) }; }); ipcMain.handle('sessions:getMessages', async (_event, sessionId: unknown) => { // M-34 修复: 校验 sessionId if (!isValidSessionId(sessionId)) return []; return sessionService.getMessages(sessionId); }); ipcMain.handle('sessions:pin', async (_event, sessionId: unknown, pinned: unknown) => { if (!isValidSessionId(sessionId)) return { success: false, error: 'Invalid sessionId' }; if (typeof pinned !== 'boolean') return { success: false, error: 'Invalid pinned flag' }; return { success: sessionService.pin(sessionId, pinned) }; }); ipcMain.handle('sessions:archive', async (_event, sessionId: unknown, archived: unknown) => { if (!isValidSessionId(sessionId)) return { success: false, error: 'Invalid sessionId' }; if (typeof archived !== 'boolean') return { success: false, error: 'Invalid archived flag' }; return { success: sessionService.archive(sessionId, archived) }; }); ipcMain.handle('sessions:deleteMessage', async (_event, messageId: unknown) => { if (typeof messageId !== 'string' || !messageId) return { success: false, error: 'Invalid messageId' }; return { success: sessionService.deleteMessage(messageId) }; }); ipcMain.handle('sessions:clearMessages', async (_event, sessionId: unknown) => { if (!isValidSessionId(sessionId)) return { success: false, error: 'Invalid sessionId' }; return { success: sessionService.clearMessages(sessionId) }; }); // P2-11: 消息截断(编辑重发 / 重新生成) ipcMain.handle('sessions:truncateAfter', async (_event, sessionId: unknown, messageId: unknown, inclusive: unknown) => { if (!isValidSessionId(sessionId)) return { success: false, error: 'Invalid sessionId' }; if (typeof messageId !== 'string' || !messageId) return { success: false, error: 'Invalid messageId' }; const isInclusive = typeof inclusive === 'boolean' ? inclusive : true; try { const truncated = sessionService.truncateMessagesAfter(sessionId, messageId, isInclusive); return { success: true, truncated }; } catch (error) { return { success: false, error: (error as Error).message }; } }); ipcMain.handle('sessions:saveTrace', async (_event, sessionId: unknown, data: unknown) => { // M-34 修复: 校验 sessionId 和 data if (!isValidSessionId(sessionId)) return { success: false, error: 'Invalid sessionId' }; // 严格校验 data 结构:traceSteps 必须为数组,tokenUsage 必须存在 if (!data || typeof data !== 'object') return { success: false, error: 'Invalid trace data' }; const traceData = data as { traceSteps?: unknown; tokenUsage?: unknown }; if (!Array.isArray(traceData.traceSteps)) { return { success: false, error: 'Invalid trace data: traceSteps must be an array' }; } if (traceData.tokenUsage === undefined) { return { success: false, error: 'Invalid trace data: tokenUsage is required' }; } try { sessionService.saveTraceData(sessionId, { traceSteps: traceData.traceSteps, tokenUsage: traceData.tokenUsage, }); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); ipcMain.handle('sessions:getTrace', async (_event, sessionId: unknown) => { if (!isValidSessionId(sessionId)) return null; return sessionService.getTraceData(sessionId); }); }