Download packages/coding-agent/src/utils/child-process.ts from SaylorTwift/pi: direct link, hf CLI and curl.
- Browser
- Download file 4.15 kB
-
https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/src/utils/child-process.ts
- Command line
-
hf download hf://SaylorTwift/pi/packages/coding-agent/src/utils/child-process.ts
-
curl -L -o child-process.ts https://huggingface.co/SaylorTwift/pi/resolve/main/packages/coding-agent/src/utils/child-process.ts
4.15 kB
| import { | |
| type ChildProcess, | |
| type ChildProcessByStdio, | |
| spawn as nodeSpawn, | |
| spawnSync as nodeSpawnSync, | |
| type SpawnOptions, | |
| type SpawnOptionsWithStdioTuple, | |
| type SpawnSyncOptionsWithStringEncoding, | |
| type SpawnSyncReturns, | |
| type StdioNull, | |
| type StdioPipe, | |
| } from "node:child_process"; | |
| import type { Readable } from "node:stream"; | |
| import crossSpawn from "cross-spawn"; | |
| const EXIT_STDIO_GRACE_MS = 100; | |
| export function spawnProcess( | |
| command: string, | |
| args: string[], | |
| options: SpawnOptionsWithStdioTuple<StdioNull, StdioPipe, StdioPipe>, | |
| ): ChildProcessByStdio<null, Readable, Readable>; | |
| export function spawnProcess(command: string, args: string[], options: SpawnOptions): ChildProcess; | |
| export function spawnProcess(command: string, args: string[], options: SpawnOptions): ChildProcess { | |
| return process.platform === "win32" ? crossSpawn(command, args, options) : nodeSpawn(command, args, options); | |
| } | |
| export function spawnProcessSync( | |
| command: string, | |
| args: string[], | |
| options: SpawnSyncOptionsWithStringEncoding, | |
| ): SpawnSyncReturns<string> { | |
| return process.platform === "win32" | |
| ? crossSpawn.sync(command, args, options) | |
| : nodeSpawnSync(command, args, options); | |
| } | |
| /** | |
| * Wait for a child process to terminate without hanging on inherited stdio handles. | |
| * | |
| * A short-lived child can `exit` while a detached descendant keeps its stdout/stderr | |
| * pipe open. We must not resolve and destroy the streams on a fixed deadline measured | |
| * from `exit`, or output still being written past that deadline is silently lost | |
| * (earendil-works/pi#5303). Instead, after `exit` we wait for the pipes to fall idle: | |
| * the grace timer is re-armed on every chunk, so an actively writing descendant keeps | |
| * us reading, while a quiet inherited handle (e.g. a Windows daemonized descendant | |
| * that never lets `close` fire) still releases us after the grace elapses. | |
| */ | |
| export function waitForChildProcess(child: ChildProcess): Promise<number | null> { | |
| return new Promise((resolve, reject) => { | |
| let settled = false; | |
| let exited = false; | |
| let exitCode: number | null = null; | |
| let postExitTimer: NodeJS.Timeout | undefined; | |
| let stdoutEnded = child.stdout === null; | |
| let stderrEnded = child.stderr === null; | |
| const cleanup = () => { | |
| if (postExitTimer) { | |
| clearTimeout(postExitTimer); | |
| postExitTimer = undefined; | |
| } | |
| child.removeListener("error", onError); | |
| child.removeListener("exit", onExit); | |
| child.removeListener("close", onClose); | |
| child.stdout?.removeListener("end", onStdoutEnd); | |
| child.stderr?.removeListener("end", onStderrEnd); | |
| child.stdout?.removeListener("data", onData); | |
| child.stderr?.removeListener("data", onData); | |
| }; | |
| const finalize = (code: number | null) => { | |
| if (settled) return; | |
| settled = true; | |
| cleanup(); | |
| child.stdout?.destroy(); | |
| child.stderr?.destroy(); | |
| resolve(code); | |
| }; | |
| const maybeFinalizeAfterExit = () => { | |
| if (!exited || settled) return; | |
| if (stdoutEnded && stderrEnded) { | |
| finalize(exitCode); | |
| } | |
| }; | |
| const armIdleTimer = () => { | |
| if (postExitTimer) clearTimeout(postExitTimer); | |
| postExitTimer = setTimeout(() => finalize(exitCode), EXIT_STDIO_GRACE_MS); | |
| }; | |
| const onData = () => { | |
| // Output is still arriving after exit; defer finalizing so we don't | |
| // destroy the stream mid-write and truncate the tail. | |
| if (exited && !settled) armIdleTimer(); | |
| }; | |
| const onStdoutEnd = () => { | |
| stdoutEnded = true; | |
| maybeFinalizeAfterExit(); | |
| }; | |
| const onStderrEnd = () => { | |
| stderrEnded = true; | |
| maybeFinalizeAfterExit(); | |
| }; | |
| const onError = (err: Error) => { | |
| if (settled) return; | |
| settled = true; | |
| cleanup(); | |
| reject(err); | |
| }; | |
| const onExit = (code: number | null) => { | |
| exited = true; | |
| exitCode = code; | |
| maybeFinalizeAfterExit(); | |
| if (!settled) { | |
| armIdleTimer(); | |
| } | |
| }; | |
| const onClose = (code: number | null) => { | |
| finalize(code); | |
| }; | |
| child.stdout?.once("end", onStdoutEnd); | |
| child.stderr?.once("end", onStderrEnd); | |
| child.stdout?.on("data", onData); | |
| child.stderr?.on("data", onData); | |
| child.once("error", onError); | |
| child.once("exit", onExit); | |
| child.once("close", onClose); | |
| }); | |
| } | |