import { killProcessTree } from "@sema-agent/core"; export declare const IS_WIN32: boolean; export interface HostShell { shell: string; args: string[]; } /** * Resolve the host shell ONCE per process (cached). POSIX resolves synchronously to `/bin/sh` — zero behavior * change. win32 defers to core's `getShellConfig` (explicit `HOST_SHELL_PATH` override → Git-for-Windows * install dirs → PATH `bash.exe` with the WSL-launcher filter) and THROWS a self-explaining error when no * usable bash exists (design D1 fail-loud: "install Git for Windows", never a silent cmd fallback). * * Call sites: `shellReady()` awaits this once (all exec/bg/stream spawns are behind it), then the spawn sites * read the cached value synchronously via {@link hostShell}. */ export declare function resolveHostShell(): Promise; /** Synchronous read of the resolved shell — valid only AFTER {@link resolveHostShell} settled (the spawn sites * are all behind `shellReady()`, which awaits it). POSIX never needs the async step. Throws on a mis-ordered * call (a programming bug, not an operator condition — fail loud, not /bin/sh-on-windows). */ export declare function hostShell(): HostShell; /** test-only: reset the shell caches (each test can re-drive resolution). */ export declare function resetHostShellCacheForTest(): void; /** Per-platform spawn options for a group-killable child (design D3). * POSIX: `detached:true` — child becomes its own process-group leader (pgid === pid) so `kill(-pid)` reaps the * whole group. BYTE-IDENTICAL to the previous inline `detached: true`. * win32: `detached:false` (a detached child opens a console; there is no killable POSIX group either way — * the kill side uses taskkill's tree) + `windowsHide:true` (no flashing console window). */ export declare function spawnGroupOptions(): { detached: boolean; windowsHide?: boolean; }; /** * HARD kill of the child's whole tree (design D2). POSIX = `process.kill(-pid, "SIGKILL")` — byte-identical, * INCLUDING the throw on a dead group (ESRCH): every call site already wraps in its own try/catch with its own * fallback (e.g. `child.kill("SIGKILL")`), and that structure must not change. * win32 = core `signalProcessTree(pid,"SIGKILL")` → `taskkill /F /T` (never throws — fire-and-forget spawn). */ export declare function killTreeHard(pid: number): void; /** * SOFT kill (design D2 — the bg KillShell/timeout-wall first rung; the caller owns the grace→hard escalation * timer, unchanged). POSIX = `process.kill(-pid, "SIGTERM")` byte-identical (throws on dead group — the caller * treats that as "already dead, skip escalation"). win32 = `taskkill /T` without /F — which for a typical * all-console tree ERRORS AND KILLS NOTHING: the ladder degrades to delay→hard-kill there; * accepted + documented (CC-identical). It never throws, so the caller's "already dead → return" branch never * fires on win32 — the escalation timer always arms, which is exactly what we want given soft is a no-op. */ export declare function killTreeSoft(pid: number): void; /** Graceful tree kill where the CALLER has no escalation timer of its own (currently unused by the host lane — * exported for parity with core's surface; the bg driver keeps its own HOST_BG_KILL_GRACE_MS ladder). */ export { killProcessTree }; /** 登记回执 = 注销的唯一凭据(纯数据;身份即凭据,见簿注)。 */ export interface ForegroundChildTicket { /** 组领袖 pid = pgid(POSIX `detached:true` 的直接后果)。 */ readonly pid: number; } /** 登记一只自起的前台子进程,返回注销用的回执。 */ export declare function trackHostForegroundChild(pid: number): ForegroundChildTicket; /** 注销(`exit` / settle / 领养移交)。按回执身份删 ⇒ 幂等,且**绝不**碰同号的其它登记。 */ export declare function untrackHostForegroundChild(ticket: ForegroundChildTicket): void; /** 一次前台收割的读数。三格分开是**刻意**的:`alreadyDead` 是想要的结果(不是失败),`failed` 是**真的** * 没收上来 —— 两者混成一个数就等于把后者静默掉([ref]:这条腿上不许有安静的兜底)。 */ export interface ForegroundReapReading { /** 真发出了杀伤信号的组数。 */ readonly signalled: number; /** `ESRCH` —— 组已经不在了。信号的语义就是「让它死」,它已经死了 ⇒ 记数,不报警。 */ readonly alreadyDead: number; /** 其余拒因,逐条 `pid:code`。本模块没有 logger,所以**交回调用方响亮记下**(它有)。 */ readonly failed: readonly string[]; } /** * 收割簿上全部前台子进程组。返回**发出**信号的读数,不是确认死亡数 —— 确认要等,而本函数的调用点之一 * 是 uncaught 出口。 * * **同步形、一次到位、不等宽限**:两个调用点(`hardShutdown` / uncaught 双钩)都紧挨着 `process.exit`, * `await` 一个宽限梯就是把收割赌在一个永远等不到的续段上。上界 = 簿上条数 × 一次 `kill(-pid)` syscall * (簿条数被 host lane 的在飞 turn 数界住),没有定时器、没有 promise。 * * 🔴 **为什么是 `killTreeHard` 而不是 SIGTERM**(codex 对抗复审 [high],亲跑复现后改):软鞍的语义是 * 「给它一个收尾窗,到点我再升级」,而这两个调用点之后**没有任何人在场**去升级 —— 一个 * `trap '' TERM` 的组收到 SIGTERM 后 `kill` 不抛(实测 `signalled:1, failed:[]`、随后仍然在跑),于是 * 「已收割」的读数是**假的**,孤儿照旧永久存活;win32 的软鞍对普通 console tree 更是明确杀不掉任何东西 * (见 {@link killTreeSoft} 注)。且本仓对**前台 exec** 的既有杀伤姿势(abort / timeout / 流被放弃) * 一律就是 `killTreeHard` —— 收割用**同一只**,不新造一种只能兑现一半的姿势(规则集更小,不是更大)。 * 代价如实写:一条行为良好的命令在这里拿不到收尾窗;它换来的是「引擎死了,它起的命令一定也死」这句话 * 真的成立。后台腿的宽限梯不受影响(那条腿有自己的属主,且不在本簿里)。 */ export declare function reapHostForegroundChildren(): ForegroundReapReading; /** 测试面:簿上现存条数(产品面没有消费者 —— 收割腿只读它自己那只 Set)。 */ export declare function liveHostForegroundChildCountForTest(): number; /** * win32 env-key case collapse (design D1). Windows env keys are case-insensitive; Node hands the * object to CreateProcess as-is, so a case-sensitive merge that produced BOTH `Path` (inherited) and `PATH` * (caller override) yields one-of-them-undefined-which in the child. Collapse case-duplicate keys LAST-WRITE-WINS * in iteration order — matching what `Object.assign` semantics promised the caller (per-command overrides beat * config env beats inherited base) — while keeping the FIRST-SEEN casing (the inherited base is assigned first, * so `Path=…` inherited + `PATH=x` override collapses to `Path=x`: native casing, override value). * POSIX: returns the input UNTOUCHED (case-sensitive env is real there — `Path` and `PATH` are distinct). */ export declare function collapseWin32EnvKeys(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv; /** * Conventional exit-code mapping for a process killed by a signal (128 + signal number). Single pattern-home * construction point for every exec adapter that must turn a `close` event's external-kill case * (`code===null`, `signal` set) into an exit code instead of silently reporting a fake success 0 — the host * lane, ssh, adb, and local-docker all consume THIS function; none of them may hand-write their own signal * table. Uses Node's authoritative platform table (`os.constants.signals`) rather than a hand-written list — * a hand-written table has previously missed `SIGABRT`/`SIGPIPE` (`kill -ABRT` reported 137, impersonating * `SIGKILL`). Callers keep a `?? 9` fallback for a name the platform table doesn't define, matching core's * `SIGNUM[signal] ?? 9`. */ export declare function signalNumber(signal: NodeJS.Signals): number | undefined; /** The platform-free collapse algorithm (exported so the mac/Linux suite can pin the win32 behavior — design * D5: structural verification everywhere, behavioral bite on the Windows runner). */ export declare function collapseEnvKeysCaseInsensitive(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv; //# sourceMappingURL=host-platform.d.ts.map