Skip to content

Type Alias: AgentPaneEvent

agentmux


agentmux / agent-pane-state-store / AgentPaneEvent

AgentPaneEvent = { type: "init-ready"; } | { reason: string; type: "init-failed"; } | { at: number; type: "stream-subscribed"; } | { at: number; type: "stream-unsubscribed"; } | { at: number; lastKind: KindBeforeDisconnect; reason: DisconnectReason; type: "stream-disconnected"; } | { addedCount: number; type: "stream-flush-observed"; } | { idleSinceMs: number; recoverThresholdMs: number; thresholdMs: number; type: "stream-stuck"; } | { idleSinceMs: number; thresholdMs: number; type: "working-recovered"; } | { at: number; type: "turn-started"; } | { type: "turn-active-reconciled"; } | { at: number; type: "turn-inactive-reconciled"; } | { tokens: number; type: "context-reconciled-at-mount"; } | { model: string | null; reason: string; source: ContextSource; tokens: number; type: "context-reading-rejected"; window: number | null; } | { model: string; reported: number; tokens: number; type: "context-window-refuted"; window: number; } | { outcome: TurnOutcome; statsMerged: boolean; stoppingCleared: boolean; type: "turn-ended"; } | { type: "turn-reset"; } | { type: "turn-start-failed"; } | { reason: string; type: "turn-start-suppressed"; } | { name: string; type: "tool-started"; } | { type: "tool-ended"; } | { input: number | null; output: number | null; type: "tokens-updated"; } | { boundaryUuid?: string | null; durationMs?: number; frameTimestamp?: string | null; source: "real" | "heuristic"; tokensAfter: number; tokensBefore: number; trigger?: "manual" | "auto"; type: "context-compacted"; } | { trigger: "manual" | "auto"; type: "compaction-started"; } | { trigger: "manual" | "auto"; type: "compaction-started-buffered"; } | { reason: "rate_limited"; type: "provider-waiting"; } | { code: AgentFailure["code"]; turnWasEnded: boolean; type: "failure-observed"; } | { type: "failure-cleared"; } | { at: number; type: "stop-requested"; } | { type: "stop-failed"; } | { deadlineMs: number; type: "schedule-interrupt-timeout"; } | { at: number; type: "interrupt-timed-out"; } | { deadlineMs: number; type: "schedule-submit-timeout"; } | { at: number; type: "submit-timed-out"; } | { id: string; type: "pending-flush-started"; } | { id: string; type: "pending-queued"; } | { id: string; type: "pending-accepted"; wasPresent: boolean; } | { id: string; type: "pending-rejected"; wasPresent: boolean; } | { ageMs: number; id: string; queuedAt: number; type: "pending-expired"; wasPresent: boolean; } | { question?: string; questionCount?: number; type: "waiting-for-input"; } | { reason: "submitted" | "typing" | "closed"; type: "waiting-ended"; } | { at: number; type: "attached-task-observed"; } | { type: "attached-task-cleared"; } | { at: number; type: "registry-attached-task-observed"; } | { type: "registry-attached-task-cleared"; } | { at: number; type: "resume-retry-started"; } | { type: "resume-retry-resolved"; }

Defined in: agent-pane-state/types.ts:1069

{ type: "init-ready"; }


{ reason: string; type: "init-failed"; }


{ at: number; type: "stream-subscribed"; }


{ at: number; type: "stream-unsubscribed"; }


{ at: number; lastKind: KindBeforeDisconnect; reason: DisconnectReason; type: "stream-disconnected"; }

PR F: the stream tore down while a turn was in flight (Submitting / Streaming / Interrupting). Surfaced alongside the generic stream-unsubscribed so the view can drive a dedicated “Disconnected — reconnecting…” banner without re-deriving from turnPhase.kind. Emitted ONLY on the working-set → Disconnected transition; idle-state unsubscribes do not emit it.


{ addedCount: number; type: "stream-flush-observed"; }


{ idleSinceMs: number; recoverThresholdMs: number; thresholdMs: number; type: "stream-stuck"; }

idleSinceMs: number

recoverThresholdMs: number

The bar that would actually force this turn back to Idle. Always larger than thresholdMs, and larger still under a rate-limit backoff. Reported because a diagnostic that shows only the warning bar reads as “past the threshold and still stuck”, which is how a real 5h stall was misread while it was in progress.

thresholdMs: number

The WARNING bar (STUCK_THRESHOLD_MS). Crossing it is cosmetic.

type: "stream-stuck"

Stream has been silent for idleSinceMs while subscribed — exceeds thresholdMs. Surfaced for diagnostics / UI badge.


