Skip to content

Commit a817a76

Browse files
authored
Merge branch 'claude-code-best:main' into main
2 parents 0e3c232 + 7974241 commit a817a76

139 files changed

Lines changed: 3588 additions & 960 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish-npm.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ name: Publish to npm
33
on:
44
push:
55
tags:
6-
- 'v*'
6+
- "v*"
77
workflow_dispatch:
88
inputs:
99
version:
10-
description: '版本号 (例如: v1.9.0)'
10+
description: "版本号 (例如: v1.9.0)"
1111
required: true
1212
type: string
1313

CLAUDE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ bun run docs:dev
7878

7979
- **Runtime**: Bun (not Node.js). All imports, builds, and execution use Bun APIs.
8080
- **Build**: `build.ts` 执行 `Bun.build()` with `splitting: true`,入口 `src/entrypoints/cli.tsx`,输出 `dist/cli.js` + chunk files。Build 默认启用 19 个 feature(见下方 Feature Flag 段)。构建后自动替换 `import.meta.require` 为 Node.js 兼容版本(产物 bun/node 都可运行)。构建时会将 `vendor/audio-capture/``src/utils/vendor/ripgrep/` 复制到 `dist/vendor/` 下。
81-
- **Build (Vite)**: `vite.config.ts` + `scripts/post-build.ts`,chunk 输出到 `dist/chunks/`。post-build 同样复制 vendor 文件到 `dist/vendor/`
82-
- **Vendor 路径解析**: 构建后 chunk 文件位于 `dist/``dist/chunks/` 下,vendor 二进制在 `dist/vendor/``src/utils/ripgrep.ts``packages/audio-capture-napi/src/index.ts` 均通过 `import.meta.url` 路径中 `lastIndexOf('dist')` 定位 dist 根目录,再拼接 `vendor/` 子路径,确保不同构建产物层级下路径一致。
81+
- **Build (Vite)**: `vite.config.ts` + `scripts/post-build.ts`,代码分割模式,chunk 输出到 `dist/chunks/`。post-build 遍历 `dist/``dist/chunks/` 下所有 `.js` 文件做 `globalThis.Bun` 解构 patch,复制 vendor 文件到 `dist/vendor/`
82+
- **Vendor 路径解析**: 构建后 chunk 文件位于 `dist/``dist/chunks/` 下,vendor 二进制在 `dist/vendor/``src/utils/distRoot.ts` 提供共享的 `distRoot` 函数,通过 `import.meta.url` 路径中 `lastIndexOf('dist')``lastIndexOf('src')` 定位根目录。`ripgrep.ts``computerUse/setup.ts``claudeInChrome/setup.ts``updateCCB.ts` 均使用 `distRoot` 而非内联 `import.meta.url` 路径推算。`packages/audio-capture-napi/src/index.ts` 有独立的 `lastIndexOf('dist')` 逻辑,功能等价。
83+
- **为什么 Vite 必须代码分割**: Bun/JSC 会全量解析单个大 JS 文件的 bytecode 和 JIT,单文件 17MB 产物导致 RSS 暴涨至 ~1GB(Node/V8 懒解析仅需 ~220MB)。代码分割为 600+ 小 chunk 后 Bun 按需加载,`--version` RSS 从 966MB 降至 35MB,完整加载从 1GB+ 降至 ~500MB。
8384
- **Dev mode**: `scripts/dev.ts` 通过 Bun `-d` flag 注入 `MACRO.*` defines,运行 `src/entrypoints/cli.tsx`。默认启用全部 feature。
8485
- **Module system**: ESM (`"type": "module"`), TSX with `react-jsx` transform.
8586
- **Monorepo**: Bun workspaces — 17 个 workspace packages + 若干辅助目录 in `packages/` resolved via `workspace:*`

contributors.svg

Lines changed: 43 additions & 35 deletions
Loading

