mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
- compact 时清理 contentReplacementState(seenIds/replacements) - logError() 使用 shortErrorStack 替代完整 err.stack,减少 GC 压力 - permissionDenials 每次 submitMessage 清空,防止无限增长 - SSE 缓冲区添加 1MB 上限,防止畸形数据无限累积 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
110 lines
5.0 KiB
TypeScript
110 lines
5.0 KiB
TypeScript
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 '@claude-code-best/builtin-tools/tools/BashTool/bashPermissions.js'
|
|
import { clearClassifierApprovals } from '../../utils/classifierApprovals.js'
|
|
import { resetGetMemoryFilesCache } from '../../utils/claudemd.js'
|
|
import { logError } from '../../utils/log.js'
|
|
import { clearSessionMessagesCache } from '../../utils/sessionStorage.js'
|
|
import { clearBetaTracingState } from '../../utils/telemetry/betaSessionTracing.js'
|
|
import { resetMicrocompactState } from './microCompact.js'
|
|
|
|
/**
|
|
* Compact-scoped cleanup callbacks registered by REPL or other long-lived
|
|
* components. Called during runPostCompactCleanup() so instance-scoped state
|
|
* (e.g. contentReplacementState) is freed alongside module-level caches.
|
|
*/
|
|
const compactCleanupCallbacks: Array<() => void> = []
|
|
|
|
export function registerCompactCleanup(callback: () => void): void {
|
|
compactCleanupCallbacks.push(callback)
|
|
}
|
|
|
|
/**
|
|
* 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')) {
|
|
// Intentionally fire-and-forget: the file-content cache sweep is a
|
|
// best-effort memory release whose completion no caller depends on.
|
|
// Keeping `runPostCompactCleanup` synchronous lets compaction call sites
|
|
// (REPL post-compact handler, /compact command, autoCompact) finish their
|
|
// own state transitions without an extra microtask round-trip — the sweep
|
|
// catches up on the next event-loop tick.
|
|
//
|
|
// The .catch is required even though the current attributionHooks.ts is a
|
|
// no-op stub: without it, a future restored sweepFileContentCache that
|
|
// throws would surface as an unhandled promise rejection from a function
|
|
// whose synchronous signature gives callers no way to observe it.
|
|
void import('../../utils/attributionHooks.js')
|
|
.then(m => m.sweepFileContentCache())
|
|
.catch(error => {
|
|
logError(error)
|
|
})
|
|
}
|
|
clearSessionMessagesCache()
|
|
for (const cb of compactCleanupCallbacks) {
|
|
try {
|
|
cb()
|
|
} catch (error) {
|
|
logError(error)
|
|
}
|
|
}
|
|
}
|