Files
claude-code/src/services/compact/reactiveCompact.ts
claude-code-best 198c09b263 fix: 内存优化 — 预测性 compact 阈值、增量 lookups orphaned 修复、deferred slice 引用优化
- P0: REPL.tsx 用 useMemo 包裹 deferred messages slice,避免每次渲染创建新数组引用导致不必要的后台重渲染
- P1: 预测性 compact 阈值改用 effectiveContextWindow - growth,消除与 autocompact buffer 的双重预留;TOOL_RESULT_GROWTH_ESTIMATE 从 20K 降至 15K
- P2: 增量 lookups 增加 lastAssistantMsgId 一致性检查和 orphaned server_tool_use/mcp_tool_use 扫描,防止 UI 永久 loading
- P3: reactiveCompact 类型断言改为直接使用 'compact' 字面量
- docs: CLAUDE.md 统一使用 precheck 替代分散的 typecheck/lint/test 命令

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-02 20:32:00 +08:00

98 lines
2.8 KiB
TypeScript

import { isEnvTruthy } from '../../utils/envUtils.js'
import {
isMediaSizeErrorMessage,
isPromptTooLongMessage,
} from '../api/errors.js'
import type { AssistantMessage, Message } from '../../types/message.js'
import { type CompactionResult, compactConversation } from './compact.js'
import { logError } from '../../utils/log.js'
import { logForDebugging } from '../../utils/debug.js'
import type { CacheSafeParams } from '../../utils/forkedAgent.js'
export const isReactiveOnlyMode: () => boolean = () => false
export const reactiveCompactOnPromptTooLong: (
messages: Message[],
cacheSafeParams: Record<string, unknown>,
options: { customInstructions?: string; trigger?: string },
) => Promise<{ ok: boolean; reason?: string; result?: CompactionResult }> =
async (messages, cacheSafeParams, options) => {
const params = cacheSafeParams as unknown as CacheSafeParams
try {
const result = await compactConversation(
messages,
params.toolUseContext,
params,
true,
options.customInstructions,
true,
{
isRecompactionInChain: false,
turnsSincePreviousCompact: 0,
autoCompactThreshold: 0,
querySource: 'compact',
},
)
return { ok: true, result }
} catch (error) {
logError(error)
return { ok: false, reason: String(error) }
}
}
export const isReactiveCompactEnabled: () => boolean = () => {
if (isEnvTruthy(process.env.DISABLE_COMPACT)) return false
return true
}
export const isWithheldPromptTooLong: (message: Message) => boolean =
message => {
if (message.type !== 'assistant' || !message.isApiErrorMessage) return false
return isPromptTooLongMessage(message as AssistantMessage)
}
export const isWithheldMediaSizeError: (message: Message) => boolean =
message => {
if (message.type !== 'assistant' || !message.isApiErrorMessage) return false
return isMediaSizeErrorMessage(message as AssistantMessage)
}
export const tryReactiveCompact: (params: {
hasAttempted: boolean
querySource: string
aborted: boolean
messages: Message[]
cacheSafeParams: Record<string, unknown>
}) => Promise<CompactionResult | null> = async ({
hasAttempted,
aborted,
messages,
cacheSafeParams,
}) => {
if (hasAttempted || aborted) return null
const params = cacheSafeParams as unknown as CacheSafeParams
try {
const result = await compactConversation(
messages,
params.toolUseContext,
params,
true,
undefined,
true,
{
isRecompactionInChain: false,
turnsSincePreviousCompact: 0,
autoCompactThreshold: 0,
},
)
return result
} catch (error) {
logForDebugging(
`reactiveCompact: emergency compaction failed — ${String(error)}`,
{ level: 'warn' },
)
logError(error)
return null
}
}