feat(shared): add Node-safe process stream reader and search output collector
Introduces: - src/shared/process-stream-reader.ts: Buffer-concat stream reader compatible with both Bun and Node ChildProcess stdout (replaces Web Response API usage) - src/tools/shared/search-process-output.ts: structured subprocess output collector with timeout, kill, and rejection cleanup - bun-spawn-shim hardened: Node path forces windowsHide: true; spawn errors no longer escape as unhandledRejection Foundation for #3919 fix. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import type { SpawnedProcess } from "../../shared/bun-spawn-shim"
|
||||
import { readProcessStream } from "../../shared/process-stream-reader"
|
||||
|
||||
export interface SearchProcessOutput {
|
||||
readonly stdout: string
|
||||
readonly stderr: string
|
||||
readonly exitCode: number
|
||||
}
|
||||
|
||||
function getErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
|
||||
function createProcessTimeout(
|
||||
proc: SpawnedProcess,
|
||||
timeoutMs: number,
|
||||
timeoutMessage: string
|
||||
): Promise<never> {
|
||||
return new Promise<never>((_, reject) => {
|
||||
const id = setTimeout(() => {
|
||||
proc.kill()
|
||||
reject(new Error(timeoutMessage))
|
||||
}, timeoutMs)
|
||||
|
||||
// #3919: Handle rejected exits here so timeout cleanup cannot leak unhandled rejections.
|
||||
void proc.exited.then(
|
||||
() => clearTimeout(id),
|
||||
() => clearTimeout(id)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export async function collectSearchProcessOutput(
|
||||
proc: SpawnedProcess,
|
||||
timeoutMs: number,
|
||||
timeoutMessage: string
|
||||
): Promise<SearchProcessOutput> {
|
||||
const stderrPromise = readProcessStream(proc.stderr).catch(getErrorMessage)
|
||||
const stdout = await Promise.race([
|
||||
readProcessStream(proc.stdout),
|
||||
createProcessTimeout(proc, timeoutMs, timeoutMessage),
|
||||
])
|
||||
const [exitCode, stderr] = await Promise.all([proc.exited, stderrPromise])
|
||||
|
||||
return { stdout, stderr, exitCode }
|
||||
}
|
||||
Reference in New Issue
Block a user