Compare commits

...

4 Commits

Author SHA1 Message Date
claude-code-best
5e215bb061 chore: v2.0.4 2026-05-04 23:25:20 +08:00
claude-code-best
b28de717dd 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>
2026-05-04 23:23:25 +08:00
claude-code-best
5c1be19511 docs: update contributors 2026-05-04 08:44:14 +00:00
claude-code-best
2545dcabfd fix: ccb update 使用 bun install -g @latest 替代 bun update -g
bun update -g 只更新到 package.json 版本范围内的最新版,无法跨版本升级。
改为 bun install -g @latest 与 npm 侧行为一致,强制拉取最新发布版。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 19:28:16 +08:00
9 changed files with 180 additions and 38 deletions

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -1,6 +1,6 @@
{
"name": "claude-code-best",
"version": "2.0.3",
"version": "2.0.4",
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
"type": "module",
"author": "claude-code-best <claude-code-best@proton.me>",

View File

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

View File

@@ -1003,6 +1003,15 @@ export class QueryEngine {
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
break
}

View File

@@ -129,7 +129,7 @@ export async function updateCCB(): Promise<void> {
try {
if (pkgManager === 'bun') {
execSync(`bun update -g ${PACKAGE_NAME}`, {
execSync(`bun install -g ${PACKAGE_NAME}@latest`, {
stdio: 'inherit',
cwd: homedir(),
timeout: 120_000,
@@ -153,7 +153,9 @@ export async function updateCCB(): Promise<void> {
process.stderr.write('\n')
process.stderr.write('Try manually updating with:\n')
if (pkgManager === 'bun') {
process.stderr.write(chalk.bold(` bun update -g ${PACKAGE_NAME}`) + '\n')
process.stderr.write(
chalk.bold(` bun install -g ${PACKAGE_NAME}@latest`) + '\n',
)
} else {
process.stderr.write(
chalk.bold(` npm install -g ${PACKAGE_NAME}@latest`) + '\n',

View File

@@ -320,6 +320,16 @@ async function doInitializeTelemetry(): Promise<void> {
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
telemetryInitialized = true
try {

View File

@@ -163,3 +163,77 @@ export function isSnipRuntimeEnabled(): boolean {
export function shouldNudgeForSnips(messages: Message[]): boolean {
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 => {
if ('export' in exporter) {
return new PeriodicExportingMetricReader({
const reader = new PeriodicExportingMetricReader({
exporter,
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
})

View File

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