import type { SessionRegistry } from "../session-registry.js"; import { type UsageDelta } from "./renderer.js"; /** Minimal shape of the AgentSessionEvent union we care about — kept loose * so the file doesn't pull in @earendil-works/pi-agent-core types at compile * time for callers that only consume the factory. */ type SubagentEvent = { type: "turn_start"; } | { type: "message_end"; message: unknown; } | { type: "turn_end"; message: unknown; } | { type: "tool_execution_start"; toolCallId: string; toolName: string; args: unknown; } | { type: "tool_execution_end"; toolCallId: string; toolName: string; result: unknown; isError: boolean; } | { type: "compaction_start"; reason: string; } | { type: "auto_retry_start"; attempt: number; maxAttempts: number; errorMessage?: string; } | { type: string; [k: string]: unknown; }; export interface ViewportObserverOpts { registry: SessionRegistry; /** session key in the registry (taskId, bugId, or sprintId:ceremony, etc.) */ sessionId: string; /** phase scope for tail buffer and per-phase usage */ phaseRole: string; /** Exact OrchestratorTree node ID for this dispatch. Orchestrators know * the node they started (`::`) — passing it pins * every telemetry event to that node. Without it the observer falls back * to a role-prefix scan for the first *running* match, which misattributes * telemetry whenever an earlier same-role node is still open (the * CART-BUG-003 dashboard regression: review-plan:2's logs accumulated on a * leaked review-plan:1). */ nodeId?: string; /** displayed in the per-line prefix `[ HH:MM:SS tN]`. Usually same * as `phaseRole` but kept separate so callers like run-sprint ceremony can * show a friendlier role label without changing the registry key. */ displayRole?: string; /** optional `─── phase X/Y begin · sessionId ───` header to emit immediately */ beginHeader?: string; /** optional JSONL audit sink (the run-task debug log). */ writeDebug?: (rec: Record) => void; /** optional verbose status setter — only called when FORGE_VERBOSE=1. * The observer doesn't read FORGE_VERBOSE itself; caller decides whether to * wire this. */ setStatusVerbose?: (key: string, msg: string) => void; /** optional toast for compaction/retry; ignore to suppress. */ notify?: (msg: string, level: "info" | "warning" | "error") => void; /** keys used for `setStatusVerbose` when caller wants verbose status. */ verboseKeys?: { messageKey?: string; }; /** invoked after every handled event so orchestrators can refresh their own * status line (e.g. FORGE_VERBOSE status with `lastTool`, elapsed seconds). */ afterEach?: () => void; } /** One persisted tail-view line — exactly what the live dashboard rendered. */ export interface TailLogEntry { line: string; warning?: boolean; } /** * Head-preserving cap on the recorded tail log. Live phases rarely exceed a * few hundred lines; the cap only bounds pathological runs. Head-preserving * (stop recording, don't drop oldest) because the log's consumer is the * transcript-archive REPLAY, where the beginning is the valuable part. */ export declare const TAIL_LOG_CAP = 5000; export interface AttachedObserver { onEvent: (event: SubagentEvent) => void; /** Mutable counters orchestrator can read after subagent returns. */ state: { turn: number; toolCount: number; errCount: number; lastTool: string; cumUsage: UsageDelta; cumCompression: { calls: number; tokensSaved: number; }; /** * Verbatim record of every tail line this observer rendered, in * order — the live view stream. Orchestrators persist it next to * the phase transcript (`.tail.jsonl`) so transcript * replay re-reads the EXACT lines the live dashboard showed * instead of reconstructing an approximation from messages. */ tailLog: TailLogEntry[]; }; } /** * Persist the recorded tail log next to its phase transcript as JSONL * (`.tail.jsonl`). The transcript archive sweeps it with * the run; replay re-reads the exact lines the live dashboard showed. * Best-effort — observability data, never throws. */ export declare function persistTailLog(subagentTranscriptPath: string, tailLog: TailLogEntry[]): string | null; export declare function attachViewportObserver(opts: ViewportObserverOpts): AttachedObserver; export {};