import { TraceBuffer } from "./trace-buffer.js"; export declare function parseOneShot(out: string, type: string): any; export interface SignalSpec { name: string; address: number; size: number; encoding: string; } export type Frame = { type: "sample"; t: number; values: Record; } | { type: "status"; device_state: string; t?: number; samples?: number; } | { type: "error"; error: string; } | { type: "signals"; signals: SignalSpec[]; unresolved: string[]; }; /** Pure: parse one NDJSON line from the daemon into a Frame, or null. */ export declare function parseFrame(line: string): Frame | null; export interface SessionOpts { signals: string[]; rateHz: number; elf?: string; outFile?: string; capacity?: number; swo?: string[]; } export declare class TraceSession { private opts; private proc; buffer: TraceBuffer; deviceState: string; startedAt: number; filePath: string | null; private stdoutBuf; private onFrameExtra; private pendingReconcile; private termTimer; private killTimer; private static readonly STDERR_RING; private stderrLines; private stderrBuf; private sawErrorFrame; private firstFrameSeen; private firstFrameWaiters; constructor(opts: SessionOpts); onFrame(cb: (f: Frame) => void): void; /** Detach the frame sink so late `sample`/`status` frames during a shutdown * race aren't rebroadcast after `trace_end`, and the consumer (Dashboard) * doesn't hold this session longer than necessary. */ offFrame(): void; private stoppedCbs; /** Register a callback for when the daemon process has fully exited. If the * process is already gone, fires synchronously. */ onStopped(cb: () => void): void; private fireStopped; /** The ELF this session traces against (explicit opt or the configured default). * Used by the web UI's /symbols endpoint so autocomplete matches the trace. */ elfPath(): string; start(): void; /** §11.6: fold daemon stderr into a bounded ring (last STDERR_RING lines). */ private ingestStderr; /** §11.6: the last `n` captured daemon stderr lines, newest last. */ stderrTail(n?: number): string; /** Daemon exited. §11.6: if it died non-zero WITHOUT emitting an error frame, * synthesize an `error: ` device_state so status/start never * reports a stale "running" for a process that's actually dead. */ private onExit; private ingest; /** Reconcile the buffer's watched-signal set to match the daemon's authoritative * list (from a "signals" frame): add any new names, drop any no longer present. */ private reconcileSignals; /** Add signals to the live poll set (NDJSON `add` to daemon stdin). * Resolves with the POST-reconcile signal-name set once the daemon's next * "signals" frame is ingested (PROTOCOL §4); ~2 s timeout → resolves with the * current `buffer.signalNames()` so a missing frame never hangs the caller. */ addSignals(specs: string[]): Promise; /** Remove signals from the live poll set (NDJSON `remove` to daemon stdin). * Same post-reconcile / timeout contract as addSignals. */ removeSignals(specs: string[]): Promise; /** Queue a resolver fired by the next "signals" frame; falls back to the * current signal set after ~2 s so callers can't hang on a silent daemon. */ private awaitReconcile; /** §11.6: resolve on the FIRST ingested frame (signals|sample|error), or * `null` on timeout / if the daemon exits first. Lets MCP `start` report a * truthful device_state instead of an optimistic "running". Idempotent: * if a frame was already seen before the call, resolves immediately. */ waitForFirstFrame(timeoutMs?: number): Promise; private lastFirstFrame; /** Fire all pending first-frame waiters once. `f` is the triggering frame * (or null when the daemon exited / spawn-errored before any frame). */ private resolveFirstFrame; /** Cancel any pending SIGTERM/SIGKILL escalation timers (clean exit). */ private clearKillTimers; /** §11.5 guaranteed teardown — escalate {cmd:stop} → SIGTERM → SIGKILL. * Idempotent and safe to call when `proc` is already null (the daemon may * have self-exited on a connect failure / crash / target reset). A wedged * daemon blocked in uninterruptible libusb ignores SIGTERM, so the SIGKILL * fallback is the ONLY thing that guarantees no `pkill -9` is ever needed. * The escalation timers are cleared on real exit (see onExit) so a clean * shutdown never fires a stray kill at a recycled PID. * * §12.1: stop() ONLY ends the daemon now. The web UI is owned by the * persistent Dashboard singleton (see trace-webui.ts), which OUTLIVES the * session — the MCP `stop` handler calls dashboard.unbind() so the server * keeps listening and the dashboard tab survives across traces. */ stop(): void; isRunning(): boolean; } export declare function getActiveSession(): TraceSession | null; export declare function setActiveSession(s: TraceSession | null): void; export declare function traceWrite(writes: string[]): Promise<{ ok: boolean; results?: any[]; error?: string; }>; export declare function traceCall(func: string, args: number[], confirm: boolean, retType: string, timeout: number): Promise<{ ok: boolean; r0?: number; decoded?: any; error?: string; }>;