import { OnLine } from "../utils/exec.js"; export interface BuildResult { success: boolean; duration_seconds: number; errors: string[]; warnings_count: number; output_path: string; } /** * Match real compiler / linker / build-system errors only. Keyword-only * matching ("any line containing 'error'") is too greedy — it picks up * comments like `// error handling` from cmake output and inflates the * error list. * * Patterns: GCC/Clang/MSVC compiler diagnostics, linker errors, CMake * Errors, Ninja FAILED markers. * * @internal exported for testing */ export declare function parseErrors(output: string): string[]; /** * Count compiler warnings only — same tightening logic as parseErrors. * @internal exported for testing */ export declare function countWarnings(output: string): number; export type BuildType = "Debug" | "Release" | "RelWithDebInfo"; export declare function crosspadBuild(mode: "incremental" | "clean" | "reconfigure", onLine?: OnLine, buildType?: BuildType, signal?: AbortSignal): Promise; export interface RunResult { pid: number | null; exe_path: string; already_running?: boolean; responsive?: boolean; /** Where the simulator's stdout and stderr are being appended. */ log_path?: string; /** Last lines of that file, filled in only when the sim failed to come up. */ log_tail?: string[]; error?: string; } /** How many lines of the sim log a failed launch reports inline. */ export declare const RUN_LOG_TAIL_LINES = 30; /** Where this launch's stdout/stderr goes: /hil_logs/sim_.log */ export declare function simLogPath(now?: number): string; /** Last `n` non-empty lines of a log file, or [] if there is nothing to read. */ export declare function tailFile(file: string, n: number): string[]; /** * Is the process still there? EPERM means it is, owned by someone else. * @internal exported for testing */ export declare function processAlive(pid: number): boolean; /** * Launch the simulator binary in the background. * * Refuses to spawn a second instance if one is already responding on the * remote-control port — multiple instances clobber each other's window * state and the TCP listener binds to the same port. * * After spawn we poll the TCP control port for up to ~3s — this distinguishes * "process started, ready to accept commands" from "process started but * crashed before binding" so callers don't fire screenshot/stats too early. * * Both streams go to a log file, and a sim that dies before answering brings * its last lines back with it: a failed probe on its own reports that nothing * answered, never that the vcpkg SDL2 was missing or the kit JSON was unreadable. */ export declare function crosspadRun(force?: boolean): Promise; export interface KillResult { success: boolean; killed_pids: number[]; was_running: boolean; error?: string; } /** * Canonicalize a filesystem path. realpath resolves every symlink in the * chain — needed because /proc//exe always returns the kernel's view * (post-symlink) while BIN_EXE comes from string concatenation under * CROSSPAD_PC_ROOT, which is commonly itself a symlink (e.g. ~/GIT/crosspad-pc * → /mnt/big-disk/crosspad-pc). Without canonicalization the string compare * silently misses every running sim. * * Falls back to path.resolve when realpath fails (binary not built yet, or * a non-existent /proc path during a TOCTOU race) so callers always get a * comparable absolute path. * * @internal exported for testing */ export declare function canonicalize(p: string): string; /** * Strip the kernel's " (deleted)" suffix that appears in /proc//exe * when the executable was unlinked or replaced after the process started * (typical during dev: rebuild while sim is still running). Without this * strip the path compare misses any running-but-rebuilt sim — the most * common reason a developer would hit "agent can't kill the simulator." * * @internal exported for testing */ export declare function stripDeletedSuffix(p: string): string; /** * Find PIDs running the CrossPad simulator binary. * * On Linux scans /proc//exe and matches the resolved+canonicalized * symlink against BIN_EXE. This is the only reliable identification: * pgrep -x compares /proc//comm, which Qt/pthread routinely overwrite * via pthread_setname_np / prctl(PR_SET_NAME), so a sim launched as * "CrossPad" shows up under whatever Qt named the main thread last. * /proc//exe is the kernel's record of the executed binary and cannot * be spoofed by userspace renames. * * On macOS/Windows there's no /proc, so fall back to pgrep by basename. * macOS suffers the same Qt comm-rename in theory but our binary name is * 8 chars (fits in comm's 15-char limit) and PC builds are predominantly * exercised on Linux, so this is documented as best-effort. * * @internal exported for testing */ export declare function findCrosspadPids(): number[]; /** * Kill the running PC simulator. SIGTERM matched PIDs, poll up to 3s for * graceful exit, SIGKILL stragglers. Detection uses both /proc and the TCP * control port — either signal alone has been observed to lie (TCP port * occasionally lingers after the binary exits, and a crashed process may * keep its socket past the syscall return). * * Per-PID kill errors (EPERM, EUNKNOWN) are aggregated and surfaced in * `error` so the caller sees *why* a kill didn't take, not just "still * running" — historically this was the hardest kill failure to debug * because Node swallowed EPERM and the user just saw success=false. */ export declare function crosspadKill(): Promise;