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
23 changes: 23 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.

27 changes: 14 additions & 13 deletions app/src-tauri/src/whatsapp_data/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,38 +60,39 @@ pub async fn ensure_store() -> Result<Arc<WhatsAppDataStore>, String> {
/// (`openhuman_core::openhuman::channels::whatsapp_data::methods`) so the two sides never
/// drift on the string key.
pub fn register_native_handlers() {
use openhuman_core::core::event_bus::register_native_global;
// Post-#5459 the native registry is tinybus's, reached through the core's
// global bus handle. `NativeRegistry::register` replaces the old free
// function `register_native_global` one-for-one; the method-name constants
// and handler signatures are unchanged.
let native = openhuman_core::core::bus::BUS.native();

register_native_global::<ListChatsRequest, Vec<WhatsAppChat>, _, _>(
native.register::<ListChatsRequest, Vec<WhatsAppChat>, _, _>(
methods::LIST_CHATS,
|req| async move {
let store = ensure_store().await?;
ops::list_chats(&store, req).map_err(|e| format!("{e:#}"))
},
);
register_native_global::<ListMessagesRequest, Vec<WhatsAppMessage>, _, _>(
native.register::<ListMessagesRequest, Vec<WhatsAppMessage>, _, _>(
methods::LIST_MESSAGES,
|req| async move {
let store = ensure_store().await?;
ops::list_messages(&store, req).map_err(|e| format!("{e:#}"))
},
);
register_native_global::<SearchMessagesRequest, Vec<WhatsAppMessage>, _, _>(
native.register::<SearchMessagesRequest, Vec<WhatsAppMessage>, _, _>(
methods::SEARCH_MESSAGES,
|req| async move {
let store = ensure_store().await?;
ops::search_messages(&store, req).map_err(|e| format!("{e:#}"))
},
);
register_native_global::<IngestRequest, IngestResult, _, _>(
methods::INGEST,
|req| async move {
let store = ensure_store().await?;
// `{e:#}` renders the full anyhow chain so the underlying SQLite
// cause (locked / malformed / FK) survives to the scanner's log.
ops::ingest(&store, req).map_err(|e| format!("[whatsapp_data] ingest failed: {e:#}"))
},
);
native.register::<IngestRequest, IngestResult, _, _>(methods::INGEST, |req| async move {
let store = ensure_store().await?;
// `{e:#}` renders the full anyhow chain so the underlying SQLite
// cause (locked / malformed / FK) survives to the scanner's log.
ops::ingest(&store, req).map_err(|e| format!("[whatsapp_data] ingest failed: {e:#}"))
});
log::info!(
"[whatsapp_data] registered shell native handlers (list_chats / list_messages / search_messages / ingest)"
);
Expand Down
10 changes: 5 additions & 5 deletions app/src/components/settings/panels/VoicePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,11 @@ const VoicePanel = ({ embedded = false }: VoicePanelProps = {}) => {
const slugs = new Set(vs.voiceProviders.map(p => p.slug));
const sttStr =
vs.sttProvider.kind === 'cloud'
// `cloud` is a routing sentinel: it delegates to the configured
// engine, which voice_status reports after resolving it. Seed the
// selector with that effective engine so Settings does not claim
// the backend proxy is in use when a hosted BYOK engine is.
? voiceResponse.stt_engine || 'cloud'
? // `cloud` is a routing sentinel: it delegates to the configured
// engine, which voice_status reports after resolving it. Seed the
// selector with that effective engine so Settings does not claim
// the backend proxy is in use when a hosted BYOK engine is.
voiceResponse.stt_engine || 'cloud'
: vs.sttProvider.kind === 'local'
? vs.sttProvider.engine
: slugs.has(vs.sttProvider.providerSlug)
Expand Down
18 changes: 10 additions & 8 deletions src/core/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,9 @@ pub fn manifest() -> PeerManifest {
.expect("the events interface constant is valid");
PeerManifest::new("openhuman")
.version(
Version::parse(env!("CARGO_PKG_VERSION"))
.unwrap_or_else(|_| Version::new(0, 0, 0)),
Version::parse(env!("CARGO_PKG_VERSION")).unwrap_or_else(|_| Version::new(0, 0, 0)),
)
.provides(InterfaceVersion::provided(
interface,
EVENTS_VERSION,
))
.provides(InterfaceVersion::provided(interface, EVENTS_VERSION))
.consumes(InterfaceVersion::consumed(
EVENTS_INTERFACE
.try_into()
Expand Down Expand Up @@ -183,8 +179,14 @@ mod tests {
fn the_manifest_declares_the_catalog_in_both_directions() {
let manifest = manifest();
let interface = EVENTS_INTERFACE.try_into().unwrap();
assert!(manifest.provided(&interface).is_some(), "openhuman publishes");
assert!(manifest.consumed(&interface).is_some(), "openhuman subscribes");
assert!(
manifest.provided(&interface).is_some(),
"openhuman publishes"
);
assert!(
manifest.consumed(&interface).is_some(),
"openhuman subscribes"
);
}

#[tokio::test]
Expand Down
56 changes: 25 additions & 31 deletions src/core/jsonrpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,12 +298,10 @@ pub async fn invoke_method(state: AppState, method: &str, params: Value) -> Resu
// `scheduler_gate::set_signed_out(false)`. Duplicating that check
// here would pull a domain concern into the transport layer and would
// add an extra config-load round-trip on every 401.
crate::core::bus::BUS.publish(
crate::core::events::DomainEvent::SessionExpired {
source: format!("jsonrpc.invoke_method:{method}"),
reason: sanitized_reason,
},
);
crate::core::bus::BUS.publish(crate::core::events::DomainEvent::SessionExpired {
source: format!("jsonrpc.invoke_method:{method}"),
reason: sanitized_reason,
});
} else if is_unconfirmed_unauthorized_error(msg) {
log::info!(
"[jsonrpc] unconfirmed unauthorized error for method='{}' (not session expiry) — leaving session intact: {}",
Expand Down Expand Up @@ -1663,25 +1661,23 @@ async fn domain_events_handler(headers: axum::http::HeaderMap) -> Response {
.await
.map(|event| (Ok::<_, std::convert::Infallible>(event), rx))
})
.filter_map(
|item| -> Option<Result<Event, std::convert::Infallible>> {
let event = match item {
Ok(ev) => ev,
Err(_) => return None,
};
let domain = event.domain().to_string();
let event_name = event.variant_name();
let agent = event.agent_hint().unwrap_or("").to_string();
let data = json!({
"domain": domain,
"event": event_name,
"agent": agent,
"timestamp": chrono::Utc::now().format("%H:%M:%S").to_string(),
});
let data_str = serde_json::to_string(&data).ok()?;
Some(Ok(Event::default().event(domain).data(data_str)))
},
);
.filter_map(|item| -> Option<Result<Event, std::convert::Infallible>> {
let event = match item {
Ok(ev) => ev,
Err(_) => return None,
};
let domain = event.domain().to_string();
let event_name = event.variant_name();
let agent = event.agent_hint().unwrap_or("").to_string();
let data = json!({
"domain": domain,
"event": event_name,
"agent": agent,
"timestamp": chrono::Utc::now().format("%H:%M:%S").to_string(),
});
let data_str = serde_json::to_string(&data).ok()?;
Some(Ok(Event::default().event(domain).data(data_str)))
});

let config_stream =
futures::stream::once(async move { Ok::<_, std::convert::Infallible>(config_event) });
Expand Down Expand Up @@ -2660,12 +2656,10 @@ pub async fn bootstrap_core_runtime(
Prompt-class external-effect tool calls run unprompted",
host_kind.tag()
);
crate::core::bus::BUS.publish(
crate::core::events::DomainEvent::ApprovalGateDisabled {
host: host_kind.tag().to_string(),
reason: "env-override".to_string(),
},
);
crate::core::bus::BUS.publish(crate::core::events::DomainEvent::ApprovalGateDisabled {
host: host_kind.tag().to_string(),
reason: "env-override".to_string(),
});
}
// Artifact surface bridges DomainEvent::ArtifactReady/Failed onto the web
// channel ("Files in this chat" panel + ArtifactCard updates). This is
Expand Down
4 changes: 2 additions & 2 deletions src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ use serde::Serialize;
pub mod agent_cli;
pub mod all;
pub mod auth;
pub mod bus;
pub mod bus_testing;
pub mod cli;
pub mod cli_capability;
pub mod dispatch;
pub mod event_bind_tokens;
pub mod bus;
pub mod bus_testing;
pub mod events;
// Ungated compile-time marker for the `http-server` gate (#5048) — the desktop
// shell asserts `HTTP_SERVER_COMPILED_IN` so a listener-less core fails the
Expand Down
4 changes: 2 additions & 2 deletions src/openhuman/agent/artifacts/store_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,10 +196,10 @@ async fn validate_artifact_id_rejects_slashes() {

use crate::core::bus::BUS;
use crate::core::events::DomainEvent;
use tinybus::EventHandler;
use tinybus::SubscriptionHandle;
use async_trait::async_trait;
use std::sync::{Arc, Mutex as StdMutex};
use tinybus::EventHandler;
use tinybus::SubscriptionHandle;

#[derive(Clone)]
struct PendingCollector {
Expand Down
28 changes: 10 additions & 18 deletions src/openhuman/agent/bus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -373,9 +373,8 @@ async fn handle_agent_run_turn_on_large_stack(
/// allowing any part of the system to request an agentic turn without
/// depending directly on the agent harness.
pub fn register_agent_handlers() {
BUS.native().register::<AgentTurnRequest, AgentTurnResponse, _, _>(
AGENT_RUN_TURN_METHOD,
|req| {
BUS.native()
.register::<AgentTurnRequest, AgentTurnResponse, _, _>(AGENT_RUN_TURN_METHOD, |req| {
#[cfg(test)]
{
handle_agent_run_turn_on_large_stack(req)
Expand All @@ -384,8 +383,7 @@ pub fn register_agent_handlers() {
{
handle_agent_run_turn(req)
}
},
);
});
tracing::debug!("[agent::bus] registered native handler `{AGENT_RUN_TURN_METHOD}`");
}

Expand Down Expand Up @@ -434,20 +432,16 @@ pub fn register_agent_handlers() {
/// }
/// ```
#[cfg(test)]
pub async fn mock_agent_run_turn<F, Fut>(
handler: F,
) -> crate::core::bus_testing::MockBusGuard
pub async fn mock_agent_run_turn<F, Fut>(handler: F) -> crate::core::bus_testing::MockBusGuard
where
F: Fn(AgentTurnRequest) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<AgentTurnResponse, String>> + Send + 'static,
{
crate::core::bus_testing::mock_bus_stub::<
AgentTurnRequest,
AgentTurnResponse,
F,
Fut,
_,
>(AGENT_RUN_TURN_METHOD, handler, || register_agent_handlers())
crate::core::bus_testing::mock_bus_stub::<AgentTurnRequest, AgentTurnResponse, F, Fut, _>(
AGENT_RUN_TURN_METHOD,
handler,
|| register_agent_handlers(),
)
.await
}

Expand All @@ -461,9 +455,7 @@ where
/// handler with a stub, use [`mock_agent_run_turn`] instead.
#[cfg(test)]
pub async fn use_real_agent_handler() -> tokio::sync::MutexGuard<'static, ()> {
let guard = crate::core::bus_testing::BUS_HANDLER_LOCK
.lock()
.await;
let guard = crate::core::bus_testing::BUS_HANDLER_LOCK.lock().await;
register_agent_handlers();
guard
}
Expand Down
17 changes: 10 additions & 7 deletions src/openhuman/agent/harness/session/runtime_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,16 @@ async fn run_single_publishes_completed_and_error_events() {
crate::core::bus::init().await.expect("bus init");
let events = Arc::new(AsyncMutex::new(Vec::<DomainEvent>::new()));
let events_handler = Arc::clone(&events);
let _handle = crate::core::bus::BUS.get().unwrap().on("runtime-events-test", move |event| {
let events = Arc::clone(&events_handler);
let cloned = event.clone();
Box::pin(async move {
events.lock().await.push(cloned);
})
});
let _handle = crate::core::bus::BUS
.get()
.unwrap()
.on("runtime-events-test", move |event| {
let events = Arc::clone(&events_handler);
let cloned = event.clone();
Box::pin(async move {
events.lock().await.push(cloned);
})
});

let ok_provider: Arc<dyn ChatModel<()>> = Arc::new(StaticModel {
response: Mutex::new(Some(Ok(ChatResponse {
Expand Down
4 changes: 1 addition & 3 deletions src/openhuman/agent/harness/session/turn/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,7 @@ impl Agent {
let mut closed = false;
loop {
match rx.try_recv() {
Ok(crate::core::events::DomainEvent::ComposioIntegrationsChanged {
toolkits,
}) => {
Ok(crate::core::events::DomainEvent::ComposioIntegrationsChanged { toolkits }) => {
saw_signal = true;
log::info!(
"[agent_loop] received composio integrations changed event (active_toolkits={:?})",
Expand Down
4 changes: 2 additions & 2 deletions src/openhuman/agent/learning/extract/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ use async_trait::async_trait;

use crate::core::bus::BUS;
use crate::core::events::DomainEvent;
use tinybus::EventHandler;
use tinybus::SubscriptionHandle;
use crate::openhuman::agent::learning::candidate::{
self, Buffer, CueFamily, EvidenceRef, FacetClass, LearningCandidate,
};
use tinybus::EventHandler;
use tinybus::SubscriptionHandle;

// ── Constants ────────────────────────────────────────────────────────────────

Expand Down
4 changes: 2 additions & 2 deletions src/openhuman/agent/learning/profile_md_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ use async_trait::async_trait;

use crate::core::bus::BUS;
use crate::core::events::DomainEvent;
use tinybus::EventHandler;
use tinybus::SubscriptionHandle;
use crate::openhuman::agent::learning::cache::FacetCache;
use crate::openhuman::integrations::composio::providers::profile_md::replace_managed_block;
use crate::openhuman::memory::store::profile::UserState;
use tinybus::EventHandler;
use tinybus::SubscriptionHandle;

// ── Class → block metadata ────────────────────────────────────────────────────

Expand Down
4 changes: 2 additions & 2 deletions src/openhuman/agent/learning/startup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
use std::path::Path;
use std::sync::OnceLock;

use tinybus::SubscriptionHandle;
use crate::openhuman::memory::global::client_if_ready;
use crate::openhuman::memory::store::MemoryClientRef;
use tinybus::SubscriptionHandle;

static EMAIL_SIG_HANDLE: OnceLock<Option<SubscriptionHandle>> = OnceLock::new();

Expand Down Expand Up @@ -167,7 +167,6 @@ fn register_with_client(
mod tests {
use super::*;
use crate::core::events::DomainEvent;
use tinybus::EventBus;
use crate::openhuman::agent::learning::candidate::Buffer;
use crate::openhuman::agent::learning::extract::signature::{
parse_signature, register_email_signature_subscriber_on,
Expand All @@ -176,6 +175,7 @@ use tinybus::EventBus;
use std::sync::Arc;
use std::time::Duration;
use tempfile::TempDir;
use tinybus::EventBus;

/// Build a real `MemoryClient` against a fresh temp workspace. The temp dir
/// is returned so callers keep it alive for the client's lifetime.
Expand Down
Loading
Loading