Feat/integrate lint preview (#285)

* 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 from 637c908 dropped 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 from 637c908 dropped 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>
This commit is contained in:
claude-code-best
2026-04-16 20:59:29 +08:00
committed by GitHub
parent a02dc0bded
commit c8d08d235b
137 changed files with 13267 additions and 837 deletions

View File

@@ -1,6 +1,13 @@
import { spawn, type ChildProcess } from 'child_process'
import { type ChildProcess } from 'child_process'
import { resolve } from 'path'
import { buildCliLaunch, spawnCli } from '../utils/cliLaunch.js'
import { errorMessage } from '../utils/errors.js'
import {
writeDaemonState,
removeDaemonState,
queryDaemonStatus,
stopDaemonByPid,
} from './state.js'
/**
* Exit code used by workers for permanent (non-retryable) failures.
@@ -29,30 +36,62 @@ interface WorkerState {
* Daemon supervisor entry point. Called from `cli.tsx` via:
* `claude daemon [subcommand]`
*
* Starts and supervises long-running workers. Currently spawns one
* `remoteControl` worker that runs the headless bridge server.
* Manages the daemon supervisor AND background sessions under one namespace.
*
* Subcommands:
* (none) — start the supervisor with default workers
* start — same as no subcommand
* status — print worker status (TODO: IPC)
* stop send SIGTERM to supervisor (TODO: PID file)
* (none) — unified status (supervisor + sessions)
* start — start the supervisor with default workers
* stop — send SIGTERM to supervisor
* statusunified status (supervisor + sessions)
* ps — alias for status
* bg — start a background session
* attach — attach to a background session
* logs — show session logs
* kill — kill a session
*/
export async function daemonMain(args: string[]): Promise<void> {
const subcommand = args[0] || 'start'
const subcommand = args[0] || 'status'
switch (subcommand) {
// --- Supervisor management ---
case 'start':
await runSupervisor(args.slice(1))
break
case 'status':
console.log('daemon status: not yet implemented (requires IPC)')
break
case 'stop':
console.log('daemon stop: not yet implemented (requires PID file)')
await handleDaemonStop()
break
// --- Unified status ---
case 'status':
case 'ps':
await showUnifiedStatus()
break
// --- Session management (delegates to bg.ts) ---
case 'bg': {
const bg = await import('../cli/bg.js')
await bg.handleBgStart(args.slice(1))
break
}
case 'attach': {
const bg = await import('../cli/bg.js')
await bg.attachHandler(args[1])
break
}
case 'logs': {
const bg = await import('../cli/bg.js')
await bg.logsHandler(args[1])
break
}
case 'kill': {
const bg = await import('../cli/bg.js')
await bg.killHandler(args[1])
break
}
case '--help':
case '-h':
case 'help':
printHelp()
break
default:
@@ -64,17 +103,25 @@ export async function daemonMain(args: string[]): Promise<void> {
function printHelp(): void {
console.log(`
Claude Code Daemon — persistent background supervisor
Claude Code Daemon — background process management
USAGE
claude daemon [subcommand] [options]
claude daemon [subcommand]
SUBCOMMANDS
start Start the daemon supervisor (default)
status Show worker status
status Show daemon and session status (default)
start Start the daemon supervisor
stop Stop the daemon
bg Start a background session
attach Attach to a background session
logs Show session logs
kill Kill a session
help Show this help
OPTIONS
REPL
/daemon [subcommand] Same commands available in interactive mode
OPTIONS (for start)
--dir <path> Working directory (default: current)
--spawn-mode <mode> Worker spawn mode: same-dir | worktree (default: same-dir)
--capacity <N> Max concurrent sessions per worker (default: 4)
@@ -85,6 +132,63 @@ OPTIONS
`)
}
/**
* Show unified status: daemon supervisor + background sessions.
*/
async function showUnifiedStatus(): Promise<void> {
// 1. Daemon supervisor status
const result = queryDaemonStatus()
console.log('=== Daemon Supervisor ===')
switch (result.status) {
case 'running': {
const s = result.state!
console.log(` Status: running`)
console.log(` PID: ${s.pid}`)
console.log(` CWD: ${s.cwd}`)
console.log(` Started: ${s.startedAt}`)
console.log(` Workers: ${s.workerKinds.join(', ')}`)
break
}
case 'stopped':
console.log(' Status: stopped')
break
case 'stale':
console.log(' Status: stale (cleaned up)')
break
}
// 2. Background sessions
console.log('\n=== Background Sessions ===')
const bg = await import('../cli/bg.js')
await bg.psHandler([])
}
/**
* Stop a running daemon from another CLI process.
*/
async function handleDaemonStop(): Promise<void> {
const result = queryDaemonStatus()
if (result.status === 'stopped') {
console.log('daemon is not running')
return
}
if (result.status === 'stale') {
console.log('daemon was stale (cleaned up)')
return
}
console.log(`stopping daemon (PID: ${result.state!.pid})...`)
const stopped = await stopDaemonByPid()
if (stopped) {
console.log('daemon stopped')
} else {
console.log('daemon could not be stopped (may have already exited)')
}
}
/**
* Parse supervisor arguments from CLI.
*/
@@ -140,12 +244,22 @@ async function runSupervisor(args: string[]): Promise<void> {
},
]
// Write daemon state file so other CLI processes can query/stop us
writeDaemonState({
pid: process.pid,
cwd: dir,
startedAt: new Date().toISOString(),
workerKinds: workers.map(w => w.kind),
lastStatus: 'running',
})
const controller = new AbortController()
// Graceful shutdown
const shutdown = () => {
console.log('[daemon] supervisor shutting down...')
controller.abort()
removeDaemonState()
for (const w of workers) {
if (w.process && !w.process.killed) {
w.process.kill('SIGTERM')
@@ -222,17 +336,11 @@ function spawnWorker(
CLAUDE_CODE_SESSION_KIND: 'daemon-worker',
}
// Build the worker command: reuse the same entrypoint with --daemon-worker flag
const execArgs = [
...process.execArgv,
process.argv[1]!,
`--daemon-worker=${worker.kind}`,
]
console.log(`[daemon] spawning worker '${worker.kind}'`)
const child = spawn(process.execPath, execArgs, {
env,
const launch = buildCliLaunch([`--daemon-worker=${worker.kind}`], { env })
const child = spawnCli(launch, {
cwd: dir,
stdio: ['ignore', 'pipe', 'pipe'],
})