// turn-tracker.ts — per-session monotonic turn counter for KB fade. // // The openclaw `before_prompt_build` hook does not expose currentTurn // to plugins (verified against the SDK hook-types: PluginHookAgentContext // has agentId/sessionId/workspaceDir/... but no turn). To make fade // effective, we maintain a small Map that increments // once per before_prompt_build fire. The `dynamic-kb-cache` tool reads // the same counter so each new Entry's `last_refresh_at_turn` is // consistent with the hook's view of the clock. // // Restart safety: in-memory counters reset to 0 on plugin reload, but // `seedFromBlock` is called the first time we touch a given session // during a hook; it scans the loaded block's entries and seeds the // counter to max(last_refresh_at_turn). The next bump then yields a // strictly-greater turn, so existing entries keep their accumulated // fade distance instead of regressing to d=0. // // Process-local only. The Plexum side keeps the same semantics in Go // (it has currentTurn from the host). import type { Block } from './kbblock.js'; const counters = new Map(); /** * Return the latest turn assigned to this session, or 0 if untouched. * `dynamic-kb-cache` uses this for `at_turn` on Block.add so a fact * cached during turn N reads back at d=0 on turn N+1. */ export function currentTurnForSession(sessionId: string): number { if (!sessionId) return 0; return counters.get(sessionId) ?? 0; } /** * Increment the counter for this session and return the new value. * If the counter is unset (first hook after plugin start), seed it to * max(entry.last_refresh_at_turn) so restart doesn't regress fade. * * Pass the loaded block so seeding doesn't need a second disk read. */ export function bumpTurnForSession(sessionId: string, block: Block): number { if (!sessionId) return 0; let cur = counters.get(sessionId); if (cur === undefined) { cur = 0; for (const e of block.entries) { if (e.last_refresh_at_turn > cur) cur = e.last_refresh_at_turn; } } const next = cur + 1; counters.set(sessionId, next); return next; } /** * Test hook. Forget all in-memory counters. Not exported through any * stable contract — for unit tests only. */ export function _resetForTesting(): void { counters.clear(); }