Files
orca/src/main/jcode/daemon-prewarm.ts
T
Neilandczzczz 0a45ad5ec4 refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST
The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
2026-10-03 01:49:40 -07:00

70 lines
2.9 KiB
TypeScript

// Why: jcode's TUI client starts its own server and then waits a hardcoded 5s for
// the socket to answer a ping (`wait_for_server_ready` in
// crates/jcode-app-core/src/server/socket.rs). Orca gives every pane its own
// JCODE_RUNTIME_DIR, so every jcode pane is a COLD daemon start — and a cold start
// on a loaded machine overruns that budget, which is how an orchestration worker
// died with "Timed out waiting for responsive server socket" before its prompt was
// ever delivered. Starting the daemon as the PTY spawns gives it the shell's own
// startup time as head start, so the client finds a live socket instead of racing.
import { spawnProcess } from '../../shared/child-process/run-process'
import { getTuiAgentLaunchCommand, TUI_AGENT_CONFIG } from '../../shared/tui-agent-config'
export type JcodeDaemonPrewarm = {
launchAgent?: string
runtimeDir?: string
cwd?: string
env?: Record<string, string>
platform?: NodeJS.Platform
}
/** Runtime dirs already warmed in this Orca process; the daemon outlives one pane. */
const warmed = new Set<string>()
export function resetJcodeDaemonPrewarmForTests(): void {
warmed.clear()
}
/** The runtime dir to warm, or null when this pane is not a local jcode launch.
* Why non-Windows only: the runtime dir is a unix-socket directory, and Orca only
* stamps it off Windows (see shouldInjectJcodeRuntimeDir). */
function prewarmTarget({ launchAgent, runtimeDir, platform }: JcodeDaemonPrewarm): string | null {
const unsupported = launchAgent !== 'jcode' || (platform ?? process.platform) === 'win32'
return unsupported || !runtimeDir ? null : runtimeDir
}
/**
* Start this pane's jcode daemon in the background, at most once per runtime dir.
*
* Fire-and-forget by contract: jcode's client starts its own server when none is
* listening, so a pre-warm that fails costs nothing beyond the cold start Orca
* already had. Never throws, and never blocks the spawn path.
*/
export function prewarmJcodeDaemon(args: JcodeDaemonPrewarm): boolean {
const runtimeDir = prewarmTarget(args)
if (runtimeDir === null || warmed.has(runtimeDir)) {
return false
}
warmed.add(runtimeDir)
// A missing binary or a spawn refusal just means no head start; forget the dir so
// the next pane on it can try again.
const giveUp = (): boolean => (warmed.delete(runtimeDir), false)
try {
const child = spawnProcess({
program: getTuiAgentLaunchCommand(TUI_AGENT_CONFIG.jcode, args.platform ?? process.platform),
// Why --no-update: an update check here would delay the very socket the client
// is about to wait on. Why stdio ignore + unref: the daemon is jcode's to own
// and must outlive this spawn, so Orca keeps no handle on it.
args: ['--no-update', 'serve'],
cwd: args.cwd,
env: { ...args.env, JCODE_RUNTIME_DIR: runtimeDir },
detached: true,
stdio: 'ignore'
})
child.unref()
child.on('error', giveUp)
return true
} catch {
return giveUp()
}
}