Files
wpide-server/src/orchestrator/memory.ts
T

44 lines
1.9 KiB
TypeScript

/**
* Server-side conversation memory. Persists each run's user goal + final
* assistant answer per session, and recalls the recent turns so a run has
* context even when the plugin sends little/no history. Independent of the
* plugin's own carry-forward.
*/
import { getDb } from '../db/pool.js';
import { newId } from '../lib/crypto.js';
import type { RunContextMessage } from './types.js';
const MAX_RECALL = 12; // turns to prepend
const MAX_CONTENT = 4000; // clip very long turns
export function saveTurn(session_id: string, role: 'user' | 'assistant', content: string, user_id?: string): void {
if (!session_id || !content) return;
getDb().prepare(
'INSERT INTO conversation_turns (id, session_id, user_id, role, content) VALUES (?,?,?,?,?)',
).run(newId(), session_id, user_id ?? null, role, content.slice(0, MAX_CONTENT));
}
export function recallTurns(session_id: string, limit = MAX_RECALL): RunContextMessage[] {
if (!session_id) return [];
const rows = getDb().prepare(
'SELECT role, content FROM conversation_turns WHERE session_id = ? ORDER BY created_at DESC LIMIT ?',
).all(session_id, limit) as { role: string; content: string }[];
return rows
.reverse()
.map((r) => ({ role: r.role === 'assistant' ? 'assistant' : 'user', content: r.content }) as RunContextMessage);
}
/**
* Merge recalled memory with the context the plugin sent, de-duplicating
* so we don't double-feed turns the plugin already included. Memory is
* prepended (older), then the plugin's context (newer).
*/
export function mergeContext(session_id: string, pluginContext: RunContextMessage[]): RunContextMessage[] {
const recalled = recallTurns(session_id);
if (recalled.length === 0) return pluginContext;
const seen = new Set(pluginContext.map((m) => `${m.role}:${m.content.slice(0, 80)}`));
const fromMemory = recalled.filter((m) => !seen.has(`${m.role}:${m.content.slice(0, 80)}`));
return [...fromMemory, ...pluginContext];
}