{ idleSinceMs: number; thresholdMs: number; type: "working-recovered"; }

idleSinceMs: number

thresholdMs: number

type: "working-recovered"

The watchdog force-recovered a hung Streaming turn to Idle after idleSinceMs ≥ LIVENESS_RECOVERY_MS with no tool active and no rate-limit wait — i.e. a turn whose terminal session_end was never observed (the persistent-mode stuck-”Working” class). Surfaced for telemetry; the phase change is the real effect. See SPEC_WORKING_STATE_LIVENESS_MODEL_2026_06_29.md.


{ at: number; type: "turn-started"; }


{ type: "turn-active-reconciled"; }

ReconcileTurnActive promoted the phase to Streaming because the backend reported a turn already in flight — either the mount-default Idle (the original case) or a settled Done.completed episode (the focus-triggered reconcile’s missed-live-turn-start case). Surfaced for diagnostics — distinguishes this from a normal user-initiated turn-started. Not “-at-mount” anymore since it can now fire well after mount.


{ at: number; type: "turn-inactive-reconciled"; }

ReconcileTurnActive demoted a stuck Streaming phase to Idle because the backend’s authoritative turn_active reported the turn has ended (the result event fired) but the frontend never observed the terminal session_end — the class behind the Agent1 stuck-”Working” and Agent2 stuck-”Queued” incidents. Completes #2005’s reconciliation symmetry (which only promoted Idle→Streaming). Surfaced for telemetry; the phase change is the real effect. See docs/retro/retro-agent2-stuck-queued-message-2026-07-16.md.


{ tokens: number; type: "context-reconciled-at-mount"; }

ReconcileContextFromHistory seeded context from the restored transcript’s last main-agent call at mount. Surfaced for diagnostics only.


{ model: string | null; reason: string; source: ContextSource; tokens: number; type: "context-reading-rejected"; window: number | null; }

A reading that can’t be true (more tokens than its window, or than any known window with none) was refused or will not display. Logged by the pane store; the meter shows nothing rather than a wrong number. See context-reading.ts implausibleReason.


{ model: string; reported: number; tokens: number; type: "context-window-refuted"; window: number; }

An accepted prompt was larger than the window Claude Code reported for its model, so the reported window can’t be right; the meter uses the next known tier above the prompt instead. Logged once per model by the pane store. context-reading.ts refutedReportedWindow.


{ outcome: TurnOutcome; statsMerged: boolean; stoppingCleared: boolean; type: "turn-ended"; }

outcome: TurnOutcome

statsMerged: boolean

stoppingCleared: boolean

type: "turn-ended"

A turn finished and the phase transitioned to Done. Since the sound-notifications subsystem (SPEC_SOUND_NOTIFICATIONS_ 2026_06_05.md §3.2), the event carries the outcome directly so downstream consumers (notification sounds, telemetry, future UI affordances) don’t have to snapshot the slot to read it back from state.turnPhase.outcome. The reducer already computes outcome two lines earlier — this is a pure forwarding of an existing value.


{ type: "turn-reset"; }


{ type: "turn-start-failed"; }


{ reason: string; type: "turn-start-suppressed"; }

reason: string

type: "turn-start-suppressed"

Invariant fire: TurnStart while streaming inactive OR initPhase.kind === “InitPending” — dropped.


{ name: string; type: "tool-started"; }


{ type: "tool-ended"; }


{ input: number | null; output: number | null; type: "tokens-updated"; }


{ boundaryUuid?: string | null; durationMs?: number; frameTimestamp?: string | null; source: "real" | "heuristic"; tokensAfter: number; tokensBefore: number; trigger?: "manual" | "auto"; type: "context-compacted"; }

source: "real" — sourced from an authoritative CompactionBoundary event (trigger/durationMs present). source: "heuristic" — inferred from a ≥50%-drop in TokensIn (the pre-existing cross-provider fallback; trigger/durationMs absent). See docs/specs/SPEC_COMPACTION_DETECTION_AND_HANDLING_2026_07_31.md §4.3.

optional boundaryUuid?: string | null

source: "real" only — see CompactionBoundary command’s doc comment.

optional durationMs?: number

optional frameTimestamp?: string | null

source: "real" only — see CompactionBoundary command’s doc comment.

source: "real" | "heuristic"

tokensAfter: number

tokensBefore: number

optional trigger?: "manual" | "auto"

type: "context-compacted"


{ trigger: "manual" | "auto"; type: "compaction-started"; }

CompactionStarted landed — compaction is in progress.


{ trigger: "manual" | "auto"; type: "compaction-started-buffered"; }