docs/performance-reporter.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# 内存占用 1G 调研报告
2+
3+
> 诊断 session `a3593062` RSS 达 1.09 GB,定位 Bun 运行时内存膨胀根因
4+
5+
## 数据收集
6+
7+
- **诊断数据**: RSS 1,118 MB,V8 heap 84 MB,原生内存缺口 1,034 MB(92%)
8+
- **构建方式**: `bun run build:vite` → Vite/Rollup 单文件构建,产物 17MB `dist/cli.js`
9+
- **Vite 配置**: `codeSplitting: false``vite.config.ts:97`),所有代码内联为单文件
10+
- **Node.js 对比**: 相同 17MB 产物,Node.js RSS 仅 223 MB(`--version`)/ 340 MB(完整加载)
11+
12+
## 探索与验证
13+
14+
### 已确认
15+
16+
| 问题 | 位置 | 说明 |
17+
|------|------|------|
18+
| **根因: Vite 单文件构建 + Bun 解析大文件内存效率低** | `vite.config.ts:97` | `codeSplitting: false` 产出 17MB 单文件,Bun/JSC 解析时 RSS 暴涨至 966MB |
19+
| Node.js 对同等 17MB 文件仅需 223MB | 实测 | V8 对大文件解析的内存效率远优于 JSC |
20+
| Bun.build 代码分割可解决问题 | 实测 | `bun run build`(代码分割 → 627 chunk)Bun RSS 仅 30MB(`--version`)/ 318MB(完整加载) |
21+
22+
### 已否认
23+
24+
- 不是 feature flags 数量问题 — 全部 35 features 开启时,代码分割构建内存正常
25+
- 不是内存泄漏 — `detachedContexts: 0``activeHandles: 0`
26+
- 不是原生 addon 问题 — vendor 文件仅 2.7MB
27+
- 不是 TypeScript 源码体量问题 — `bun run dev`(直接加载 TS)完整路径仅 345MB
28+
29+
## 结论
30+
31+
**根因是 Vite 构建配置 `codeSplitting: false`,产出 17MB 单文件,Bun/JSC 解析单文件大 JS 时内存效率极差(966MB vs Node 的 223MB)。**
32+
33+
实测对比矩阵:
34+
35+
| 构建方式 | 产物结构 | Bun RSS | Node RSS | Bun/Node |
36+
|----------|----------|---------|----------|----------|
37+
| `build:vite` | 17MB 单文件 | **966 MB** | 223 MB | 4.3x |
38+
| `build:vite` pipe mode | 同上 | **1,088 MB** | 340 MB | 3.2x |
39+
| `build` (Bun) | 627 chunk | 30 MB | 42 MB | 0.7x |
40+
| `build` (Bun) pipe mode | 同上 | 318 MB | 253 MB | 1.3x |
41+
| `bun run dev` TS 源码 | 动态加载 | 42 MB |||
42+
| `bun run dev` pipe mode | 动态加载 | 345 MB |||
43+
44+
核心差异:
45+
- **Node/V8** 解析 17MB 文件只需 223MB — V8 的懒解析(lazy parsing)只编译入口需要的部分
46+
- **Bun/JSC** 解析 17MB 文件需要 966MB — JSC 对单文件做全量编译,bytecode + JIT 占用大量原生内存
47+
- 代码分割后(627 个小 chunk),Bun 按需加载,内存回到正常水平
48+
49+
## 建议
50+
51+
1. **开启 Vite 代码分割** — 在 `vite.config.ts` 中启用 `codeSplitting: true` 或使用 Rollup 的 `manualChunks` 配置。这是最直接的修复
52+
2. **或切换到 Bun.build**`bun run build` 已默认启用代码分割(`splitting: true`),Bun RSS 仅 30-318MB
53+
3. **如果必须单文件** — 考虑用 Node.js 运行 Vite 产物(`node dist/cli-node.js`),代价是失去 Bun 特有 API
54+
4. **验证 `codeSplitting: false` 的存在理由** — 注释说"all dynamic imports inlined",可能是为了简化部署。评估是否真的需要单文件

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claude-code-best",
3-
"version": "2.4.4",
3+
"version": "2.6.6",
44
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
55
"type": "module",
66
"author": "claude-code-best <claude-code-best@proton.me>",

packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { SocketConnectionError } from './mcpSocketClient.js'
99
import {
1010
localPlatformLabel,
1111
type BridgePermissionRequest,
12+
toLoggerDetail,
1213
type ChromeExtensionInfo,
1314
type ClaudeForChromeContext,
1415
type PermissionMode,
@@ -578,7 +579,7 @@ export class BridgeClient implements SocketClient {
578579
const durationMs = Date.now() - this.connectionStartTime
579580
logger.error(
580581
`[${serverName}] Failed to create WebSocket after ${durationMs}ms:`,
581-
error,
582+
toLoggerDetail(error),
582583
)
583584
trackEvent?.('chrome_bridge_connection_failed', {
584585
duration_ms: durationMs,
@@ -618,7 +619,10 @@ export class BridgeClient implements SocketClient {
618619
)
619620
this.handleMessage(message)
620621
} catch (error) {
621-
logger.error(`[${serverName}] Failed to parse bridge message:`, error)
622+
logger.error(
623+
`[${serverName}] Failed to parse bridge message:`,
624+
toLoggerDetail(error),
625+
)
622626
}
623627
})
624628

@@ -862,7 +866,10 @@ export class BridgeClient implements SocketClient {
862866
const allowed = await pending.onPermissionRequest(request)
863867
this.sendPermissionResponse(requestId, allowed)
864868
} catch (error) {
865-
logger.error(`[${serverName}] Error handling permission request:`, error)
869+
logger.error(
870+
`[${serverName}] Error handling permission request:`,
871+
toLoggerDetail(error),
872+
)
866873
this.sendPermissionResponse(requestId, false)
867874
}
868875
}

