pub struct State {
pub lifecycle: LifecyclePhase,
pub processes: HashMap<u32, ProcessRecord>,
pub windows: HashMap<String, WindowMirror>,
pub pool: HashSet<String>,
pub instance_registry: HashMap<String, u32>,
pub next_instance_num: u32,
pub backend_window_ids: HashMap<String, String>,
pub event_version: u64,
pub next_client_id: u64,
pub monitors: Vec<Rect>,
pub pending_hwnds: HashMap<u64, PendingHwnd>,
pub just_promoted_labels: HashSet<String>,
}Expand description
Top-level launcher state. Single Arc<Mutexupdate(state, cmd, conn) for every
incoming command.
Fields§
§lifecycle: LifecyclePhase§processes: HashMap<u32, ProcessRecord>Keyed by PID. Multiple records per PID would be a bug — the reducer enforces unique-pid on insert.
windows: HashMap<String, WindowMirror>Read-only window mirror (Phase B.4). Keyed by label. Source of
truth still lives in agentmux-cef::AppState.browsers /
window_meta; this is a passive copy fed by host
ReportWindow* commands. B.5 inverts the dependency: host
queries this map instead of maintaining its own.
pool: HashSet<String>Phase B.4 follow-up — pre-warmed pool inventory. Tracked
separately from windows because pool entries are not
user-visible until promote. On promote the host emits
ReportPoolWindowRemoved + ReportWindowOpened so the same
label transitions atomically (from launcher’s perspective)
from pool to windows. On pre-promote destroy: only
ReportPoolWindowRemoved.
instance_registry: HashMap<String, u32>Phase B.5 — authoritative window instance registry. Maps
label → sequential instance number (1 for “main”, 2 for the
second window opened, etc.). Numbers are never reused within
a launcher run — when a window closes the entry is removed
but next_instance_num keeps advancing. Sole source of truth
post-B.5e (host’s WindowInstanceRegistry was deleted in
PR #584); host holds a passive shadow projection in
agentmux-cef::AppState.shadow_instance_registry. Updated by
the same reducer paths that mutate windows.
next_instance_num: u32Next instance number to assign. Starts at 2 — “main” is
pre-seeded with 1 in Default (matching host’s
WindowInstanceRegistry::new behavior so a synthetic main
open wouldn’t collide).
backend_window_ids: HashMap<String, String>Phase B.5 (window_id_map step a) — authoritative
label → backend window ID map. Mirrors host’s existing
agentmux-cef::AppState.window_id_map. Populated by
Command::ReportBackendWindowIdRegistered (sent from host
when the frontend calls register_backend_window IPC
after init); drained by ReportBackendWindowIdUnregistered
on close. Will become host-side authoritative through the
standard a→b→c→d→e ratchet.
event_version: u64Monotonic counter for Event.version. Bumped by bump_version().
next_client_id: u64Monotonic counter for client_id (returned in Registered events).
monitors: Vec<Rect>Phase B.9.1 (WRR) — current monitor topology, replaced
wholesale on ReportMonitorTopologyChanged. Empty by default
until the host’s wrr/wndproc.rs reports the first
WM_DISPLAYCHANGE-equivalent (or its initial topology probe
at startup). OffMonitor drift is suppressed when this is
empty — we don’t know enough to classify yet.
pending_hwnds: HashMap<u64, PendingHwnd>Phase B.9.1 — HWNDs the reducer has seen via ReportHwndOpened
but couldn’t yet associate with a WindowMirror. Three
reasons an entry lives here transiently:
- The OS create event raced ahead of the host’s
OnAfterCreated→ReportWindowOpenedchain. - The host couldn’t determine
label_hintat create time. - The HWND belongs to a pool window not yet promoted.
Drained on each
ReportWindowOpened(we try to match a pending HWND bylabel_hint/timing). Anything still here after a follow-up event is classified asHwndWithoutBrowser.
just_promoted_labels: HashSet<String>Drift-storm fix (PR #708 round 3) — labels for which the host
emitted ReportPoolWindowPromoted but the corresponding
ReportWindowOpened hasn’t arrived yet. The actual host emit
order on tear-off is ReportPoolWindowRemoved →
ReportPoolWindowPromoted → ReportWindowOpened, so at
promote-time the launcher has NO WindowMirror for the label
— the mirror is created by ReportWindowOpened a few ms
later. Without this set, the post-promote mirror is initialized
with foregrounded_since_open: false, the open-transient drift
detector then fires HiddenSinceOpen on every visible→hidden
flicker during HWND repositioning, the host fans each event
out across the bridge and the renderer’s V8 isolate crashes.
ReportWindowOpened consumes the entry to initialize the new
mirror with foregrounded_since_open: true. Removed on
ReportWindowClosed if open never arrived (bounded leak).
See docs/specs/ANALYSIS_DRIFT_STORM_RENDERER_CRASH_2026-05-06.md.
Implementations§
Source§impl State
impl State
Sourcepub fn bump_version(&mut self) -> u64
pub fn bump_version(&mut self) -> u64
Bump and return the new event version. Always called inside the reducer when constructing an Event so version numbers stay strictly monotonic.
Strict (non-wrapping) add: Phase D’s GetSnapshot resync
protocol relies on monotonicity (event.version > snapshot.version), and a wrap to 0 would silently break
that contract. u64 at one event/ns would take 584 years to
overflow — never going to happen in practice; if it ever
does, the panic is the right failure mode.
(gemini MEDIUM PR #574 round-1.)
Sourcepub fn alloc_client_id(&mut self) -> u64
pub fn alloc_client_id(&mut self) -> u64
Bump and return the next client_id. Client IDs are stable per launcher run; not persisted across restart. Same strict- add reasoning as bump_version.