/** * IPC Memory Handlers — 记忆系统域(P2-9 从 handlers.ts 拆分) */ import { ipcMain } from 'electron'; import type { IPCContext } from './context'; import type { MemoryType } from '../harness/memory/manager'; const VALID_MEMORY_TYPES: readonly MemoryType[] = ['episodic', 'semantic', 'working']; export function registerMemoryHandlers(ctx: IPCContext): void { const { memoryManager, sessionService } = ctx; ipcMain.handle('db:searchMemories', async (_event, query: unknown, options: unknown) => { // M-37 修复: query 和 options 校验 if (typeof query !== 'string' || !query.trim()) { return []; } // 构造合法的搜索选项(仅保留已知字段,强制类型安全) const searchOptions: { topK?: number; sessionId?: string; type?: MemoryType; minImportance?: number } = {}; if (options && typeof options === 'object') { const opts = options as Record; // topK 限制范围 1-100(防止过大查询) if (opts.topK !== undefined) { const topK = Number(opts.topK); if (Number.isFinite(topK) && topK >= 1 && topK <= 100) { searchOptions.topK = topK; } else { searchOptions.topK = 10; // 默认值 } } if (typeof opts.sessionId === 'string') searchOptions.sessionId = opts.sessionId; // type 必须是合法的 MemoryType 枚举值 if (typeof opts.type === 'string' && (VALID_MEMORY_TYPES as readonly string[]).includes(opts.type)) { searchOptions.type = opts.type as MemoryType; } if (typeof opts.minImportance === 'number' && Number.isFinite(opts.minImportance)) { searchOptions.minImportance = opts.minImportance; } } return memoryManager.search(query, searchOptions); }); // ===== v0.2.0: 记忆系统增强查询(Memory Viewer UI) ===== const VALID_MEMORY_TYPES_LIST: readonly string[] = ['episodic', 'semantic', 'working']; ipcMain.handle('memory:listAll', async (_event, options?: unknown) => { // M-49 修复: 校验 options.type 枚举和 limit 范围 let limit = 100; let type: string | undefined; if (options && typeof options === 'object') { const opts = options as Record; if (opts.type !== undefined) { if (typeof opts.type !== 'string' || !VALID_MEMORY_TYPES_LIST.includes(opts.type)) { return { success: false, error: `Invalid type (must be one of: ${VALID_MEMORY_TYPES_LIST.join(', ')})` }; } type = opts.type; } if (opts.limit !== undefined) { const num = Number(opts.limit); // 限制 1-1000 范围,SQLite 中 LIMIT -1 表示无限制,需阻止 if (!Number.isFinite(num) || num < 1 || num > 1000) { return { success: false, error: 'Invalid limit (must be 1-1000)' }; } limit = Math.floor(num); } } const db = sessionService.getDB(); const results: Record = {}; try { if (!type || type === 'episodic') { const rows = db.prepare('SELECT * FROM episodic_memories ORDER BY created_at DESC LIMIT ?').all(limit) as Array>; results.episodic = rows.map((r) => ({ ...r, type: 'episodic', content: r.content ?? '' })); } if (!type || type === 'semantic') { const rows = db.prepare('SELECT * FROM semantic_memories ORDER BY updated_at DESC LIMIT ?').all(limit) as Array>; results.semantic = rows.map((r) => ({ ...r, type: 'semantic', content: r.value ?? r.key ?? '', importance: r.confidence ?? 0, created_at: r.created_at ?? r.updated_at })); } if (!type || type === 'working') { const rows = db.prepare('SELECT * FROM working_memories ORDER BY updated_at DESC LIMIT ?').all(limit) as Array>; results.working = rows.map((r) => ({ ...r, type: 'working', content: r.value ?? r.key ?? '', importance: 0.5, created_at: r.updated_at ?? Date.now() })); } return { success: true, data: results }; } catch (error) { return { success: false, error: (error as Error).message }; } }); ipcMain.handle('memory:delete', async (_event, type: unknown, id: unknown) => { // M-50 修复: 校验 type 枚举(防止三元表达式默认映射到 working_memories)和 id 类型 if (typeof type !== 'string' || !VALID_MEMORY_TYPES_LIST.includes(type)) { return { success: false, error: `Invalid type (must be one of: ${VALID_MEMORY_TYPES_LIST.join(', ')})` }; } if (typeof id !== 'string' || !id) { return { success: false, error: 'Invalid memory id' }; } const db = sessionService.getDB(); try { const table = type === 'episodic' ? 'episodic_memories' : type === 'semantic' ? 'semantic_memories' : 'working_memories'; db.prepare(`DELETE FROM ${table} WHERE id = ?`).run(id); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); }