packages/@ant/claude-for-chrome-mcp/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ export { localPlatformLabel } from './types.js'
88
export type {
99
BridgeConfig,
1010
ChromeExtensionInfo,
11+
ChromeBridgeTrackEventMetadata,
1112
ClaudeForChromeContext,
1213
Logger,
14+
LoggerDetail,
1315
PermissionMode,
1416
SocketClient,
1517
} from './types.js'
18+
export { toLoggerDetail } from './types.js'

packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
PermissionMode,
1010
PermissionOverrides,
1111
} from './types.js'
12+
import { toLoggerDetail } from './types.js'
1213

1314
export class SocketConnectionError extends Error {
1415
constructor(message: string) {
@@ -87,7 +88,10 @@ class McpSocketClient {
8788
await this.validateSocketSecurity(socketPath)
8889
} catch (error) {
8990
this.connecting = false
90-
logger.info(`[${serverName}] Security validation failed:`, error)
91+
logger.info(
92+
`[${serverName}] Security validation failed:`,
93+
toLoggerDetail(error),
94+
)
9195
// Don't retry on security failures (wrong perms/owner) - those won't
9296
// self-resolve. Only the error handler retries on transient errors.
9397
return
@@ -145,14 +149,20 @@ class McpSocketClient {
145149
logger.info(`[${serverName}] Received unknown message: ${message}`)
146150
}
147151
} catch (error) {
148-
logger.info(`[${serverName}] Failed to parse message:`, error)
152+
logger.info(
153+
`[${serverName}] Failed to parse message:`,
154+
toLoggerDetail(error),
155+
)
149156
}
150157
}
151158
})
152159

