From 144c4f551c19844ce5385edfa17f28ecc758fa66 Mon Sep 17 00:00:00 2001 From: James Feng <47167674+GhostDragon124@users.noreply.github.com> Date: Mon, 1 Jun 2026 23:55:47 +0800 Subject: [PATCH] fix: comment out ACP/performanceShim imports for CC_Pure build compatibility --- src/entrypoints/cli.tsx | 17 ++-- src/services/acp/entry.ts | 74 ++++++++++++++++ src/utils/performanceShim.ts | 165 +++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 src/services/acp/entry.ts create mode 100644 src/utils/performanceShim.ts diff --git a/src/entrypoints/cli.tsx b/src/entrypoints/cli.tsx index 7358e7ed2..b1e6729fd 100644 --- a/src/entrypoints/cli.tsx +++ b/src/entrypoints/cli.tsx @@ -2,7 +2,8 @@ // Performance shim MUST be the first import — it replaces globalThis.performance // with a JS-backed implementation before React/OTel capture the native reference. // Without this, JSC's C++ Vector grows without bound in long-running sessions. -import '../utils/performanceShim.js'; +// CC_Pure: performanceShim and ACP entry not needed +// import '../utils/performanceShim.js'; import { feature } from 'bun:bundle'; import { isEnvTruthy } from '../utils/envUtils.js'; @@ -120,13 +121,13 @@ async function main(): Promise { return; } - // Fast-path for `--acp` — ACP (Agent Client Protocol) agent mode over stdio. - if (feature('ACP') && process.argv[2] === '--acp') { - profileCheckpoint('cli_acp_path'); - const { runAcpAgent } = await import('../services/acp/entry.js'); - await runAcpAgent(); - return; - } + // CC_Pure: ACP feature not included — commented out + // if (feature('ACP') && process.argv[2] === '--acp') { + // profileCheckpoint('cli_acp_path'); + // const { runAcpAgent } = await import('../services/acp/entry.js'); + // await runAcpAgent(); + // return; + // } if (args[0] === 'weixin') { profileCheckpoint('cli_weixin_path'); diff --git a/src/services/acp/entry.ts b/src/services/acp/entry.ts new file mode 100644 index 000000000..27bbe8134 --- /dev/null +++ b/src/services/acp/entry.ts @@ -0,0 +1,74 @@ +import { AgentSideConnection, ndJsonStream } from '@agentclientprotocol/sdk' +import type { Stream } from '@agentclientprotocol/sdk' +import { Readable, Writable } from 'node:stream' +import { AcpAgent } from './agent.js' +import { enableConfigs } from '../../utils/config.js' +import { applySafeConfigEnvironmentVariables } from '../../utils/managedEnv.js' + +/** + * Creates an ACP Stream from a pair of Node.js streams. + */ +export function createAcpStream( + nodeReadable: NodeJS.ReadableStream, + nodeWritable: NodeJS.WritableStream, +): Stream { + const readableFromClient = Readable.toWeb( + nodeReadable as typeof process.stdin, + ) as unknown as ReadableStream + const writableToClient = Writable.toWeb( + nodeWritable as typeof process.stdout, + ) as unknown as WritableStream + return ndJsonStream(writableToClient, readableFromClient) +} + +/** + * Entry point for the ACP (Agent Client Protocol) agent mode. + */ +export async function runAcpAgent(): Promise { + enableConfigs() + + // Apply environment variables from settings.json (ANTHROPIC_BASE_URL, + // ANTHROPIC_AUTH_TOKEN, model overrides, etc.) so the API client can + // authenticate. Without this, Zed-launched processes won't have these + // env vars in process.env. + applySafeConfigEnvironmentVariables() + + const stream = createAcpStream(process.stdin, process.stdout) + + let agent!: AcpAgent + const connection = new AgentSideConnection(conn => { + agent = new AcpAgent(conn) + return agent + }, stream) + + // stdout is used for ACP messages — redirect console to stderr + console.log = console.error + console.info = console.error + console.warn = console.error + console.debug = console.error + + async function shutdown(): Promise { + // Clean up all active sessions + for (const [sessionId] of agent.sessions) { + try { + await agent.unstable_closeSession({ sessionId }) + } catch { + // Best-effort cleanup + } + } + process.exit(0) + } + + // Exit cleanly when the ACP connection closes + connection.closed.then(shutdown).catch(shutdown) + + process.on('SIGTERM', shutdown) + process.on('SIGINT', shutdown) + + process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason) + }) + + // Keep process alive while connection is open + process.stdin.resume() +} diff --git a/src/utils/performanceShim.ts b/src/utils/performanceShim.ts new file mode 100644 index 000000000..174518bb6 --- /dev/null +++ b/src/utils/performanceShim.ts @@ -0,0 +1,165 @@ +/** + * Performance shim — replaces globalThis.performance to prevent JSC's C++ Vector + * from growing without bound. + * + * In Bun, globalThis.performance is JSC's native Performance object. It stores + * marks, measures, and resource timings in a C++ Vector that never shrinks even + * after clearMarks(). Long-running sessions (daemon, /loop) accumulate hundreds + * of MB of dead capacity. + * + * This shim keeps performance.now() on the native object (fast, no memory cost) + * but redirects mark/measure/getEntries operations to a plain JS Map that the GC + * can reclaim. Third-party code (React reconciler, OTel/Langfuse) uses + * performance.now() for timing — that stays native. The accumulating operations + * go to GC-able JS memory instead. + * + * MUST be installed before React/OTel import — see cli.tsx first import. + */ + +const original = globalThis.performance + +// JS-backed storage — fully GC-able +const marks = new Map() +const measures = new Map< + string, + { name: string; startTime: number; duration: number } +>() + +function now(): number { + return original.now() +} + +function mark(name: string): PerformanceMark { + marks.set(name, now()) + // Return a minimal PerformanceMark-like object to satisfy the interface. + // React/OTel only use mark() for side effects, not the return value. + return { + name, + entryType: 'mark', + startTime: marks.get(name)!, + duration: 0, + } as PerformanceMark +} + +function measure( + name: string, + startMarkOrOptions?: string | MeasureOptions, + endMark?: string, +): void { + let startTime: number + let duration: number + + if (typeof startMarkOrOptions === 'string') { + const start = marks.get(startMarkOrOptions) + const end = endMark ? marks.get(endMark) : now() + startTime = start ?? now() + duration = (end ?? now()) - startTime + } else if (startMarkOrOptions && typeof startMarkOrOptions === 'object') { + startTime = startMarkOrOptions.start ?? 0 + duration = (startMarkOrOptions.end ?? now()) - startTime + } else { + startTime = 0 + duration = now() + } + + measures.set(name, { name, startTime, duration }) +} + +interface MeasureOptions { + start?: number + end?: number + detail?: unknown +} + +interface PerformanceEntryLike { + readonly name: string + readonly entryType: string + readonly startTime: number + readonly duration: number +} + +function getEntriesByType(type: string): PerformanceEntryLike[] { + if (type === 'mark') { + return [...marks.entries()].map(([name, startTime]) => ({ + name, + entryType: 'mark', + startTime, + duration: 0, + })) + } + if (type === 'measure') { + return [...measures.values()].map(m => ({ + name: m.name, + entryType: 'measure', + startTime: m.startTime, + duration: m.duration, + })) + } + return [] +} + +function getEntriesByName(name: string, type?: string): PerformanceEntryLike[] { + const entries = getEntriesByType(type ?? 'mark').concat( + type === undefined ? getEntriesByType('measure') : [], + ) + return entries.filter(e => e.name === name) +} + +function clearMarks(name?: string): void { + if (name !== undefined) { + marks.delete(name) + } else { + marks.clear() + } +} + +function clearMeasures(name?: string): void { + if (name !== undefined) { + measures.delete(name) + } else { + measures.clear() + } +} + +// Plain object shim — must NOT inherit from Performance.prototype because +// native getters (onresourcetimingbufferfull, timeOrigin, toJSON) check +// that `this` is an actual JSC Performance instance and throw otherwise. +const shim = { + now, + mark, + measure: measure as typeof performance.measure, + getEntriesByType: getEntriesByType as typeof performance.getEntriesByType, + getEntriesByName: getEntriesByName as typeof performance.getEntriesByName, + clearMarks: clearMarks as typeof performance.clearMarks, + clearMeasures: clearMeasures as typeof performance.clearMeasures, + clearResourceTimings: (() => {}) as typeof performance.clearResourceTimings, + setResourceTimingBufferSize: + (() => {}) as typeof performance.setResourceTimingBufferSize, + // Delegate read-only properties to the original + get timeOrigin() { + return original.timeOrigin + }, + get onresourcetimingbufferfull() { + return (original as any).onresourcetimingbufferfull + }, + set onresourcetimingbufferfull(_v: any) { + // no-op — prevent accumulation + }, + toJSON() { + return original.toJSON() + }, +} as typeof performance + +/** + * Install the shim onto globalThis.performance. Safe to call multiple times. + * Must run before React and OTel import to prevent them from capturing the + * native Performance reference. + */ +export function installPerformanceShim(): void { + if ((globalThis as any).__performanceShimInstalled) return + ;(globalThis as any).__performanceShimInstalled = true + globalThis.performance = shim +} + +// Auto-install on import +installPerformanceShim()