Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-lite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,7 @@ jobs:
core/cli_tests.rs
core/jsonrpc_tests.rs
core/legacy_aliases.rs
core/runtime/builder.rs
core/runtime/services.rs
openhuman/agent/harness/builtin_definitions.rs
openhuman/agent/harness/definition_tests.rs
Expand Down
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions app/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 35 additions & 0 deletions src/core/runtime/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ pub struct ServiceSet {
pub memory_sync: bool,
/// Orchestration relay-mailbox drain supervisor.
pub orchestration: bool,
/// Browser Companion relay (TinyFlows Chrome extension WebSocket server).
/// Runtime-gated in turn by `config.browser_companion.enabled`. Only
/// meaningful when the `flows` Cargo feature is on — a no-op elsewhere.
pub companion_relay: bool,
}

impl ServiceSet {
Expand All @@ -82,6 +86,7 @@ impl ServiceSet {
integrations: true,
memory_sync: true,
orchestration: true,
companion_relay: true,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}

Expand All @@ -102,6 +107,7 @@ impl ServiceSet {
integrations: false,
memory_sync: false,
orchestration: false,
companion_relay: false,
}
}

Expand All @@ -122,6 +128,7 @@ impl ServiceSet {
integrations: false,
memory_sync: false,
orchestration: false,
companion_relay: false,
}
}

Expand Down Expand Up @@ -152,6 +159,7 @@ impl ServiceSet {
integrations: false,
memory_sync: true,
orchestration: false,
companion_relay: false,
}
}
}
Expand Down Expand Up @@ -722,6 +730,10 @@ impl CoreRuntime {
if self.services.channels {
services::spawn_channels_service();
}
#[cfg(feature = "flows")]
if self.services.companion_relay {
services::spawn_companion_relay_service();
}
}
}

Expand Down Expand Up @@ -911,4 +923,27 @@ mod tests {
assert!(!headless.memory_sync);
assert!(!headless.orchestration);
}

#[test]
fn companion_relay_service_is_desktop_only() {
// The browser-companion relay binds a loopback WebSocket for the Chrome
// extension and must run ONLY on the desktop host — never in the
// headless API, the bare `none()` set, or the embedded runtime.
assert!(
ServiceSet::desktop().companion_relay,
"desktop() must enable companion_relay"
);
assert!(
!ServiceSet::headless_api().companion_relay,
"headless_api() must not enable companion_relay"
);
assert!(
!ServiceSet::none().companion_relay,
"none() must not enable companion_relay"
);
assert!(
!ServiceSet::embedded().companion_relay,
"embedded() must not enable companion_relay"
);
}
}
37 changes: 37 additions & 0 deletions src/core/runtime/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,43 @@ pub fn spawn_channels_service() {
log::debug!("[channels] channels feature disabled at compile time — not spawning listeners");
}

/// Browser Companion relay (TinyFlows Chrome extension WebSocket server).
///
/// Entirely gated behind the `flows` Cargo feature (the `browser_companion`
/// domain is itself `#[cfg(feature = "flows")]`, so this function only
/// exists to be called when the feature is on — the call site in
/// [`crate::core::runtime::builder::CoreBuilder::start_selected_services`]
/// carries the matching `#[cfg]`). Runtime-gated in turn by
/// `config.browser_companion.enabled` inside `start_companion_server`
/// itself, mirroring `spawn_cron_service`'s config-gate pattern.
#[cfg(feature = "flows")]
pub fn spawn_companion_relay_service() {
tokio::spawn(async {
log::debug!("[browser_companion] spawn_companion_relay_service: loading config");
match crate::openhuman::config::Config::load_or_init().await {
Ok(config) => {
if !config.browser_companion.enabled {
log::debug!(
"[browser_companion] spawn_companion_relay_service: disabled via config; skipping"
);
return;
}
log::info!("[browser_companion] spawn_companion_relay_service: starting relay");
if let Err(error) =
crate::openhuman::browser_companion::start_companion_server(&config).await
{
log::error!(
"[browser_companion] spawn_companion_relay_service: start_companion_server failed: {error}"
);
}
}
Err(err) => {
log::warn!("[core] config load failed, skipping browser_companion relay: {err}");
}
}
});
}

/// Which bootstrap jobs a given [`ServiceSet`] enables — the single source of
/// truth for the flag→job mapping.
///
Expand Down
38 changes: 38 additions & 0 deletions src/openhuman/browser_companion/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//! Browser Companion: owns the lifecycle + pairing of the TinyFlows
//! `CompanionServer` — a loopback WebSocket relay the Chrome extension
//! connects to so native workflow runs can drive/observe the user's browser.
//!
//! **Increment 1 scope** (this module): server lifecycle (start/stop),
//! pairing (pair/unpair/rotate secret), and status reporting.
//!
//! **Increment 2** added [`bind_run`]/[`unbind_run`] (Stage C3 — flows
//! wiring): `src/openhuman/flows/ops.rs` calls these around a real
//! `flows_run`/`flows_run_detached` execution whose graph has a
//! `tool_call { slug: "browser" }` node, binding the run's `thread_id` to the
//! caller-selected shared tab so its `slug:"browser"` calls (routed through
//! [`browser_relay`] wrapped in `tinyflows::browser::RoutingToolInvoker`) are
//! authorized. No RPC controllers (`browser_companion.*`) yet — that still
//! lands in a later stage.
//!
//! Entirely gated behind the existing `flows` Cargo feature — this domain
//! rides the same `tinyflows` dependency as `openhuman::flows` /
//! `openhuman::tinyflows`, adds no new feature, and is compiled out
//! wholesale (leaf-gate style, see `AGENTS.md`'s `flows` gate section) when
//! `flows` is off. The one exception is [`crate::openhuman::config::schema::BrowserCompanionConfig`]
//! (`src/openhuman/config/schema/browser_companion.rs`), which stays
//! ungated as inert config data — matching the `MeetConfig` precedent.
//!
//! Spawned at boot by `core::runtime::services::spawn_companion_relay_service`,
//! selected by `ServiceSet::companion_relay`.

mod ops;
mod store;
mod types;

pub use ops::{
bind_run, browser_relay, companion_status, is_extension_connected, pair, rotate_secret,
start_companion_server, stop_companion_server, unbind_run, unpair,
};
pub use types::{BrowserCompanionStatus, PairingInfo, SharedTabView};

pub(crate) const LOG_PREFIX: &str = "[browser_companion]";
Loading
Loading