perf: 优化内存与遥测管理,启用 Vite minify

- 禁用 HISTORY_SNIP feature flag 并新增 proactiveTruncate 防止无 compact_boundary 时内存无限增长
- 跳过未启用 telemetry 时的 OTel 初始化,防止长会话 PerformanceMeasure 堆积
- OTel 导出遇 401/403 自动关闭 reader,防止 handle 泄漏
- Vite 构建启用 minify

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
claude-code-best
2026-05-04 23:23:25 +08:00
parent 5c1be19511
commit b28de717dd
6 changed files with 135 additions and 3 deletions

View File

@@ -49,7 +49,7 @@ export const DEFAULT_BUILD_FEATURES = [
'DAEMON', // 守护进程模式,长驻 supervisor 管理后台 worker非 GB 级主因) 'DAEMON', // 守护进程模式,长驻 supervisor 管理后台 worker非 GB 级主因)
'ACP', // ACP 代理协议,支持外部 agent 接入 'ACP', // ACP 代理协议,支持外部 agent 接入
'WORKFLOW_SCRIPTS', // 工作流脚本(.claude/workflows/ 中的 YAML/MD 'WORKFLOW_SCRIPTS', // 工作流脚本(.claude/workflows/ 中的 YAML/MD
'HISTORY_SNIP', // 历史消息裁剪,压缩上下文窗口 // 'HISTORY_SNIP', // 历史消息裁剪,压缩上下文窗口
// 'CONTEXT_COLLAPSE', // 已禁用:实现是空壳 stub启用后会抑制 auto compact 导致上下文管理完全失效 // 'CONTEXT_COLLAPSE', // 已禁用:实现是空壳 stub启用后会抑制 auto compact 导致上下文管理完全失效
'MONITOR_TOOL', // Monitor 工具,流式监控后台进程输出 'MONITOR_TOOL', // Monitor 工具,流式监控后台进程输出
// 'FORK_SUBAGENT', // 已禁用:显式 `fork: true` 参数触发 fork 路径(继承父级上下文和模型),不影响 forceAsync 和探索任务模型选择 // 'FORK_SUBAGENT', // 已禁用:显式 `fork: true` 参数触发 fork 路径(继承父级上下文和模型),不影响 forceAsync 和探索任务模型选择

View File

@@ -1003,6 +1003,15 @@ export class QueryEngine {
uuid: msg.uuid, uuid: msg.uuid,
} }
} }
// Proactive truncation: prevent unbounded growth when API doesn't
// return compact_boundary (e.g. third-party compat layers).
if (feature('HISTORY_SNIP') && snipModule) {
const truncated = snipModule.proactiveTruncate(this.mutableMessages)
if (truncated !== this.mutableMessages) {
this.mutableMessages.length = 0
this.mutableMessages.push(...truncated)
}
}
// Don't yield other system messages in headless mode // Don't yield other system messages in headless mode
break break
} }

View File