153160
this.socket.on('error', (error: Error & { code?: string }) => {
154161
clearTimeout(connectTimeout)
155-
logger.info(`[${serverName}] Socket error (code: ${error.code}):`, error)
162+
logger.info(
163+
`[${serverName}] Socket error (code: ${error.code}):`,
164+
toLoggerDetail(error),
165+
)
156166
this.connected = false
157167
this.connecting = false
158168

packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type {
77
PermissionOverrides,
88
SocketClient,
99
} from './types.js'
10+
import { toLoggerDetail } from './types.js'
1011

1112
export const handleToolCall = async (
1213
context: ClaudeForChromeContext,
@@ -44,7 +45,10 @@ export const handleToolCall = async (
4445

4546
return handleToolCallDisconnected(context)
4647
} catch (error) {
47-
context.logger.info(`[${context.serverName}] Error calling tool:`, error)
48+
context.logger.info(
49+
`[${context.serverName}] Error calling tool:`,
50+
toLoggerDetail(error),
51+
)
4852

4953
if (error instanceof SocketConnectionError) {
5054
return handleToolCallDisconnected(context)
@@ -165,8 +169,7 @@ async function handleToolCallConnected(
165169

166170
// Fallback for unexpected result format
167171
context.logger.warn(
168-
`[${context.serverName}] Unexpected result format from socket bridge`,
169-
response,
172+
`[${context.serverName}] Unexpected result format from socket bridge: ${JSON.stringify(response)}`,
170173
)
171174

172175
return {

packages/@ant/claude-for-chrome-mcp/src/types.ts

Lines changed: 82 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,84 @@
1+
/**
2+
* Logger 第二参数的可选类型。
3+
* 调用方通过 util.format 追加详情,实践中多为 catch 到的异常对象。
4+
*/
5+
export type LoggerDetail = Error | NodeJS.ErrnoException
6+
7+
/** 将 unknown 收窄为 LoggerDetail,供 catch 块传给 logger 使用。 */
8+
export function toLoggerDetail(detail: unknown): LoggerDetail | undefined {
9+
return detail instanceof Error ? detail : undefined
10+
}
11+
12+
/** 宿主注入的日志接口,与 DebugLogger(util.format)对齐。 */
113
export interface Logger {
2-
info: (message: string, ...args: unknown[]) => void
3-
error: (message: string, ...args: unknown[]) => void
4-
warn: (message: string, ...args: unknown[]) => void
5-
debug: (message: string, ...args: unknown[]) => void
6-
silly: (message: string, ...args: unknown[]) => void
14+
info: (message: string, detail?: LoggerDetail) => void // 信息
15+
error: (message: string, detail?: LoggerDetail) => void // 错误
16+
warn: (message: string, detail?: LoggerDetail) => void // 警告
17+
debug: (message: string, detail?: LoggerDetail) => void // 调试
18+
silly: (message: string, detail?: LoggerDetail) => void // 最细粒度调试
19+
}
20+
21+
/**
22+
* Bridge 连接失败时的 error_type 枚举。
23+
* 由 bridgeClient 在 getUserId / getOAuthToken / WebSocket 创建失败时上报。
24+
*/
25+
export type ChromeBridgeConnectionErrorType =
26+
| 'no_user_id' // 无法获取用户 UUID
27+
| 'no_oauth_token' // 无法获取 OAuth token
28+
| 'websocket_error' // WebSocket 创建或运行异常
29+
30+
/** 工具调用相关遥测元数据(started / completed / timeout / error)。 */
31+
export type ChromeBridgeToolCallMetadata = {
32+
tool_name: string // MCP 工具名
33+
tool_use_id: string // 本次调用的 UUID
34+
duration_ms?: number // 耗时(毫秒)
35+
timeout_ms?: number // 超时阈值(毫秒),仅 timeout 事件
36+
error_message?: string // 错误摘要(截断),仅 error 事件
37+
}
38+
39+
/** Bridge 连接失败遥测元数据。 */
40+
export type ChromeBridgeConnectionFailedMetadata = {
41+
duration_ms: number // 自连接开始到失败的耗时(毫秒)
42+
error_type: ChromeBridgeConnectionErrorType // 失败原因分类
43+
reconnect_attempt: number // 当前重连尝试次数
744
}
845

46+
/** Bridge 开始连接遥测元数据。 */
47+
export type ChromeBridgeConnectionStartedMetadata = {
48+
bridge_url: string // 目标 WebSocket URL(含用户路径)
49+
}
50+
51+
/** Bridge 断开连接遥测元数据。 */
52+
export type ChromeBridgeDisconnectedMetadata = {
53+
close_code: number // WebSocket 关闭码
54+
duration_since_connect_ms: number // 自连接成功到断开的时长(毫秒)
55+
reconnect_attempt: number // 即将进行的重连序号
56+
}
57+
58+
/** Bridge 连接成功遥测元数据。 */
59+
export type ChromeBridgeConnectionSucceededMetadata = {
60+
duration_ms: number // 自开始到连接就绪的耗时(毫秒)
61+
status: 'paired' | 'waiting' // paired=已配对扩展;waiting=等待扩展接入
62+
}
63+
64+
/** Bridge 重连次数耗尽遥测元数据。 */
65+
export type ChromeBridgeReconnectExhaustedMetadata = {
66+
total_attempts: number // 累计重连次数上限
67+
}
68+
69+
/**
70+
* trackEvent 回调的 metadata 联合类型。
71+
* 各变体对应 bridgeClient 内 chrome_bridge_* 事件;null 表示无附加字段。
72+
*/
73+
export type ChromeBridgeTrackEventMetadata =
74+
| ChromeBridgeToolCallMetadata
75+
| ChromeBridgeConnectionFailedMetadata
76+
| ChromeBridgeConnectionStartedMetadata
77+
| ChromeBridgeDisconnectedMetadata
78+
| ChromeBridgeConnectionSucceededMetadata
79+
| ChromeBridgeReconnectExhaustedMetadata
80+
| null // 无元数据(如 peer_connected / peer_disconnected)
81+
982
export type PermissionMode =
1083
| 'ask'
1184
| 'skip_all_permission_checks'
@@ -48,10 +121,10 @@ export interface ClaudeForChromeContext {
48121
bridgeConfig?: BridgeConfig
49122
/** If set, permission mode is sent to the extension immediately on bridge connection. */
50123
initialPermissionMode?: PermissionMode
51-
/** Optional callback to track telemetry events for bridge connections */
52-
trackEvent?: <K extends string>(
53-
eventName: K,
54-
metadata: Record<string, unknown> | null,
124+
/** Bridge 遥测回调;eventName 为 chrome_bridge_* 事件名 */
125+
trackEvent?: (
126+
eventName: string, // 事件名
127+
metadata: ChromeBridgeTrackEventMetadata, // 事件元数据
55128
) => void
56129
/** Called when user pairs with an extension via the browser pairing flow. */
57130
onExtensionPaired?: (deviceId: string, name: string) => void

0 commit comments

Comments
 (0)