From bb8383682ee9f70f2cf2dd0ed1e316c864fcbc84 Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Thu, 30 Jul 2026 07:25:05 +0300 Subject: [PATCH] feat(medulla): serve the workflow plane to the hosted orchestrator (#5266) --- .github/workflows/ci-lite.yml | 1 + src/core/jsonrpc.rs | 7 +- src/core/runtime/builder.rs | 7 +- src/core/runtime/services.rs | 22 +- src/openhuman/credentials/ops.rs | 10 + src/openhuman/flows/medulla_bridge.rs | 575 +++++++++++++ src/openhuman/flows/medulla_bridge_tests.rs | 593 +++++++++++++ src/openhuman/flows/mod.rs | 5 + src/openhuman/flows/ops.rs | 99 ++- src/openhuman/socket/event_handlers.rs | 53 ++ src/openhuman/socket/manager.rs | 77 +- src/openhuman/socket/medulla/mod.rs | 194 ++++- src/openhuman/socket/medulla/payloads.rs | 310 ++++++- src/openhuman/socket/medulla/workflows.rs | 872 ++++++++++++++++++++ src/openhuman/socket/mod.rs | 1 + src/openhuman/socket/ops.rs | 87 ++ src/openhuman/socket/schemas.rs | 44 +- src/openhuman/socket/token_provider.rs | 2 +- src/openhuman/socket/ws_loop.rs | 2 + 19 files changed, 2900 insertions(+), 61 deletions(-) create mode 100644 src/openhuman/flows/medulla_bridge.rs create mode 100644 src/openhuman/flows/medulla_bridge_tests.rs create mode 100644 src/openhuman/socket/medulla/workflows.rs create mode 100644 src/openhuman/socket/ops.rs diff --git a/.github/workflows/ci-lite.yml b/.github/workflows/ci-lite.yml index 10951bc94..b117d8f86 100644 --- a/.github/workflows/ci-lite.yml +++ b/.github/workflows/ci-lite.yml @@ -464,6 +464,7 @@ jobs: openhuman/agent_registry/agents/loader.rs openhuman/composio/action_tool.rs openhuman/inference/local/mod.rs + openhuman/socket/ops.rs openhuman/tool_registry/ops_tests.rs openhuman/tool_registry/schemas.rs openhuman/tools/ops_tests.rs diff --git a/src/core/jsonrpc.rs b/src/core/jsonrpc.rs index cec370099..37b9dfdc0 100644 --- a/src/core/jsonrpc.rs +++ b/src/core/jsonrpc.rs @@ -2574,6 +2574,7 @@ pub async fn bootstrap_core_runtime( pub async fn start_core_runtime_services( services: crate::core::runtime::ServiceSet, config: Option<&crate::openhuman::config::Config>, + flows_enabled: bool, ) { let Some(cfg) = config else { log::error!( @@ -2597,7 +2598,11 @@ pub async fn start_core_runtime_services( match crate::openhuman::socket::global_socket_manager() { Some(socket_mgr) => { - crate::core::runtime::services::spawn_socket_auto_connect(services, socket_mgr.clone()); + crate::core::runtime::services::spawn_socket_auto_connect( + services, + socket_mgr.clone(), + flows_enabled, + ); } None => { log::warn!( diff --git a/src/core/runtime/builder.rs b/src/core/runtime/builder.rs index 6910dc2e8..d70bddb34 100644 --- a/src/core/runtime/builder.rs +++ b/src/core/runtime/builder.rs @@ -702,7 +702,12 @@ impl CoreRuntime { /// each service keeps its own runtime config gate. async fn start_selected_services(&self) { use crate::core::runtime::services; - jsonrpc::start_core_runtime_services(self.services, self.config.as_ref()).await; + jsonrpc::start_core_runtime_services( + self.services, + self.config.as_ref(), + self.ctx.domains().flows, + ) + .await; if self.services.heartbeat { services::spawn_login_gated_services(self.ctx.host_kind().is_desktop_shell()); diff --git a/src/core/runtime/services.rs b/src/core/runtime/services.rs index 2db9d80e7..68fba0757 100644 --- a/src/core/runtime/services.rs +++ b/src/core/runtime/services.rs @@ -416,19 +416,20 @@ fn spawn_mcp_reconnect_supervisor(config: Config) { pub fn spawn_socket_auto_connect( services: ServiceSet, socket_mgr: std::sync::Arc, + _flows_enabled: bool, ) { if services.socketio { tokio::spawn(async move { log::info!("[socket] Checking for stored session to auto-connect..."); let config = match Config::load_or_init().await { - Ok(c) => c, + Ok(c) => std::sync::Arc::new(c), Err(e) => { log::debug!("[socket] Config not available for auto-connect: {e}"); return; } }; let api_url = crate::api::config::effective_backend_api_url(&config.api_url); - let token = match crate::api::jwt::get_session_token(&config) { + let _initial_token = match crate::api::jwt::get_session_token(&config) { Ok(Some(t)) => t, Ok(None) => { log::info!( @@ -445,7 +446,22 @@ pub fn spawn_socket_auto_connect( "[socket] Session token found — auto-connecting to {}", api_url ); - if let Err(e) = socket_mgr.connect(&api_url, &token).await { + // Keep the authenticated token and user-scoped workflow bridge in + // one serialized identity transaction. The active profile may have + // changed since CoreRuntime::build(), so the build-time Config is + // not authoritative here. + let _rebind = socket_mgr.lock_identity_rebind().await; + if let Err(e) = socket_mgr.disconnect().await { + log::error!("[socket] Auto-connect could not stop the prior connection: {e}"); + return; + } + #[cfg(feature = "flows")] + if _flows_enabled { + crate::openhuman::flows::medulla_bridge::install(std::sync::Arc::clone(&config)); + } + let provider = + crate::openhuman::socket::token_provider::token_provider_from_config(config); + if let Err(e) = socket_mgr.connect_with_provider(&api_url, provider).await { log::error!("[socket] Auto-connect failed: {e}"); } else { log::info!("[socket] Auto-connect initiated successfully"); diff --git a/src/openhuman/credentials/ops.rs b/src/openhuman/credentials/ops.rs index 865c013e9..bd650eeff 100644 --- a/src/openhuman/credentials/ops.rs +++ b/src/openhuman/credentials/ops.rs @@ -702,6 +702,16 @@ pub async fn clear_session(config: &Config) -> Result