/ services / compact / postCompactCleanup.ts
postCompactCleanup.ts
 1  import { feature } from 'bun:bundle'
 2  import type { QuerySource } from '../../constants/querySource.js'
 3  import { clearSystemPromptSections } from '../../constants/systemPromptSections.js'
 4  import { getUserContext } from '../../context.js'
 5  import { clearSpeculativeChecks } from '../../tools/BashTool/bashPermissions.js'
 6  import { clearClassifierApprovals } from '../../utils/classifierApprovals.js'
 7  import { resetGetMemoryFilesCache } from '../../utils/claudemd.js'
 8  import { clearSessionMessagesCache } from '../../utils/sessionStorage.js'
 9  import { clearBetaTracingState } from '../../utils/telemetry/betaSessionTracing.js'
10  import { resetMicrocompactState } from './microCompact.js'
11  
12  /**
13   * Run cleanup of caches and tracking state after compaction.
14   * Call this after both auto-compact and manual /compact to free memory
15   * held by tracking structures that are invalidated by compaction.
16   *
17   * Note: We intentionally do NOT clear invoked skill content here.
18   * Skill content must survive across multiple compactions so that
19   * createSkillAttachmentIfNeeded() can include the full skill text
20   * in subsequent compaction attachments.
21   *
22   * querySource: pass the compacting query's source so we can skip
23   * resets that would clobber main-thread module-level state. Subagents
24   * (agent:*) run in the same process and share module-level state
25   * (context-collapse store, getMemoryFiles one-shot hook flag,
26   * getUserContext cache); resetting those when a SUBAGENT compacts
27   * would corrupt the MAIN thread's state. All compaction callers should
28   * pass querySource — undefined is only safe for callers that are
29   * genuinely main-thread-only (/compact, /clear).
30   */
31  export function runPostCompactCleanup(querySource?: QuerySource): void {
32    // Subagents (agent:*) run in the same process and share module-level
33    // state with the main thread. Only reset main-thread module-level state
34    // (context-collapse, memory file cache) for main-thread compacts.
35    // Same startsWith pattern as isMainThread (index.ts:188).
36    const isMainThreadCompact =
37      querySource === undefined ||
38      querySource.startsWith('repl_main_thread') ||
39      querySource === 'sdk'
40  
41    resetMicrocompactState()
42    if (feature('CONTEXT_COLLAPSE')) {
43      if (isMainThreadCompact) {
44        /* eslint-disable @typescript-eslint/no-require-imports */
45        ;(
46          require('../contextCollapse/index.js') as typeof import('../contextCollapse/index.js')
47        ).resetContextCollapse()
48        /* eslint-enable @typescript-eslint/no-require-imports */
49      }
50    }
51    if (isMainThreadCompact) {
52      // getUserContext is a memoized outer layer wrapping getClaudeMds() →
53      // getMemoryFiles(). If only the inner getMemoryFiles cache is cleared,
54      // the next turn hits the getUserContext cache and never reaches
55      // getMemoryFiles(), so the armed InstructionsLoaded hook never fires.
56      // Manual /compact already clears this explicitly at its call sites;
57      // auto-compact and reactive-compact did not — this centralizes the
58      // clear so all compaction paths behave consistently.
59      getUserContext.cache.clear?.()
60      resetGetMemoryFilesCache('compact')
61    }
62    clearSystemPromptSections()
63    clearClassifierApprovals()
64    clearSpeculativeChecks()
65    // Intentionally NOT calling resetSentSkillNames(): re-injecting the full
66    // skill_listing (~4K tokens) post-compact is pure cache_creation. The
67    // model still has SkillTool in schema, invoked_skills preserves used
68    // skills, and dynamic additions are handled by skillChangeDetector /
69    // cacheUtils resets. See compactConversation() for full rationale.
70    clearBetaTracingState()
71    if (feature('COMMIT_ATTRIBUTION')) {
72      void import('../../utils/attributionHooks.js').then(m =>
73        m.sweepFileContentCache(),
74      )
75    }
76    clearSessionMessagesCache()
77  }