mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 12:55:51 +00:00
revert: 恢复 HISTORY_SNIP
This commit is contained in:
@@ -59,7 +59,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 和探索任务模型选择
|
||||||
|
|||||||
@@ -1003,15 +1003,6 @@ 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,77 +163,3 @@ 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)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -206,49 +206,10 @@ async function getOtlpReaders() {
|
|||||||
|
|
||||||
return exporters.map(exporter => {
|
return exporters.map(exporter => {
|
||||||
if ('export' in exporter) {
|
if ('export' in exporter) {
|
||||||
const reader = new PeriodicExportingMetricReader({
|
return 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
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user