/** * Drumbeat relauncher adapters (DEC-091, slice D3). D2 shipped the dumb daemon * + the `H2ARelauncher` interface with only a logging adapter; D3 supplies the * two **local** adapters that actually revive a stalled agent: * * - **local-tmux**: `tmux send-keys` the resume/launch command into the pane * captured at launch (DEC-085 `launchContext.tmux`). The natural revive for a * user-local agent — it lands back in its original terminal. * - **headless**: detached background respawn of the captured command + a * notification (the PRINCIPAL escalation is wired fully in D7). * * `chainRelauncher` composes them (try tmux, fall back to headless) — the * "local-tmux adapter + headless fallback" of D3. The actual process I/O is * behind an injectable `RelauncherRuntime` so the adapters are unit-testable * with no real tmux or child processes. The remote adapter is D4. */ import { type H2AActorRef, type H2ADrumbeatResumeBody, type H2AEnvelope } from "@sentropic/h2a"; import { type SendRemoteOptions, type SendRemoteResult } from "../remote/client.js"; import type { H2ARelauncher } from "./watch.js"; /** Process I/O the adapters need — injected so tests supply a fake. */ export interface RelauncherRuntime { /** Run a foreground command; return true on exit code 0. */ run(file: string, args: readonly string[]): boolean; /** Spawn a detached background process; return true if it started. */ spawnDetached(command: string | { file: string; args: readonly string[]; cwd?: string; }, options: { cwd?: string; }): boolean; /** Run a command and return its stdout (undefined on failure). For probes. */ capture?(file: string, args: readonly string[]): string | undefined; /** Optional side-channel note (relances are observable). */ notify?(line: string): void; } export declare const defaultRelauncherRuntime: RelauncherRuntime; interface RelauncherCommonOptions { runtime?: RelauncherRuntime; log?: (line: string) => void; } export type RemoteEndpointResolver = (instance: string) => string | undefined | Promise; export type RemoteEnvelopeSender = (url: string, envelope: H2AEnvelope, options: SendRemoteOptions) => Promise; export interface RemoteRelauncherOptions { /** Store root used by the default endpoint resolver. */ root?: string; /** Actor placed on the resume envelope and used as the signature signer. */ actor: H2AActorRef; /** Signer's ed25519 private-key PEM. */ privateKeyPem: string; /** Resolve the target instance to its remote endpoint. Defaults to registry lookup. */ resolveEndpoint?: RemoteEndpointResolver; /** Transport override for tests. Defaults to sendRemoteEnvelope. */ sendImpl?: RemoteEnvelopeSender; /** Clock source for deterministic tests. */ now?: () => number; log?: (line: string) => void; } /** * Build a tmux target-pane spec from a captured `launchContext.tmux`. With a * window it is `session:window.pane`; otherwise the pane is assumed to be a * unique tmux pane id (`%N`, captured via `#{pane_id}` by the D6 plugins) and * targeted directly. */ export declare function tmuxTarget(t: { session: string; window?: string; pane: string; }): string; /** * Type `text` into a tmux pane AND submit it. Sends the text literally (`-l`, so * spaces / special chars aren't interpreted as tmux key names) then Enter as a * SEPARATE send-keys. A combined `send-keys "text" Enter` (or one without `-l`) * leaves the line in the input but **never commits** on modern TUIs * (codex/claude) — the bug behind "the line is written but no Enter fires". A * second Enter covers TUIs that buffer the first. The single shared submit path * for both the drumbeat relauncher and the EVO-1 wake/drive local-tmux driver. */ export declare function tmuxSendSubmit(runtime: Pick, target: string, text: string): boolean; /** * Default window (ms): if a tmux client attached to the pane's session was * active within this window, a wake/relance is DEFERRED so it does not clobber * a human typing in the pane. Override via `H2A_WAKE_DEFER_ACTIVITY_MS` (0 = off). */ export declare const H2A_WAKE_DEFER_ACTIVITY_DEFAULT_MS = 4000; /** * True iff a tmux CLIENT attached to `target`'s session had activity within the * defer window — i.e. a human is (probably) typing/looking RIGHT NOW, so a wake * that types into the pane would clobber their in-progress input. TUI-agnostic * (uses tmux `client_activity`, not any TUI's composer state). Fail-OPEN: if the * probe can't be read (no `capture`, no tmux, detached/no client), returns false * so waking still works — the guard only fires on a POSITIVE recent-activity read. */ export declare function paneHasRecentHumanActivity(runtime: Pick, target: string, opts?: { now?: number; windowMs?: number; }): boolean; /** Revive a stalled agent by sending its resume/launch command into its pane. */ export declare function localTmuxRelauncher(options?: RelauncherCommonOptions): H2ARelauncher; /** Fallback: detached background respawn of the captured command + notify. */ export declare function headlessRelauncher(options?: RelauncherCommonOptions): H2ARelauncher; /** Try each adapter in order; the first to issue a relance wins (D3 fallback). */ export declare function chainRelauncher(...relaunchers: readonly H2ARelauncher[]): H2ARelauncher; /** * D4 remote adapter: sign a `drumbeat.resume` envelope and deliver it to the * target instance's declared remote endpoint. It never executes remote code. */ export declare function remoteRelauncher(options: RemoteRelauncherOptions): H2ARelauncher; export type H2ARelauncherKind = "logging" | "local-tmux" | "headless" | "remote" | "auto"; export {}; //# sourceMappingURL=relaunchers.d.ts.map