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;
}