mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-15 21:05:51 +00:00
* feat: 适配 zed acp 协议 * docs: 完善 acp 文档 * feat: integrate feature branches + daemon/job 命令层级化 + 跨平台后台引擎 Cherry-picked from origin/lint/preview (637c908), excluding lint-only changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct detectMimeFromBase64 to decode raw bytes from base64 Cherry-picked from origin/lint/preview (ee36954). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: daemon 子进程 spawn 跨平台修复 + CliLaunchSpec 集中化重构 Cherry-picked from origin/lint/preview (c5f52cd), excluding lint-only formatting changes. - 新建 src/utils/cliLaunch.ts: 集中化 CLI 子进程启动层 - 修复 --daemon-worker=kind 等号格式解析 - 修复 daemon/bg fast path 缺少 setShellIfWindows() - 修复 checkPathExists 用 existsSync 替代 execSync('dir') - 7 个 spawn 站点迁移到 CliLaunchSpec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
5.9 KiB
TypeScript
181 lines
5.9 KiB
TypeScript
import { type ChildProcess, spawn, type SpawnOptions } from 'child_process'
|
|
import { isInBundledMode } from './bundledMode.js'
|
|
import { quote } from './bash/shellQuote.js'
|
|
|
|
/**
|
|
* CliLaunchSpec — normalized descriptor for spawning a child CLI process.
|
|
*
|
|
* Every site that re-execs the CLI (daemon workers, bg sessions, bridge
|
|
* sessions, assistant/RCS daemon launchers) should use this instead of
|
|
* manually assembling `[...process.execArgv, process.argv[1]!, ...]`.
|
|
*
|
|
* Centralizing the bootstrap contract prevents the class of bugs where
|
|
* individual spawn sites forget execArgv, windowsHide, or env propagation.
|
|
*/
|
|
export interface CliLaunchSpec {
|
|
/** Runtime binary path (e.g. bun, node). */
|
|
execPath: string
|
|
/** Full argument list including bootstrap args and CLI args. */
|
|
args: string[]
|
|
/** Environment for the child process. */
|
|
env: NodeJS.ProcessEnv
|
|
/** Whether to hide the console window on Windows. */
|
|
windowsHide: boolean
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Frozen bootstrap snapshot — computed once at module load time.
|
|
//
|
|
// Bun quirk (https://github.com/oven-sh/bun/issues/11673): in single-file
|
|
// executables, app arguments from process.argv can leak into process.execArgv.
|
|
// We snapshot and filter once, so every child gets a clean, stable set of
|
|
// runtime flags regardless of when buildCliLaunch is called.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Filter out leaked application arguments from process.execArgv.
|
|
* Only keep known runtime flags: -d (defines), --feature, --inspect variants.
|
|
*/
|
|
function sanitizeExecArgv(raw: readonly string[]): string[] {
|
|
const result: string[] = []
|
|
for (let i = 0; i < raw.length; i++) {
|
|
const arg = raw[i]!
|
|
// Bun define flags: -d KEY:VALUE or -dKEY:VALUE
|
|
if (arg === '-d' || arg.startsWith('-d ') || arg.startsWith('-d\t')) {
|
|
result.push(arg)
|
|
if (arg === '-d' && i + 1 < raw.length) {
|
|
result.push(raw[++i]!)
|
|
}
|
|
continue
|
|
}
|
|
if (arg.startsWith('-d') && arg.includes(':')) {
|
|
result.push(arg)
|
|
continue
|
|
}
|
|
// Bun feature flags: --feature NAME
|
|
if (arg === '--feature') {
|
|
result.push(arg)
|
|
if (i + 1 < raw.length) {
|
|
result.push(raw[++i]!)
|
|
}
|
|
continue
|
|
}
|
|
// Node/Bun inspect flags
|
|
if (/^--inspect(-brk)?(=|$)/.test(arg)) {
|
|
result.push(arg)
|
|
continue
|
|
}
|
|
// Keep other known runtime flags (e.g. --conditions, --experimental-*)
|
|
if (arg.startsWith('--') && !arg.includes('=') && i + 1 < raw.length) {
|
|
// Unknown two-part flag — skip conservatively in bundled mode only
|
|
if (isInBundledMode()) continue
|
|
result.push(arg)
|
|
result.push(raw[++i]!)
|
|
continue
|
|
}
|
|
if (arg.startsWith('-') && !isInBundledMode()) {
|
|
result.push(arg)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
const BOOTSTRAP_ARGS: readonly string[] = Object.freeze(
|
|
sanitizeExecArgv(process.execArgv),
|
|
)
|
|
const SCRIPT_PATH: string | undefined = process.argv[1]
|
|
const EXEC_PATH: string = process.execPath
|
|
const IS_WINDOWS = process.platform === 'win32'
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Public API
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Build a normalized launch spec for spawning a child CLI process.
|
|
*
|
|
* @param cliArgs Arguments to pass to the CLI entrypoint (e.g. ['daemon', 'start'])
|
|
* @param opts.env Override environment (defaults to process.env)
|
|
*/
|
|
export function buildCliLaunch(
|
|
cliArgs: string[],
|
|
opts?: { env?: NodeJS.ProcessEnv },
|
|
): CliLaunchSpec {
|
|
const baseEnv = opts?.env ?? process.env
|
|
|
|
// In bundled mode the execPath IS the CLI binary — no script path needed.
|
|
// In script mode (dev / npm) we need the script path between runtime flags
|
|
// and CLI args so the runtime knows which file to execute.
|
|
const args: string[] =
|
|
isInBundledMode() || !SCRIPT_PATH
|
|
? [...BOOTSTRAP_ARGS, ...cliArgs]
|
|
: [...BOOTSTRAP_ARGS, SCRIPT_PATH, ...cliArgs]
|
|
|
|
// Ensure Windows children can discover git-bash without shelling out
|
|
const env: NodeJS.ProcessEnv = { ...baseEnv }
|
|
if (IS_WINDOWS) {
|
|
if (
|
|
process.env.CLAUDE_CODE_GIT_BASH_PATH &&
|
|
!env.CLAUDE_CODE_GIT_BASH_PATH
|
|
) {
|
|
env.CLAUDE_CODE_GIT_BASH_PATH = process.env.CLAUDE_CODE_GIT_BASH_PATH
|
|
}
|
|
if (process.env.SHELL && !env.SHELL) {
|
|
env.SHELL = process.env.SHELL
|
|
}
|
|
}
|
|
|
|
return {
|
|
execPath: EXEC_PATH,
|
|
args,
|
|
env,
|
|
windowsHide: IS_WINDOWS,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Spawn a child CLI process from a launch spec.
|
|
*
|
|
* Callers provide transport-level options (stdio, detached, cwd) while the
|
|
* spec handles bootstrap concerns (execPath, args, env, windowsHide).
|
|
*
|
|
* Windows note: `detached: true` on Windows creates a new console window
|
|
* (unlike Unix where it only creates a new process group). Node.js uses
|
|
* `windowsHide` to pass CREATE_NO_WINDOW, but Bun may not implement it.
|
|
* As a fallback, we always set both `windowsHide: true` and keep
|
|
* `detached` as-is — the child needs `detached` to outlive the parent.
|
|
*/
|
|
export function spawnCli(
|
|
spec: CliLaunchSpec,
|
|
spawnOpts: Omit<SpawnOptions, 'windowsHide'>,
|
|
): ChildProcess {
|
|
return spawn(spec.execPath, spec.args, {
|
|
...spawnOpts,
|
|
env: { ...spec.env, ...(spawnOpts.env as NodeJS.ProcessEnv) },
|
|
windowsHide: spec.windowsHide,
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Quote a launch spec into a single shell command string (for tmux).
|
|
*/
|
|
export function quoteCliLaunch(spec: CliLaunchSpec): string {
|
|
return quote([spec.execPath, ...spec.args])
|
|
}
|
|
|
|
/**
|
|
* Get the frozen bootstrap args snapshot.
|
|
* Useful for call sites that need the raw args (e.g. bridgeMain deps).
|
|
*/
|
|
export function getBootstrapArgs(): readonly string[] {
|
|
return BOOTSTRAP_ARGS
|
|
}
|
|
|
|
/**
|
|
* Get the script path (process.argv[1] at startup).
|
|
* Returns undefined in bundled mode.
|
|
*/
|
|
export function getScriptPath(): string | undefined {
|
|
return SCRIPT_PATH
|
|
}
|