AppState

Struct AppState 

Source
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: String

IPC 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; see docs/specs/portable-agent-working-dirs.md).
  • Installed: ~/.agentmux/ — preserves existing behavior.

AGENTMUX_DATA_HOME env var, if set, overrides both.

§browser_panes: BrowserPaneManager

Embedded browser panes (native CefBrowserView per pane).

§browser_api: BrowserApiState

Browser 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

Source

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).

Source

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.

Source

pub fn get_browser(&self, label: &str) -> Option<Browser>

Get a browser handle by label.

Source

pub fn has_browser(&self, label: &str) -> bool

Check whether a browser is registered under the given label.

Source

pub fn browsers_is_empty(&self) -> bool

Are there any registered browsers?

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

pub fn live_browser_pane_labels(&self) -> Vec<String>

Snapshot of all Live pane labels. Used by defocus_all etc.

Source

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.

Source

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.unpromotedpool.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.

Source

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.

Source

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.

Source

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.

Source

pub fn user_visibility_snapshot( &self, ) -> (HashSet<String>, Vec<(String, Browser)>)

Atomic snapshot for user-visibility filtering: pool inventory (pool.unpromotedpool.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 in pool.unpromotedpool.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 — Browser is a CEF refcounted wrapper).
Source

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.

Source

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.

Source

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.

Source

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).

Source

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.

Source

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§

Source§

impl Default for AppState

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T, U> ConvertParam<U> for T
where T: Into<U>,

§

fn into_raw(self) -> U

§

impl<T, U> ConvertReturnValue<U> for T
where T: Into<U>,

§

fn wrap_result(self) -> U

§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert 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>

Convert 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)

Convert &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)

Convert &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
§

impl<T> DowncastSync for T
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Sync + Send>

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,