pub struct State {Show 13 fields
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>,
pub background_service_enabled: bool,
}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/win_event.rs reports its one-time initial
topology probe at startup (WM_DISPLAYCHANGE updates are not
wired yet). 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.
background_service_enabled: boolWorkstream 0 Phase 1 (SPEC_TRAY_OPTIONAL_BACKGROUND_SERVICE_2026_09_04.md
§7) — mirrors the host’s AGENTMUX_BACKGROUND_SERVICE opt-in, set
once via Command::ReportBackgroundServiceEnabled right after
connect. Defaults false (today’s behavior unchanged) until the host
reports otherwise. Consulted by handle_report_window_closed and
wrr’s crash-detected twin so an intentionally-resting host (zero
windows, by design) doesn’t get classified as OrphanInstance and
arm the teardown_backstop — see PR #2983 review (Codex P2): without
this, a host correctly staying alive on purpose would still get its
whole process tree killed by the backstop after a transient UI-thread
probe hiccup, since “armed” would otherwise last for the entire
(now potentially long) resting period instead of the few seconds it
used to.
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.
Trait Implementations§
Auto Trait Implementations§
impl Freeze for State
impl RefUnwindSafe for State
impl Send for State
impl Sync for State
impl Unpin for State
impl UnsafeUnpin for State
impl UnwindSafe for State
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.