@@ -320,6 +320,16 @@ async function doInitializeTelemetry(): Promise<void> {
return return
} }
// Skip entire OTel initialization when telemetry is not enabled.
// Prevents PerformanceMeasure accumulation in long-running sessions.
if (!isEnvTruthy(process.env.CLAUDE_CODE_ENABLE_TELEMETRY)) {
telemetryInitialized = true
logForDebugging(
'[3P telemetry] Skipped — CLAUDE_CODE_ENABLE_TELEMETRY not set',
)
return
}
// Set flag before init to prevent double initialization // Set flag before init to prevent double initialization
telemetryInitialized = true telemetryInitialized = true
try { try {

View File

@@ -163,3 +163,77 @@ export function isSnipRuntimeEnabled(): boolean {
export function shouldNudgeForSnips(messages: Message[]): boolean { export function shouldNudgeForSnips(messages: Message[]): boolean {
return messages.length >= SNIP_NUDGE_THRESHOLD return messages.length >= SNIP_NUDGE_THRESHOLD
} }
/**
* Maximum total character length of message content before proactive
* truncation kicks in. ~150 MB of string data corresponds to roughly
* 1.5x the default 200k-token context window at 4 chars/token — well
* beyond what any model can actually use in a single request.
*/
const PROACTIVE_TRUNCATE_CHARS = 150_000_000
/**
* Minimum number of messages to keep when falling back to tail-only
* retention (i.e. when no compact_boundary exists in the array).
*/
const PROACTIVE_TRUNCATE_MIN_TAIL = 50
/**
* Proactively truncate old messages when the in-memory store grows too
* large. Unlike `snipCompactIfNeeded` (which waits for a snip_boundary
* from the API), this runs client-side after every push — ensuring
* unbounded growth cannot happen even when the API never returns a
* compact_boundary (e.g. third-party compat layers).
*
* Strategy:
* 1. If a `compact_boundary` exists, keep it and everything after it.
* 2. Otherwise, keep only the last `PROACTIVE_TRUNCATE_MIN_TAIL` messages.
*
* Returns the same array reference when no truncation is needed.
*/
export function proactiveTruncate(messages: Message[]): Message[] {
if (messages.length < PROACTIVE_TRUNCATE_MIN_TAIL) return messages
let totalChars = 0
for (const msg of messages) {
const content = msg.message?.content
if (typeof content === 'string') {
totalChars += content.length
} else if (Array.isArray(content)) {
for (const block of content) {
if (typeof block === 'string') {
totalChars += (block as string).length
} else if (block && typeof block === 'object') {
const obj = block as unknown as Record<string, unknown>
const text = obj.text ?? obj.content
if (typeof text === 'string') {
totalChars += text.length
}
}
}
}
}
if (totalChars < PROACTIVE_TRUNCATE_CHARS) return messages
// Find last compact_boundary — the standard anchor point
let boundaryIdx = -1
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i]!
if (
msg.type === 'system' &&
(msg as Record<string, unknown>).subtype === 'compact_boundary'
) {
boundaryIdx = i
break
}
}
const keepFrom =
boundaryIdx >= 0
? boundaryIdx
: Math.max(0, messages.length - PROACTIVE_TRUNCATE_MIN_TAIL)
if (keepFrom === 0) return messages
return messages.slice(keepFrom)
}

View File

@@ -206,10 +206,49 @@ async function getOtlpReaders() {
return exporters.map(exporter => { return exporters.map(exporter => {
if ('export' in exporter) { if ('export' in exporter) {
return new PeriodicExportingMetricReader({ const reader = new PeriodicExportingMetricReader({
exporter, exporter,
exportIntervalMillis: exportInterval, exportIntervalMillis: exportInterval,
}) })
// Wrap the export callback to auto-shutdown the reader on auth
// failures (401/403). Without this the PeriodicExportingMetricReader's
// internal setInterval keeps retrying forever, leaking handles.
const originalExport = (
exporter as unknown as {
export: (
metrics: unknown,
callback: (result: { error?: Error }) => void,
) => unknown
}
).export.bind(exporter)
;(
exporter as unknown as {
export: (
metrics: unknown,
callback: (result: { error?: Error }) => void,
) => unknown
}
).export = (metrics, callback) => {
return originalExport(metrics, result => {
if (result.error) {
const msg = result.error.message || ''
if (
msg.includes('401') ||
msg.includes('403') ||
msg.includes('Unauthorized') ||
msg.includes('authentication')
) {
logForDebugging(
`[3P telemetry] Auth error detected, shutting down metric reader`,
{ level: 'error' },
)
void reader.shutdown()
}
}
callback(result)
})
}
return reader
} }
return exporter return exporter
}) })

View File

@@ -83,7 +83,7 @@ export default defineConfig({
target: 'es2020', target: 'es2020',
copyPublicDir: false, copyPublicDir: false,
sourcemap: false, sourcemap: false,
minify: false, minify: true,
// SSR build mode — uses Rollup with Node.js target // SSR build mode — uses Rollup with Node.js target
ssr: true, ssr: true,