/** * Copyright (c) 2025 Elara AI Pty Ltd * Licensed under BSL 1.1. See LICENSE for details. */ import { type RunnerValue } from '@elaraai/e3-types'; import type { StorageBackend } from '../storage/interfaces.js'; /** * Walk up from `startDir` and return every `node_modules/.bin` directory * found, ordered from nearest to furthest. Used to prepend each to a * spawned process's PATH so the runner CLI resolves no matter which level * of the monorepo a binary was hoisted to — pnpm puts shared bins at the * workspace root, package-local bins next to their consumer. */ export declare function collectNodeModulesBins(startDir: string): string[]; /** * Walk up from `startDir` and return the first uv virtualenv bin directory * found — `.venv/bin` on POSIX, `.venv/Scripts` on Windows — as a one-element * array, or `[]` if there is no `.venv` above `startDir`. * * Unlike {@link collectNodeModulesBins} this STOPS at the first `.venv`: a uv * project has exactly one, so once we find it there is nothing further up to * collect. Used to prepend the project venv's bin dir to a spawned runner's * PATH so a Python runner installed there (`east-py`) resolves from the * project's own environment rather than a `node_modules/.bin` shim or a global * install. Local-host only — the path is injected into the child's PATH, never * into the hashed command argv. */ export declare function collectVenvBins(startDir: string): string[]; /** * Marshal input objects to staged `.beast2` files in a scratch directory. * * This is the input-staging loop extracted from `taskExecute`: each input * hash is read from the object store and written as `input-.beast2`. * * @returns The staged file paths, in input order */ export declare function marshalInputsToDir(storage: StorageBackend, repo: string, scratchDir: string, inputHashes: string[]): Promise; /** * Marshal raw value bytes to staged `.beast2` files in a scratch directory. * * The graph-free path writes args to scratch directly from request bytes — * no object-store round trip. * * @returns The staged file paths, in arg order */ export declare function marshalBytesToDir(scratchDir: string, blobs: Uint8Array[]): Promise; /** * Read a runner's output file back as bytes. * * Extracted so the graph-free path returns bytes without writing them to * the object store. */ export declare function readOutputFile(outputPath: string): Promise; /** * Build the full runner argv for a function/one-shot call. * * This is the function analogue of a task's `commandIr` output — built * directly from the wire runner variant, no IR evaluation. (Task mapping: * `args` ⇄ the `-i` data inputs, `bodyIr` ⇄ the trailing IR positional * that was `input-0`.) */ export declare function buildRunnerArgv(runner: RunnerValue, argPaths: string[], outputPath: string, bodyIrPath: string): string[]; /** * Options for {@link spawnAndCapture}. */ export interface SpawnAndCaptureOptions { /** Wall-clock limit in ms; on expiry the process group is killed and the * result reports `timedOut: true`. */ timeoutMs?: number; /** AbortSignal for cancellation (kills the process group). */ signal?: AbortSignal; /** Streaming stdout callback (called per chunk, before tail capture). */ onStdout?: (data: string) => void; /** Streaming stderr callback (called per chunk, before tail capture). */ onStderr?: (data: string) => void; /** Per-stream in-memory tail cap in bytes (default 64 KiB). */ maxLogBytes?: number; /** Executable dirs prepended to the child PATH ahead of everything else — * a materialized execution environment's bin dir (materializeEnvironment) * must beat both the project venv and node_modules/.bin. */ extraBins?: string[]; /** Directories whose ancestor `node_modules/.bin` dirs are prepended to * PATH so runner CLIs resolve (deduped, nearest first). */ searchDirs?: string[]; /** Called once the child has spawned, with its pid (or null). The tracked * path uses this to write the `running` execution status. */ onSpawned?: (pid: number | null) => void | Promise; } /** * Result of {@link spawnAndCapture}. */ export interface SpawnAndCaptureResult { /** Process exit code (null if killed by signal or spawn failed) */ exitCode: number | null; /** Spawn-failure / non-zero-exit description (null on success) */ error: string | null; /** True if the timeout fired and killed the process group */ timedOut: boolean; /** Bounded tail of stdout (per maxLogBytes) */ stdoutTail: string; /** Bounded tail of stderr (per maxLogBytes) */ stderrTail: string; /** True if stdout bytes were dropped from the tail */ stdoutTruncated: boolean; /** True if stderr bytes were dropped from the tail */ stderrTruncated: boolean; } /** * Spawn a command and capture its output — the `runCommand` mechanics from * LocalTaskRunner minus every storage write. * * Keeps: cross-spawn/nodeSpawn selection, node_modules/.bin PATH * augmentation, `detached: true` process-group management, stdout/stderr * listeners (streaming callbacks + bounded in-memory tails), process-group * kill, timeout + AbortSignal wiring. * * Process Lifecycle Management * ============================ * We use detached: true to create a new process group, allowing us to kill * the entire process tree by signaling the negative PID (process group * leader). Process groups are flat, not hierarchical — a task that calls * setsid() escapes the kill; that is a known, accepted limitation (see the * discussion that used to live in runCommand). */ export declare function spawnAndCapture(args: string[], scratchDir: string, options?: SpawnAndCaptureOptions): Promise; //# sourceMappingURL=processExec.d.ts.map