import { EventEmitter } from "node:events"; export interface PhaseSummary { role: string; index: number; startedAt: number; endedAt?: number; status: "running" | "completed" | "failed" | "skipped" | "cancelled"; turn: number; toolCount: number; errCount: number; /** * Bounded ring buffer of human-readable tail lines for this phase's * subagent. Populated by the subagent stream wiring (Step 5). Drives * the thread-switcher chip's tail viewport when the user focuses * this phase. Capped at MAX_TAIL_LINES_PER_PHASE. */ tailBuffer: string[]; /** * Count of warning-class events appended to tailBuffer since the user * last focused this phase. Drives the ◇/◆ chip state in the switcher. * Zeroed by markRead(). */ unreadWarnings: number; /** * First non-empty assistant-turn preview captured during this phase. * Set once on the first setTurnPreview call after startPhase. */ firstTurnPreview?: string; /** * Most recent assistant-turn preview captured during this phase. * Updated on every setTurnPreview call. */ lastTurnPreview?: string; /** * Cumulative token usage across all assistant turns in this phase. * Populated by run-task on every turn_end. Drives the sticky token * footer rendered at the bottom of the tail view. */ usage?: { input: number; output: number; cacheRead: number; /** Peak (high-water) context-window size. Non-cumulative. */ context: number; }; /** * Provider + model id observed on the most recent assistant turn of this * phase. Captured by viewport/events on every turn_end so the tail-view * footer can show e.g. `ollama-cloud/glm-5.1 · ↑1.2k ↓340`. Lets users * confirm at a glance which model a phase's subagent actually ran with, * instead of inferring it from config files or stderr. */ model?: string; provider?: string; compression?: { calls: number; tokensSaved: number; }; } export interface ToolEventRecord { ts: number; kind: "tool_start" | "tool_end"; phaseRole: string; toolName: string; toolCallId: string; args?: unknown; isError?: boolean; result?: unknown; } export type SessionStatus = "running" | "cancelling" | "cancelled" | "completed" | "failed" | "escalated"; export interface SessionState { taskId: string; startedAt: number; updatedAt: number; status: SessionStatus; currentPhaseRole?: string; phases: PhaseSummary[]; events: ToolEventRecord[]; /** * Per-session AbortController created at startSession, cleared on any * terminal transition. Fires abort() when requestCancel() is called. * Drives the cancellation lifecycle: running → cancelling → cancelled. */ abortController?: AbortController; /** * Latest assistant-turn preview from any subagent under this session. * Populated by run-task on `turn_end` via setTurnPreview. Drives the * trailing "...preview" text in the thread-switcher chip strip. */ currentTurnPreview?: string; } /** * One bubble-up record per assistant turn from any subagent in the process. * Lets top-level viewports (the chip strip) surface a unified "what's * happening right now" feed identified by subagent, regardless of which * subagent is foregrounded. */ export interface TurnEvent { sessionId: string; phaseRole: string; /** Human-friendly role label (defaults to phaseRole when caller doesn't * pass one). Drives the `[displayRole]` prefix in the global feed. */ displayRole: string; turn: number; preview: string; thinking: string; /** Per-turn token delta (from `message.usage`). `context` is this turn's size. */ deltaUsage: { input: number; output: number; cacheRead: number; context: number; }; /** Cumulative phase usage at this turn. `context` is the running peak. */ cumUsage: { input: number; output: number; cacheRead: number; context: number; }; timestamp: number; } export declare class SessionRegistry extends EventEmitter { private sessions; /** Ring buffer of cross-session turn events. Capped at MAX_TURN_LOG. * Newest at the end. */ private turnLog; getSession(taskId: string): SessionState | undefined; listSessions(): SessionState[]; startSession(taskId: string): void; startPhase(taskId: string, role: string, phaseIndex: number): void; bumpTurn(taskId: string): void; recordToolStart(taskId: string, toolCallId: string, toolName: string, args: unknown): void; recordToolEnd(taskId: string, toolCallId: string, toolName: string, isError: boolean, result: unknown): void; completePhase(taskId: string, role: string, status: PhaseSummary["status"]): void; completeSession(taskId: string, status: SessionState["status"]): void; private trimEvents; /** * Request cancellation of a running session. Transitions status to * "cancelling" and fires the session's AbortController.abort(). * Returns false if session not found or already terminal. */ requestCancel(taskId: string): boolean; /** * Confirm that cancellation has fully unwound. Called by the orchestrator * after abort is detected and the pipeline exits. Transitions * "cancelling" → "cancelled". */ confirmCancelled(taskId: string): void; /** * Convenience accessor — returns the AbortSignal from the session's * AbortController, or undefined if the session doesn't exist or has * no controller. */ getAbortSignal(taskId: string): AbortSignal | undefined; private findPhase; /** * Append a per-turn bubble-up event from any subagent. The observer in * viewport/events.ts calls this once on every `turn_end`. ChipStrip * subscribes to the emitted `turn` event to refresh its global preview * panel; other consumers can read `getRecentTurnEvents()`. */ recordTurnEvent(evt: TurnEvent): void; /** Most-recent turn events across all sessions, newest first. */ getRecentTurnEvents(limit?: number): TurnEvent[]; /** Convenience: latest turn event from any subagent, or undefined. */ getLatestTurnEvent(): TurnEvent | undefined; /** * Sum of `phase.usage` across every phase of every active session in the * registry. Drives the top-level `Σ ↑X ↓Y ⇪Z` meter so users see the * process-wide token burn regardless of which subagent is foregrounded. */ getAggregateUsage(): { input: number; output: number; cacheRead: number; context: number; }; setPhaseUsage(taskId: string, phaseRole: string, usage: { input: number; output: number; cacheRead: number; context: number; }): void; setPhaseModel(taskId: string, phaseRole: string, modelInfo: { provider?: string; model?: string; }): void; setPhaseCompression(taskId: string, phaseRole: string, compression: { calls: number; tokensSaved: number; }): void; getAggregateCompression(): { calls: number; tokensSaved: number; }; appendTail(taskId: string, phaseRole: string, line: string, opts?: { warning?: boolean; }): void; markRead(taskId: string, phaseRole: string): void; getTailLines(taskId: string, phaseRole: string, limit?: number): string[]; setTurnPreview(taskId: string, preview: string): void; private evictIfNeeded; } export declare function getSessionRegistry(): SessionRegistry;