/** * IPC Memory Handlers — 记忆系统域(P2-9 从 handlers.ts 拆分) */ import { ipcMain } from 'electron'; import type { IPCContext } from './context'; import type { MemoryType } from '../harness/memory/manager'; import type { MemoryMaintenanceAction } from '../harness/memory/maintainer'; import log from 'electron-log'; const VALID_MEMORY_TYPES: readonly MemoryType[] = ['episodic', 'semantic', 'working']; export function registerMemoryHandlers(ctx: IPCContext): void { const { memoryManager, sessionService, memoryMaintainer, auditService } = ctx; // ===== v0.8.1 P1-2: MEMORY.md 维护闭环(分析/应用两阶段) ===== // 阶段一:LLM 分析当前 MEMORY.md → 结构化维护建议(不改任何文件/DB) ipcMain.handle('memory:analyzeMaintenance', async () => { try { const proposal = await memoryMaintainer.analyze(); return { success: true, data: proposal }; } catch (error) { log.warn('[IPC] memory:analyzeMaintenance failed:', (error as Error).message); return { success: false, error: (error as Error).message }; } }); // 阶段二:应用用户确认(勾选)后的动作 —— 精确匹配校验 + 重写 MEMORY.md + // 同步 semantic_memories + 审计留痕 ipcMain.handle('memory:applyMaintenance', async (_event, actions: unknown) => { if (!Array.isArray(actions)) { return { success: false, error: 'Invalid actions: must be an array' }; } // 结构校验:只透传合法字段,其余拒绝 const valid: MemoryMaintenanceAction[] = []; for (const item of actions.slice(0, 30)) { if (!item || typeof item !== 'object') continue; const a = item as Record; if (a.action !== 'delete' && a.action !== 'update') continue; if (typeof a.section !== 'string' || typeof a.entry !== 'string') continue; if (a.action === 'update' && typeof a.newEntry !== 'string') continue; valid.push({ action: a.action, section: a.section, entry: a.entry, newEntry: typeof a.newEntry === 'string' ? a.newEntry : undefined, reason: typeof a.reason === 'string' ? a.reason : undefined, }); } try { const result = memoryMaintainer.apply(valid); auditService.log({ sessionId: '', eventType: 'tool_call', actor: 'user', target: 'memory_maintenance', details: { requested: valid.length, applied: result.applied, skipped: result.skipped }, outcome: 'success', }); return { success: true, data: result }; } catch (error) { auditService.log({ sessionId: '', eventType: 'error', actor: 'user', target: 'memory_maintenance', details: { error: (error as Error).message }, outcome: 'error', }); return { success: false, error: (error as Error).message }; } }); 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 }; } }); }