Skip to main content

agentmux_srv/server/service/
mod.rs

1// Copyright 2025-2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3
4//! HTTP `/agentmux/service` dispatcher, split into concern-based submodules.
5//!
6//! * [`object`] / [`client`] / [`window`] / [`workspace`] / [`misc`] — the
7//!   per-service RPC handlers `dispatch_service` routes to.
8//! * [`introspect`] — agent-facing read-only tree snapshots + `AgentContext`
9//!   resolution (backs `/api/v1/self`, `/layout`, naming verbs).
10//! * [`object_helpers`] — wave-object read/update/meta primitives.
11//! * [`reducer_helpers`] — reducer dispatch / event publish / compensation.
12//! * [`layout_helpers`] — layout-tree + pending-action writers used by the
13//!   drag-and-drop / tear-off / redock handlers.
14//!
15//! `pub use` re-exports keep every external call site
16//! (`crate::server::service::…`, `super::service::…`) unchanged.
17
18mod client;
19mod credential;
20mod host_ipc;
21mod introspect;
22mod memory_adopt;
23pub(crate) mod layout_helpers;
24mod misc;
25mod object;
26pub(crate) mod object_helpers;
27pub(crate) mod reducer_helpers;
28pub(crate) mod session_restore;
29mod tab_lifecycle;
30mod tab_move;
31mod tear_off;
32mod window;
33mod window_close;
34mod window_create;
35mod window_inherit;
36mod window_mutate;
37mod window_query;
38mod workspace;
39mod workspace_lifecycle;
40
41use axum::{extract::State, response::Json};
42
43use crate::backend::service::WebReturnType;
44
45use super::AppState;
46use crate::backend::service::WebCallType;
47
48// ---- Public surface re-exports (external call sites depend on these) ----
49
50pub(crate) use introspect::{
51    agent_layout, agent_tabs, agent_windows, agent_workspaces, resolve_agent_context,
52    workspace_id_for_tab, AgentContext,
53};
54pub(crate) use layout_helpers::setup_torn_off_block_layout;
55pub(crate) use object_helpers::{schedule_agent_zoom_mirror, update_object_meta};
56pub(crate) use reducer_helpers::{
57    dispatch_to_reducer, publish_events, queue_layout_actions_via_reducer,
58};
59
60pub(super) async fn handle_service(
61    State(state): State<AppState>,
62    body: axum::body::Bytes,
63) -> Json<WebReturnType> {
64    let call: WebCallType = match serde_json::from_slice(&body) {
65        Ok(c) => c,
66        Err(e) => return Json(WebReturnType::error(format!("invalid request body: {e}"))),
67    };
68    Json(run_service_call(&state, &call).await)
69}
70
71/// Dispatch a service call and broadcast any resulting `MuxObjUpdate`s to the
72/// event bus — the shared core of `handle_service`. Factored out so the typed
73/// first-class agent-API verbs (e.g. `/api/v1/window/name`) get byte-identical
74/// persistence + broadcast to a raw `/agentmux/service` call without
75/// re-implementing it. See SPEC_AGENT_API_FIRST_CLASS_SURFACE_2026_06_17.md.
76pub(crate) async fn run_service_call(state: &AppState, call: &WebCallType) -> WebReturnType {
77    let service_start = std::time::Instant::now();
78    let result = dispatch_service(state, call).await;
79    let elapsed = service_start.elapsed();
80    // debug, not info: fires on every HTTP /agentmux/service call — a
81    // meaningful slice (~10%+) of an unrotated 406 MB launcher-log mirror on
82    // a real machine (SPEC_WIN10_PAGEFILE_OOM_CRASH_2026_06_29 P1). Default
83    // production filter is info, so this is now suppressed unless
84    // RUST_LOG=debug is set; still there for perf debugging on demand.
85    tracing::debug!(
86        "[http-perf] {}.{}: {:.2}ms",
87        call.service,
88        call.method,
89        elapsed.as_secs_f64() * 1000.0,
90    );
91
92    // Broadcast every MuxObjUpdate the handler returned so other
93    // clients (additional windows, test harnesses, etc.) learn about
94    // changes they didn't initiate. The calling HTTP client also gets
95    // `updates` in the response body — this broadcast is for
96    // everybody else on the event bus. Before this, only a handful
97    // of handlers (agent.open, blockcontroller events) broadcast
98    // manually, so an external harness's CreateTab / UpdateObject
99    // were invisible to the frontend.
100    //
101    // One batched frame, not one frame per update: these WS frames also
102    // reach the CALLING renderer, and they land BEFORE the HTTP response
103    // body — so N individual frames repaint the UI in N unbatched steps
104    // and the response body's batched application (mos.ts
105    // `updateMuxObjects`) arrives too late to matter (version-guarded to
106    // a no-op). CloseTab's `[delete tab, update workspace]` pair sent as
107    // two frames is exactly the blank-tab flash of
108    // SPEC_TAB_CLOSE_BUTTON_SELECT_FLASH_2026_08_25.md §7.
109    if let Some(updates) = &result.updates {
110        state.event_bus.broadcast_mux_obj_updates(updates);
111    }
112
113    result
114}
115
116async fn dispatch_service(state: &AppState, call: &WebCallType) -> WebReturnType {
117    match call.service.as_str() {
118        "object" => object::handle_object_service(state, call).await,
119        "client" => client::handle_client_service(state, call).await,
120        "credential" => credential::handle_credential_service(state, call).await,
121        "memoryadopt" => memory_adopt::handle_memory_adopt_service(state, call).await,
122        "window" => window::handle_window_service(state, call).await,
123        "workspace" => workspace::handle_workspace_service(state, call).await,
124        "host_ipc" => host_ipc::handle_host_ipc_service(state, call).await,
125        _ => misc::handle_misc_service(state, call).await,
126    }
127}