mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-26 17:38:36 +00:00
windows-child-process.test.ts regexed main.ts and bootstrap-runner.ts source text to check that spawn/execFileSync call sites wrapped their options with hiddenWindowsChildOptions(), and that backend teardown chose the right kill strategy. Extract both into dependency-free sibling modules: - windows-child-options.ts: hiddenWindowsChildOptions(options, isWindows) now takes isWindows as an injectable param (defaults to the real platform check). main.ts and bootstrap-runner.ts both import the same implementation instead of each defining their own copy. - backend-child.ts: stopBackendChild(child, deps) with forceKillProcessTree and isWindows injected, so the SIGTERM-vs-tree-kill branching is directly testable with a fake child + a spy. windows-child-options.test.ts replaces windows-child-process.test.ts, calling the real functions with fake spawn/execFileSync-shaped objects and asserting on the actual returned options / kill call.
55 lines
1.9 KiB
TypeScript
55 lines
1.9 KiB
TypeScript
/**
|
|
* backend-child.ts
|
|
*
|
|
* Windows-aware teardown for the desktop's managed backend child process.
|
|
*
|
|
* Node's `child.kill()` only signals the direct child. On Windows a backend
|
|
* that spawned its own grandchildren (a `hermes` REPL, a pty terminal
|
|
* session, the gateway) survives a plain SIGTERM and keeps files (e.g. the
|
|
* venv shim) locked. So on Windows we tree-kill via `forceKillProcessTree`;
|
|
* everywhere else a plain SIGTERM is correct and sufficient (POSIX has no
|
|
* mandatory locks, and the backend is not spawned detached so there's no
|
|
* process-group to negative-pid-kill).
|
|
*
|
|
* Extracted into its own dependency-free module (no electron import) so the
|
|
* SIGTERM-vs-tree-kill branching can be asserted directly with a fake child
|
|
* object and a spy `forceKillProcessTree`, instead of grepping main.ts source
|
|
* text for the function body.
|
|
*/
|
|
|
|
export interface StopBackendChildDeps {
|
|
/** Defaults to the real platform check; injectable for tests. */
|
|
isWindows?: boolean
|
|
/** Windows tree-kill implementation (real: taskkill /T /F via execFileSync). */
|
|
forceKillProcessTree: (pid: number) => void
|
|
}
|
|
|
|
export interface KillableChild {
|
|
pid?: number | null
|
|
killed?: boolean
|
|
kill: (signal: string) => void
|
|
}
|
|
|
|
/**
|
|
* Stop a managed child process, choosing the right strategy for the platform.
|
|
* No-ops silently if `child` is falsy, already killed, or the kill attempt
|
|
* throws (the process may already be gone) -- mirrors the original inline
|
|
* best-effort semantics in main.ts.
|
|
*/
|
|
export function stopBackendChild(child: KillableChild | null | undefined, deps: StopBackendChildDeps) {
|
|
if (!child || child.killed) {
|
|
return
|
|
}
|
|
|
|
const isWindows = deps.isWindows ?? process.platform === 'win32'
|
|
|
|
try {
|
|
if (isWindows && Number.isInteger(child.pid)) {
|
|
deps.forceKillProcessTree(child.pid as number)
|
|
} else {
|
|
child.kill('SIGTERM')
|
|
}
|
|
} catch {
|
|
// Already gone.
|
|
}
|
|
}
|