import type { AssistantMessage } from "@oh-my-pi/pi-ai"; import { type Component } from "@oh-my-pi/pi-tui"; import { AssistantMessageComponent } from "@oh-my-pi/pi-tui/chat/assistant-message"; import { ToolExecutionComponent, type ToolExecutionHandle } from "@oh-my-pi/pi-tui/chat/tool-execution"; import type { InteractiveModeContext } from "../../modes/types.js"; import type { AgentSessionEvent } from "../../session/agent-session.js"; import { ComposerPredictionController } from "./composer-prediction-controller.js"; export declare class EventController { #private; private ctx; /** Next-message prediction offered as composer ghost text after a completed turn. */ readonly composerPrediction: ComposerPredictionController; constructor(ctx: InteractiveModeContext); /** Surface an idle recap reply in the transcript as a one-line {@link RecapNotice}; a blank reply mounts nothing. */ presentIdleRecap(reply: string): void; dispose(): void; /** * Reply blocks of calls still executing. Their results are not in the session's committed * messages yet, so a transcript rebuild must carry these over itself; replies of finished * calls are replayed from the persisted results. */ liveToolReplies(): Component[]; /** * Post-tool text segments of the reply still streaming. The reply is not in the session's * committed messages until `message_end`, so a transcript rebuild must carry these over itself; * segments of replies already committed are replayed instead. */ liveAssistantSegments(): AssistantMessageComponent[]; /** * Hand the streaming reply's components the current extension text renderers (the setting enabling * them changed). Renderers take the full text, so a running typing reveal is dropped first. */ applyAssistantTextRenderers(): void; inheritReadToolAssistant(toolCallId: string, component: AssistantMessageComponent | undefined): void; subscribeToAgent(): void; /** * Route one session event through the same pipeline as the live * subscription: coalesce the cumulative `message_update` deltas of a * streaming turn into at most one handler run per window, and serialize * every other event behind any in-flight run. * * `#handleMessageUpdate` is synchronous, so without coalescing every token * re-runs the whole streaming rebuild (splitAssistantMessageToolTimeline, * reveal setTarget, per-block tool-call reconciliation) even though the TUI * paints at most ~30fps — at 40-100 tps the handler work then dominates the * CPU profile of an idle-looking streaming session (issue #7443). Only the * latest snapshot is meaningful; non-update events flush the pending * snapshot first so ordering is preserved. */ dispatchSessionEvent(event: AgentSessionEvent): Promise; /** * Run `run` in the serialized session-event dispatch pipeline, flushing any * pending coalesced `message_update` first. This does not drop work when the * viewed session changes: a caller that queues across that change must skip * the stale work itself. */ runSerialized(run: () => Promise): Promise; /** Whether `#handleToolExecutionStart` has fired for this call id this turn. */ hasToolExecutionStarted(toolCallId: string): boolean; /** The viewed session was swapped for an idle one: no run owns the chrome any more. */ markViewIdle(): void; /** * Clear every transcript-anchored/turn-scoped piece of state. Used by the * session focus proxy when re-pointing the transcript at another session: * components, timers, and stream-reveal state all reference the previous * session's transcript and must not bleed into the new one. */ resetTranscriptAnchors(): void; /** Settle replay-created cards without waiting for another streaming update. */ restorePendingToolResults(): void; handleEvent(event: AgentSessionEvent): Promise; /** * Detach both displacement trackers and return whichever components were * live, without touching their animation state. `#handleToolExecutionEnd` * settles a displaceable `wait`/`todo` result out of `pendingTools` into * these trackers instead (see the `isDisplaceableBlock()` branch there), so * a caller that enumerates only `pendingTools` before replacing the whole * transcript — the collab guest resync (`guest.ts#finalizeSnapshot`) — * misses a still-animated "waiting" card. That caller must fold this * method's result into its own live-block accounting. */ takeDisplaceableComponents(): ToolExecutionHandle[]; /** * Adopt a rebuilt-tail todo snapshot as the controller's tracked live * snapshot. Used by rebuild paths (settings/extensions overlay close, focus * attach, /resume) to preserve displacement continuity when a turn is still * active — without this, the next same-turn `todo` update would stack * another panel because the controller's tracker was reset before rebuild. * Drops the candidate when it is no longer a displaceable todo. */ inheritDisplaceableTodo(component: ToolExecutionComponent | null | undefined): void; /** * Adopt the rebuild's replayed user-prompt timestamp so a still-running turn * keeps its prompt→yield delta. Focus attach and mid-turn rebuilds reset the * controller's turn start before replaying entries; without the handoff the * in-flight assistant `message_end` would render the usage row without the * elapsed figure. Mirrors {@link inheritDisplaceableTodo}. Drops the * candidate when the rebuild saw no user message. */ inheritTurnStart(turnStartedAt: number | undefined): void; /** * Re-register parked background cards after a transcript rebuild so a * background job's replayed and live progress frames keep routing to the * preserved component. Focus attach clears {@link #backgroundCallIds} * via {@link resetTranscriptAnchors} before the rebuild repopulates it. * Ids whose component did not survive into `pendingTools` are skipped. */ markBackgroundCalls(toolCallIds: Iterable): void; /** Resync a cumulative snapshot without replay cleanup or speech side effects. */ syncAssistantMessage(message: AssistantMessage): Promise; /** The run only waits on background work, and the interrupt key would end that wait. */ isAwaitingAsyncWork(): boolean; /** * End a wait on background work (Esc while {@link isAwaitingAsyncWork}). A * user interrupt keeps a later job result from starting a turn — it lands in * context for the next prompt — and the jobs keep running. Nothing the * settle watch waits for can resume this run any more, so tear it down now * instead of when the last job finishes, and say what is still running. */ stopAwaitingAsyncWork(): Promise; sendErrorNotification(event: Extract): void; sendCompletionNotification(event: Extract): void; }