/** The two host process events this reporter observes. */ export type ProcessFatalEventType = "uncaughtException" | "unhandledRejection"; /** Structured record written for every observed fatal event. */ export interface ProcessFatalEntry { type: ProcessFatalEventType; message: string; stack?: string; name?: string; timestamp: string; } /** * Minimal process surface the reporter installs on. Structural (no host SDK * import) — the host's real `process` satisfies it, and tests can pass a spy * target to observe exactly which listeners are added and removed. */ export interface ProcessListenerTarget { on(event: string, listener: (...args: any[]) => void): unknown; off?(event: string, listener: (...args: any[]) => void): unknown; removeListener?(event: string, listener: (...args: any[]) => void): unknown; /** * Live listener census for an event. `process` is an EventEmitter and * provides it; a structural target that cannot report a count is treated as * not-proven-sole, so an invisible host handler is never pre-empted. */ listenerCount?(event: string): number; } export interface ProcessFatalReporterOptions { /** * Synchronous, best-effort rolebox state flush — the same flush the host * exit handler performs. Runs at most once, before the log entry; a throw is * swallowed and logged, never propagated. */ flush?: () => void; /** Sink for the structured entry; defaults to the rolebox `process-fatal` sub-logger. */ report?: (entry: ProcessFatalEntry) => void; /** Listener target; defaults to the host `process`. */ target?: ProcessListenerTarget; /** Force the single stderr line on/off; defaults to `ROLEBOX_FATAL_STDERR === "1"`. */ stderr?: boolean; /** * Reproduce the host's default `uncaughtException` status when rolebox's * listener is the event's sole listener: the handler calls `process.exit(1)` * after the flush and the single log entry. Default `true` — a crash must * not be swallowed. Set `false` to observe without exiting. */ preserveUncaughtExit?: boolean; /** * Opt into exiting after an `unhandledRejection`. Default `false`: the host * keeps running after an unhandled rejection, so exiting would change * semantics rather than preserve them. */ exitOnUnhandledRejection?: boolean; } /** * Observation-first process-fatal reporter that preserves the host's crash * semantics instead of overriding them. * * Measured on Node v26.4.0 and Bun 1.3.14: a thrown error with no * `uncaughtException` listener prints the stack and exits 1; with any listener * the process stays alive and later exits 0. A listener that only logs * therefore turns a crash into a surviving corrupted process. * * The `uncaughtException` handler flushes rolebox state, writes ONE structured * entry, and then calls `process.exit(1)` — the status the runtime would have * used on its own — but only when rolebox's listener is the sole listener for * the event (`target.listenerCount(event) === 1`) and no * `process.setUncaughtExceptionCaptureCallback` is installed, so the process * does NOT survive a crash. A target that cannot report a listener count, or * an event with another listener, is observed only: a pre-existing host * handler is never pre-empted. `unhandledRejection` is observe-only by * default, since the host default there already keeps running; pass * `exitOnUnhandledRejection` to opt into the exit. * * The handler never re-throws, never calls `process.abort()`, and never * touches stdout. The behaviour delta is a log line (plus an opt-in stderr * line), the flush, and — for a sole-owner crash — the exit status the host * would have had without any listener. */ export declare class ProcessFatalReporter { private readonly target; private readonly flushFn; private readonly report; private readonly stderr; private readonly preserveUncaughtExit; private readonly exitOnUnhandledRejection; private listeners; private installed; /** Latches on the first fatal event: every later event is a no-op. */ private handled; constructor(options?: ProcessFatalReporterOptions); /** * Install the two listeners once (idempotent). Returns an uninstall() that * removes exactly the listeners this call added. */ install(): () => void; /** Remove exactly the listeners install() added. Idempotent. */ uninstall(): void; /** * Handle one fatal event: flush once, write one log entry, optionally one * stderr line, then reproduce the host's default exit status when rolebox is * the event's sole owner. Never throws, never re-throws. Once an event has * been handled every later event is a no-op — state is already flushed, and * a crash storm must not multiply the flush, the log or the exit. */ handle(type: ProcessFatalEventType, error: unknown): void; /** * Whether rolebox should reproduce the host's default fatal status. Only a * sole-owner `uncaughtException` does: the runtime default is exit 1, and * this handler is what suppressed it. An `unhandledRejection` stays * observe-only unless the caller opted in. */ private shouldExit; /** True only when the target proves rolebox is the event's only listener. */ private soleOwner; /** True when a capture callback has taken over uncaught-exception handling. */ private hasCaptureCallback; /** Best-effort diagnostic logging that can never propagate. */ private safeDebug; } //# sourceMappingURL=process-fatal-reporter.d.ts.map