pub struct AppState {Show 28 fields
pub auth_key: Mutex<String>,
pub backend_endpoints: Mutex<BackendEndpoints>,
pub sidecar_child: Mutex<Option<Child>>,
pub backend_pid: Mutex<Option<u32>>,
pub backend_started_at: Mutex<Option<String>>,
pub zoom_factor: Mutex<f64>,
pub client_id: Mutex<Option<String>>,
pub window_id: Mutex<Option<String>>,
pub active_tab_id: Mutex<Option<String>>,
pub window_init_status: Mutex<String>,
pub shadow_instance_registry: Mutex<HashMap<String, u32>>,
pub shadow_backend_window_ids: Mutex<HashMap<String, String>>,
pub shadow_window_meta: Mutex<HashMap<String, WindowMeta>>,
pub cli_login_cancel: Mutex<Option<Sender<()>>>,
pub cli_login_pty_pid: Mutex<Option<u32>>,
pub cli_login_stdin: Mutex<Option<CliLoginStdin>>,
pub ipc_port: Mutex<u16>,
pub ipc_token: String,
pub window_meta: Mutex<HashMap<String, WindowMeta>>,
pub host_state: Mutex<HostState>,
pub launcher_bridge_dedup: Mutex<DedupCache>,
pub version_data_dir: Mutex<Option<String>>,
pub version_config_dir: Mutex<Option<String>>,
pub user_home_dir: Mutex<Option<String>>,
pub browser_panes: BrowserPaneManager,
pub browser_api: BrowserApiState,
pub debug_port: Mutex<u16>,
pub window_hwnds: Mutex<HashMap<String, isize>>,
}Expand description
Shared application state for the CEF host.
Unlike the Tauri version, this uses Arc<AppState> directly instead of
tauri::State<AppState>. The sidecar child is std::process::Child instead
of tauri_plugin_shell::process::CommandChild.
Fields§
§auth_key: Mutex<String>Auth key for backend communication
backend_endpoints: Mutex<BackendEndpoints>Backend (agentmux-srv) connection endpoints
sidecar_child: Mutex<Option<Child>>Handle to the sidecar child process for graceful shutdown
backend_pid: Mutex<Option<u32>>Backend process PID (set after spawn)
backend_started_at: Mutex<Option<String>>Backend process start time as ISO 8601 string
zoom_factor: Mutex<f64>Current zoom factor
client_id: Mutex<Option<String>>Client ID (set after querying backend on startup)
window_id: Mutex<Option<String>>Window ID (set after querying/creating window via backend)
active_tab_id: Mutex<Option<String>>Active tab ID (set after querying/creating default tab via backend)
window_init_status: Mutex<String>Window initialization status (“ready” or “wave-ready”)
shadow_instance_registry: Mutex<HashMap<String, u32>>Phase B.5e — host’s projection of the launcher’s
authoritative state.instance_registry. Fed by
Event::WindowInstanceAssigned /
Event::WindowInstanceReleased via
launcher_ipc::apply_event_to_shadow. Host code never
mutates this directly — reads only. Pre-seeded with
{"main": 1} to mirror the launcher’s pre-seed (avoids a
spurious first-event mismatch during startup before the
WindowInstanceAssigned { label: "main", num: 1 } event
arrives).
shadow_backend_window_ids: Mutex<HashMap<String, String>>Phase B.5 (window_id_map) — host’s projection of the
launcher’s authoritative state.backend_window_ids. Fed by
Event::BackendWindowIdRegistered /
Event::BackendWindowIdUnregistered via
apply_event_to_shadow. Sole source of truth post-step-e
(host’s window_id_map was deleted). Read via
Self::backend_window_id; never mutated directly by host
code.
shadow_window_meta: Mutex<HashMap<String, WindowMeta>>Phase B.5 (window_meta step b) — host’s projection of the
launcher’s authoritative state.windows: HashMap<String, WindowMirror> (B.4). The launcher already mirrors all
WindowMeta data via Event::WindowOpened/WindowClosed
(carrying {label, kind, parent_label} since B.4); this
host-side cache makes it readable without a launcher
round-trip. Maintained in parallel to window_meta until
step c flips reads, step d drops mutations, step e deletes
the host field. Drift logged via
target = "launcher-ipc:drift".
cli_login_cancel: Mutex<Option<Sender<()>>>Cancellation channel for an in-progress CLI login process
cli_login_pty_pid: Mutex<Option<u32>>PID of an in-progress PTY-backed CLI login child. The pipe
path uses cli_login_cancel + tokio::select! + Tokio’s
kill_on_drop Child to terminate, but the PTY path moves
the portable_pty child into a spawn_blocking task that
outlives the outer abort — so cancel needs a PID + platform
kill to actually stop the subprocess. Populated by
run_cli_login_pty, cleared when the child exits naturally.
cli_login_stdin: Mutex<Option<CliLoginStdin>>Stdin handle for the running CLI login child process. Two
variants because some providers (OpenClaw) require an
interactive TTY for their auth subcommand and we spawn them via
portable_pty instead of tokio::process::Command. Written to
by set_provider_auth to deliver an OAuth device code or
pasted token. See commands::platform::CliLoginStdin.
ipc_port: Mutex<u16>IPC HTTP server port
ipc_token: StringIPC bearer token — injected into the page alongside the port. Verified on every IPC request to prevent unauthorized local access.
window_meta: Mutex<HashMap<String, WindowMeta>>Per-window metadata (kind, parent linkage).
Phase B status: synchronous host-side cache mirroring the
launcher’s state.windows mirror. Single canonical mutation site:
inserted in client.rs::on_after_created from the popped
PendingWindowCreation entry, removed in on_before_close.
Required for synchronous lookups that can’t tolerate the launcher
round-trip lag (open_subwindow parent-liveness check; cascade-close
child enumeration). See docs/retro/migration-pattern.md for the
sync-cache exception and b5-migration-architecture-2026-04-28.md
for why step e ≠ delete here.
host_state: Mutex<HostState>Phase F.1 — host reducer state.
Owns pending_window_creations (formerly a top-level
Mutex<VecDeque<PendingWindowCreation>> field on AppState).
All mutations go through host_dispatch; reads use the
peek_back_pending_window_creation snapshot helper.
Future PRs will migrate active_drag and tear-off-hook state
here too. See agentmux-cef/src/reducer.rs and
docs/specs/SPEC_PHASE_F_HOST_REDUCER_2026-05-01.md.
launcher_bridge_dedup: Mutex<DedupCache>Phase F.7 host-bridge dedup. Keyed by "{event_kind}|{label}|{hwnd}",
value is the highest launcher-event version dispatched to renderers
for that key. The bridge skips dispatch if the incoming event’s
version is <= the cached version — preserving subscriber
idempotency under §8.14 even if the renderer-side guard fails
(multiple V8 contexts, fresh-renderer post-crash, race during
init, etc.).
Originally proposed in ANALYSIS_DRIFT_STORM_RENDERER_CRASH_2026-05-06.md
§4.4 / master spec §9.8 as Phase F.7. Implementation forced when
v0.33.688 smoke surfaced a 164× amplification of a single
HiddenSinceOpen drift event v=78 — launcher cap (PR #721)
prevented multi-emit but the bridge fanned the one event into
many V8 contexts, exhausting the renderer.
Bounded at 4096 keys with FIFO eviction (insertion order). A re-arrival for an evicted key bypasses dedup once but the renderer guard still catches it.
version_data_dir: Mutex<Option<String>>Tear-off Phase 6 — pre-warmed pool of hidden CEF windows ready for
instant promotion on tear-off. Each entry is a label of a window
that’s already painted, has its renderer connected, and is sitting
in pool-mode (?pool=1 URL flag) waiting to be assigned a workspace.
On tear-off: pop a label, reposition + show + emit pool:promote.
Version-specific data directory (e.g. ai.agentmux.cef.v0-32-111/)
version_config_dir: Mutex<Option<String>>Version-specific config directory
user_home_dir: Mutex<Option<String>>User data home used by the frontend’s agentmuxHome() helper to
construct per-agent paths (working dir, GH_CONFIG_DIR, etc.).
- Portable:
<portable>/data/— keeps agent state inside the portable folder so two coexisting portables don’t clobber each other on the same~/.agentmux/agents/<slug>/path (slugs are unique per Forge DB, not globally; seedocs/specs/portable-agent-working-dirs.md). - Installed:
~/.agentmux/— preserves existing behavior.
AGENTMUX_DATA_HOME env var, if set, overrides both.
browser_panes: BrowserPaneManagerEmbedded browser panes (native CefBrowserView per pane).
browser_api: BrowserApiStateBrowser DOM API state — CDP target cache + future connection
pool. See crate::browser_api.
debug_port: Mutex<u16>CEF remote debugging port (9223 dev / 9222 release). Populated
by main.rs from the same is_dev branch that sets
Settings.remote_debugging_port. Used by the browser DOM API
(/agentmux/browser/*) to open CDP WebSocket connections to
pane targets. See docs/specs/SPEC_BROWSER_DOM_API.md §6.
window_hwnds: Mutex<HashMap<String, isize>>Per-window opacity HWND registry. Populated by set_window_init_status
once the window is fully shown (CEF Views returns NULL at on_after_created
time). Stored as isize (the raw HWND value) so the map is Send.
Read by set_window_opacity to target exactly one HWND instead of
enumerating all process windows. See SPEC_PER_WINDOW_OPACITY_2026-05-14.md §5.
Implementations§
Source§impl AppState
impl AppState
Sourcepub fn host_dispatch(&self, cmd: HostCommand) -> DispatchOutput
pub fn host_dispatch(&self, cmd: HostCommand) -> DispatchOutput
Phase F.1 — dispatch a command through the host reducer.
Locks host_state, applies the command via reducer::update,
logs emitted events via tracing, and returns the dispatch
output (which contains the events plus the dequeued entry for
DequeuePendingWindowCreation).
Lock-hold time: pure-function reducer call, no I/O — typically
sub-microsecond. Never held across a SendMessage, CEF
callback, or any blocking call (snapshot-and-drop discipline,
see docs/specs/SPEC_PHASE_F_HOST_REDUCER_2026-05-01.md §6).
Sourcepub fn peek_back_pending_window_creation(&self) -> Option<PendingWindowCreation>
pub fn peek_back_pending_window_creation(&self) -> Option<PendingWindowCreation>
Phase F.1 — non-mutating peek at the back of the
pending_window_creations queue.
Used by wrr/win_event.rs::handle_event to label OS-level
WM_CREATE events with the upcoming window’s label. CEF’s
OnAfterCreated (which becomes the dequeue) fires AFTER this
OS event, but the host pushed the entry BEFORE calling
post_create_window, so back-of-queue is the right answer at
this moment.
Snapshot-and-drop: takes the lock, clones the entry, drops the lock. Callers never hold the lock past this call.
Sourcepub fn get_browser(&self, label: &str) -> Option<Browser>
pub fn get_browser(&self, label: &str) -> Option<Browser>
Get a browser handle by label.
Sourcepub fn has_browser(&self, label: &str) -> bool
pub fn has_browser(&self, label: &str) -> bool
Check whether a browser is registered under the given label.
Sourcepub fn browsers_is_empty(&self) -> bool
pub fn browsers_is_empty(&self) -> bool
Are there any registered browsers?
Sourcepub fn list_browser_labels(&self) -> Vec<String>
pub fn list_browser_labels(&self) -> Vec<String>
Snapshot of all registered browser labels (HashMap iteration order;
stable per-HashMap-instance, same characteristics as the original
state.browsers.lock().keys() pattern these helpers replaced).
Sourcepub fn list_browsers(&self) -> Vec<(String, Browser)>
pub fn list_browsers(&self) -> Vec<(String, Browser)>
Snapshot of all registered browsers as (label, Browser) pairs. Ordering is HashMap iteration order; callers must sort if order matters.
Sourcepub fn list_top_level_browsers(&self) -> Vec<(String, Browser)>
pub fn list_top_level_browsers(&self) -> Vec<(String, Browser)>
Snapshot of TOP-LEVEL browsers only — excludes BrowserKind::Pane
child browsers whose main frame is loading untrusted remote
content. Callers emitting JS-injected host events must use this
(or emit_event_to_window) so a hostile page in one pane can’t
observe events meant for the host frontend.
Sourcepub fn first_browser(&self) -> Option<(String, Browser)>
pub fn first_browser(&self) -> Option<(String, Browser)>
First registered browser (for “any browser” callers like command palette routing). Returns the label + Browser pair, or None if the registry is empty.
Sourcepub fn live_browser_pane_label(&self, block_id: &str) -> Option<String>
pub fn live_browser_pane_label(&self, block_id: &str) -> Option<String>
Returns the pane’s label iff entry is Live. Used by op gates
(focus/resize/navigate) — None indicates the pane is missing or
in Closing, in which case the caller must short-circuit rather
than touch the (possibly destroyed) HWND.
Sourcepub fn live_browser_pane_labels(&self) -> Vec<String>
pub fn live_browser_pane_labels(&self) -> Vec<String>
Snapshot of all Live pane labels. Used by defocus_all etc.
Sourcepub fn get_drag_session(&self, drag_id: &str) -> Option<DragSession>
pub fn get_drag_session(&self, drag_id: &str) -> Option<DragSession>
PR #5 H.3 — read-side helper for commands::drag::update_cross_drag.
Returns the active drag session iff its drag_id matches drag_id.
Snapshot-and-drop: clones under lock, drops the lock.
Sourcepub fn host_counts_snapshot(&self) -> (u32, u32)
pub fn host_counts_snapshot(&self) -> (u32, u32)
Atomic snapshot of (user_window_count, unpromoted_pool_count)
for the launcher drift-detection report. Both reads taken
under ONE host_state lock; a two-lock variant races
against promote_pool_window and would let queued pool
windows leak into the user-window count.
Filter rules (mirror list_windows / dispatch_to_renderers):
- exclude pool inventory (
pool.unpromoted∪pool.queue) - exclude
browser-pane-*child HWNDs
pool_count is the size of the pool inventory (unpromoted
∪ queue) — NOT just unpromoted. The launcher’s state.pool
mirror is built from ReportPoolWindowAdded / Removed /
Promoted events. On the host’s unpromoted→queue transition
(when pool_ready fires) NO event is emitted, so the
launcher mirror retains the queued label. Reporting just
unpromoted.len() would under-count and trigger spurious
pool drift while the warm pool is idle and ready.
Sourcepub fn unpromoted_pool_labels_snapshot(&self) -> HashSet<String>
pub fn unpromoted_pool_labels_snapshot(&self) -> HashSet<String>
Snapshot of unpromoted pool labels. Used by orphan
reconciliation and the pool-count report after spawning a
new pool slot. Caller can .contains() against the set or
iterate.
Sourcepub fn is_unpromoted_pool_label(&self, label: &str) -> bool
pub fn is_unpromoted_pool_label(&self, label: &str) -> bool
Single-label check against unpromoted pool set. Used by
client.rs::on_after_created BrowserKind classification.
Sourcepub fn pool_queue_size(&self) -> usize
pub fn pool_queue_size(&self) -> usize
Number of pool windows currently in the renderer-ready queue
(NOT including unpromoted). Used by init_pool to decide
whether to bootstrap more pool windows.
Sourcepub fn user_visibility_snapshot(
&self,
) -> (HashSet<String>, Vec<(String, Browser)>)
pub fn user_visibility_snapshot( &self, ) -> (HashSet<String>, Vec<(String, Browser)>)
Atomic snapshot for user-visibility filtering: pool inventory
(pool.unpromoted ∪ pool.queue) AND the browser registry,
taken under ONE host_state lock acquisition.
Two-lock variants (one snapshot for the pool, one for
list_browsers()) race against promote_pool_window:
between the reads, a label can move from pool.queue to
promoted, leaving the stale inventory excluding a now-real
user window. Atomic snapshot eliminates the gap.
Returns:
pool_inventory: labels inpool.unpromoted∪pool.queue(host-internal, no user UI yet — exclude from user-visible filters and from launcher-event dispatch).browsers: every label → Browser pair currently registered (cheap clone —Browseris a CEF refcounted wrapper).
Sourcepub fn is_quitting(&self) -> bool
pub fn is_quitting(&self) -> bool
PR #5 H.5 — read-side helper for the legacy is_quitting check.
Returns true iff the host has begun draining (BeginDrain
dispatched) OR has fully quit. Replaces the AtomicBool.
Sourcepub fn any_browser_pane_closing(&self) -> bool
pub fn any_browser_pane_closing(&self) -> bool
PR #6 H.7 — cross-state invariant for the 2026-05-02 freeze.
Returns true iff ANY pane is in Closing. Top-level window
creation paths (open_new_window, open_window_at_position,
spawn_pool_window) MUST refuse while this is true: empirically
(SPEC_WINDOW_FLEET_REDUCER_2026-05-02.md), creating a CEF
top-level mid-pane-close hits a Chromium v146 deadlock that
wedges the message loop with HiddenSinceOpen + IPC backpressure
(pending=N rising) and never recovers.
The check is small enough to inline at each call site with no async surface. If it turns out the gate needs to widen (“any pane present” rather than “any pane Closing”), that’s a one-line edit. Spec §5 escape hatch.
Sourcepub fn instance_num(&self, label: &str) -> Option<u32>
pub fn instance_num(&self, label: &str) -> Option<u32>
Phase B.5e — authoritative instance-number lookup. Reads
the launcher-fed shadow_instance_registry, which is the
sole source of truth post-B.5e (host’s
WindowInstanceRegistry was deleted). The shadow is
pre-seeded with {"main": 1} so the very first lookup
during startup resolves before the launcher’s first
WindowInstanceAssigned event arrives.
Sourcepub fn backend_window_id(&self, label: &str) -> Option<String>
pub fn backend_window_id(&self, label: &str) -> Option<String>
Phase B.5 (window_id_map step e) — authoritative
label→backend_window_id lookup. Reads from the
launcher-fed shadow_backend_window_ids. Sole source of
truth post-step-e (host’s window_id_map was deleted).
Sourcepub fn window_meta(&self, label: &str) -> Option<WindowMeta>
pub fn window_meta(&self, label: &str) -> Option<WindowMeta>
Phase B.5 (window_meta step c) — authoritative WindowMeta
lookup. Prefers the launcher-fed shadow_window_meta; falls
back to host’s local window_meta for the race window
where host has just inserted the pre-create handoff but
the launcher’s WindowOpened event hasn’t returned yet.
Same prefer-shadow pattern as instance_num and
backend_window_id.
Sourcepub fn subwindow_children_of(&self, parent_label: &str) -> Vec<String>
pub fn subwindow_children_of(&self, parent_label: &str) -> Vec<String>
Phase B.5 (window_meta step c) — collect labels of Subwindows
whose parent_instance_id points to parent_label. Used by
on_before_close’s cascade-close logic.
Returns the union of matches from shadow_window_meta
(the launcher-fed projection) and host’s window_meta (the
eager pre-create handoff). The union covers a critical race:
a parent may already have one mirrored subwindow AND a newly
opened sibling whose WindowOpened event hasn’t returned to
host yet. The newer sibling lives in host’s window_meta
only; the mirrored one lives in shadow only (post-step-d
the shadow becomes the sole source). Cascade-close MUST
catch both — short-circuiting on shadow-non-empty would
leave the race-window sibling orphaned. (codex P1 PR #591
round-1.)
Dedup is by label; if a label is in both, it’s reported once. Labels collected via a HashSet to dedup, returned as a Vec.
Trait Implementations§
Auto Trait Implementations§
impl !Freeze for AppState
impl !RefUnwindSafe for AppState
impl Send for AppState
impl Sync for AppState
impl Unpin for AppState
impl !UnwindSafe for AppState
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, U> ConvertReturnValue<U> for Twhere
T: Into<U>,
impl<T, U> ConvertReturnValue<U> for Twhere
T: Into<U>,
fn wrap_result(self) -> U
§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.