CompactionStarted arrived before turnPhase was confirmed working — buffered onto state.pendingCompactionPing rather than dropped. See SPEC_COMPACTION_STARTED_RECONCILIATION_RACE_2026_09_02.md.


{ reason: "rate_limited"; type: "provider-waiting"; }


{ code: AgentFailure["code"]; turnWasEnded: boolean; type: "failure-observed"; }

A failure was observed and recorded on state.failure. turnWasEnded is true iff the pane’s turn was actually working (Submitting / Streaming / Interrupting) at the time — surfaced for diagnostics (e.g. distinguishing a genuine mid-turn failure from a stray late event arriving after the pane was already idle).


{ type: "failure-cleared"; }

state.failure was cleared (explicit dismiss, or implicitly by TurnStart).


{ at: number; type: "stop-requested"; }


{ type: "stop-failed"; }


{ deadlineMs: number; type: "schedule-interrupt-timeout"; }

Reducer signal: a turn has just entered Interrupting. The dispatch layer (view / saga) is expected to start a setTimeout for INTERRUPT_TIMEOUT_MS and fire InterruptTimeoutElapsed when it expires — gated by a shared cancel flag (analogous to Arc<AtomicBool>) so a later RequestStop doesn’t double-fire. deadlineMs is at + INTERRUPT_TIMEOUT_MS so the caller can compute the remaining slice deterministically. Spec §8.


{ at: number; type: "interrupt-timed-out"; }

Reducer signal: the bounded Interrupting → Done.{interrupted} force-transition fired because no graceful ack arrived within INTERRUPT_TIMEOUT_MS. The view can surface this as “interrupt timed out — assuming dead” if needed. Spec §8.


{ deadlineMs: number; type: "schedule-submit-timeout"; }

Reducer signal: a turn has just entered Submitting. The dispatch layer is expected to start a setTimeout for SUBMIT_TIMEOUT_MS and fire SubmitTimeoutElapsed when it expires — gated by a shared cancel flag so a graceful promotion to Streaming (via StreamFlushObserved or bumpEvent from tool / token activity) cancels the timer before it fires. deadlineMs is at + SUBMIT_TIMEOUT_MS so the caller can compute the remaining slice deterministically. PR D — spec §8 / issue #728 gap 2.


{ at: number; type: "submit-timed-out"; }

Reducer signal: the bounded Submitting → Done.{errored} force-transition fired because no stream activity arrived within SUBMIT_TIMEOUT_MS. Carries the firing wall-clock for audit. PR D — spec §8 / issue #728 gap 2.


{ id: string; type: "pending-flush-started"; }


{ id: string; type: "pending-queued"; }


{ id: string; type: "pending-accepted"; wasPresent: boolean; }


{ id: string; type: "pending-rejected"; wasPresent: boolean; }


{ ageMs: number; id: string; queuedAt: number; type: "pending-expired"; wasPresent: boolean; }

ageMs: number

id: string

queuedAt: number

type: "pending-expired"

Pending entry expired and was removed; queuedAt for log/audit.

wasPresent: boolean


{ question?: string; questionCount?: number; type: "waiting-for-input"; }

Emitted when the pane enters the waiting-for-input state: turn completed with a question, composer empty. The sound service starts the looping ambient tone on this event. Spec: SPEC_AGENT_WAITING_AMBIENT_SOUND_2026_06_19.md §6.3.

optional question?: string

Text of the first pending question, when known. Consumed by the OS notification bridge, which forwards it to the srv router for redaction (SPEC_OS_NOTIFICATIONS_SYSTEM_2026_09_24.md §9.2).

optional questionCount?: number

How many questions the pending call asks — the toast adds ” (+N more)” when it’s more than one.

type: "waiting-for-input"


{ reason: "submitted" | "typing" | "closed"; type: "waiting-ended"; }

Emitted when the waiting state ends — user submitted or started typing, pane closed, or the 5-minute safety cutoff fired.


{ at: number; type: "attached-task-observed"; }

AttachedTaskObserved set state.attachedTask (0→1 edge).


{ type: "attached-task-cleared"; }

AttachedTaskCleared cleared state.attachedTask (1→0 edge).


{ at: number; type: "registry-attached-task-observed"; }

RegistryAttachedTaskObserved set state.registryAttachedTaskSince.


{ type: "registry-attached-task-cleared"; }

RegistryAttachedTaskCleared cleared state.registryAttachedTaskSince.


{ at: number; type: "resume-retry-started"; }

ResumeRetryStarted set state.reconnecting (0→1 edge).


{ type: "resume-retry-resolved"; }

ResumeRetryResolved cleared state.reconnecting (1→0 edge).