import { liveThreads } from "./state.ts"; import type { Registry, Thread } from "./types.ts"; export const CLEARED_CONTEXT = 'Earlier pending-request snapshots are obsolete.'; export const REFRESHING_CONTEXT = 'Earlier pending-request snapshots are obsolete while the ledger refreshes.'; const escapeXml = (text: string): string => text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">"); const formatThread = (thread: Thread): string => { const qualifier = thread.status === "blocked" || thread.status === "waiting" ? ` (${thread.status})` : ""; return `- ${escapeXml(thread.canonicalRequest)}${qualifier}`; }; export function buildContextBlock(registry: Registry): string | undefined { const threads = liveThreads(registry); if (threads.length === 0) return undefined; return ` This block supersedes earlier pending_requests snapshots. Earlier user requests that remain unfinished follow. This is context, not a request to manage tracking. The current user request wins. Continue these only when relevant or when the current request leaves room. Never mention the ledger or thread ids. ${threads.map(formatThread).join("\n")} `; } export type ContextTurnSource = "interactive" | "rpc" | "extension" | "internal"; export function nextContextRevision( source: ContextTurnSource, registry: Registry, previous: string | undefined, reconciling = false, ): string | undefined { if (source === "internal") return undefined; const desired = buildContextBlock(registry); if (reconciling) { if (previous === REFRESHING_CONTEXT) return undefined; const stale = desired !== undefined || (previous !== undefined && previous !== CLEARED_CONTEXT); return stale ? REFRESHING_CONTEXT : undefined; } const content = desired ?? (previous && previous !== CLEARED_CONTEXT ? CLEARED_CONTEXT : undefined); return content === previous ? undefined : content; }