import { feature } from 'bun:bundle' import type { QuerySource } from '../../constants/querySource.js' import { clearSystemPromptSections } from '../../constants/systemPromptSections.js' import { getUserContext } from '../../context.js' import { clearSpeculativeChecks } from '../../tools/BashTool/bashPermissions.js' import { clearClassifierApprovals } from '../../utils/classifierApprovals.js' import { resetGetMemoryFilesCache } from '../../utils/claudemd.js' import { clearSessionMessagesCache } from '../../utils/sessionStorage.js' import { clearBetaTracingState } from '../../utils/telemetry/betaSessionTracing.js' import { resetMicrocompactState } from './microCompact.js' /** * Run cleanup of caches and tracking state after compaction. * Call this after both auto-compact and manual /compact to free memory * held by tracking structures that are invalidated by compaction. * * Note: We intentionally do NOT clear invoked skill content here. * Skill content must survive across multiple compactions so that * createSkillAttachmentIfNeeded() can include the full skill text * in subsequent compaction attachments. * * querySource: pass the compacting query's source so we can skip * resets that would clobber main-thread module-level state. Subagents * (agent:*) run in the same process and share module-level state * (context-collapse store, getMemoryFiles one-shot hook flag, * getUserContext cache); resetting those when a SUBAGENT compacts * would corrupt the MAIN thread's state. All compaction callers should * pass querySource — undefined is only safe for callers that are * genuinely main-thread-only (/compact, /clear). */ export function runPostCompactCleanup(querySource?: QuerySource): void { // Subagents (agent:*) run in the same process and share module-level // state with the main thread. Only reset main-thread module-level state // (context-collapse, memory file cache) for main-thread compacts. // Same startsWith pattern as isMainThread (index.ts:188). const isMainThreadCompact = querySource === undefined || querySource.startsWith('repl_main_thread') || querySource === 'sdk' resetMicrocompactState() if (feature('CONTEXT_COLLAPSE')) { if (isMainThreadCompact) { /* eslint-disable @typescript-eslint/no-require-imports */ ;( require('../contextCollapse/index.js') as typeof import('../contextCollapse/index.js') ).resetContextCollapse() /* eslint-enable @typescript-eslint/no-require-imports */ } } if (isMainThreadCompact) { // getUserContext is a memoized outer layer wrapping getClaudeMds() → // getMemoryFiles(). If only the inner getMemoryFiles cache is cleared, // the next turn hits the getUserContext cache and never reaches // getMemoryFiles(), so the armed InstructionsLoaded hook never fires. // Manual /compact already clears this explicitly at its call sites; // auto-compact and reactive-compact did not — this centralizes the // clear so all compaction paths behave consistently. getUserContext.cache.clear?.() resetGetMemoryFilesCache('compact') } clearSystemPromptSections() clearClassifierApprovals() clearSpeculativeChecks() // Intentionally NOT calling resetSentSkillNames(): re-injecting the full // skill_listing (~4K tokens) post-compact is pure cache_creation. The // model still has SkillTool in schema, invoked_skills preserves used // skills, and dynamic additions are handled by skillChangeDetector / // cacheUtils resets. See compactConversation() for full rationale. clearBetaTracingState() if (feature('COMMIT_ATTRIBUTION')) { void import('../../utils/attributionHooks.js').then(m => m.sweepFileContentCache(), ) } clearSessionMessagesCache() }