import type { Agent } from '@deepseek-ai/dsh-agent' import type { ReviewSnapshot } from './snapshot.js' import { createHash } from 'node:crypto' import { ReviewError } from './assessment.js' import type { Config } from './config.js' import { packReviewInput, type ReviewDiagnostics } from './budget.js' const bytes = (text: string) => Buffer.byteLength(text, 'utf8') /** Bounded exact authorization survives surface compaction; summaries never gain authority. */ export class ContextMemory { private instructions = new Map() private instructionBytes = 0 constructor(private readonly config: Config) {} render(snapshot: ReviewSnapshot, agent: Agent, diagnostics: ReviewDiagnostics = { phase: 'context', limitBytes: this.config.maxInputBytes, recoveries: 0, attempts: 0, }): string { diagnostics.phase = 'context' for (const entry of [...snapshot.projectInstructions, ...snapshot.history]) { if (entry.role === 'fact' || entry.role === 'checkpoint') continue const serialized = JSON.stringify(entry) const key = createHash('sha256').update(serialized).digest('hex') if (this.instructions.has(key)) continue if (this.instructionBytes + bytes(serialized) > this.config.maxInputBytes) { diagnostics.inputBytes = this.instructionBytes + bytes(serialized) diagnostics.remainingBytes = 0 throw new ReviewError('Complete retained authorization exceeds the review input budget', 'INPUT_BUDGET') } this.instructions.set(key, entry) this.instructionBytes += bytes(serialized) } const historicalFacts = snapshot.history.filter(entry => entry.role === 'fact' || entry.role === 'checkpoint') const facts: unknown[] = historicalFacts.slice(-128) for (const seq of agent.session.surface.nodes.slice(-32)) { const event = agent.session.eventAt(seq) if (event?.type === 'assistant/message') { const content = event.data.message.content.filter(block => block.type === 'text') if (content.length) facts.push({ kind: 'assistant-context', role: 'fact', content }) } else if (event?.type === 'tool/result') { facts.push({ kind: 'tool-result', role: 'fact', data: event.data }) } } const required = { environment: { cwd: snapshot.cwd }, retained_instructions: [...this.instructions.values()], pending_action: snapshot.action, fact_windows: { historicalEntries: 128, surfaceNodes: 32 }, omission_notice: 'Older facts outside the windows and any omitted facts are unknown, not evidence that an action is safe.', } return packReviewInput({ ...required, historical_facts: facts, omitted_fact_count: Math.max(0, historicalFacts.length - 128) }, [], this.config.maxInputBytes, diagnostics, true).input } get retainedBytes(): number { return this.instructionBytes } }