Feat/orch fixes (#4720)

This commit is contained in:
Steven Enamakel
2026-07-08 19:51:59 -07:00
committed by GitHub
parent 3a38100219
commit e456b77993
148 changed files with 461 additions and 560 deletions
@@ -517,7 +517,7 @@ pub async fn run(goal: &str, backend: &dyn AutomateBackend) -> AutomateOutcome {
}
// Song row pressed, but the backend can't confirm the track (non-macOS).
(_, None) => {
let unverified = matches!(verified, None);
let unverified = verified.is_none();
steps.push(if unverified {
"verify: playback unverified".to_string()
} else {
+4 -4
View File
@@ -115,7 +115,7 @@ pub fn ax_list_elements_filtered(app_name: &str, filter: &str) -> Result<Vec<AXE
if !needle.is_empty() {
elements.retain(|e| e.label.to_lowercase().contains(&needle));
}
return Ok(elements);
Ok(elements)
}
#[cfg(target_os = "windows")]
{
@@ -157,7 +157,7 @@ pub fn ax_press_element(app_name: &str, label: &str) -> Result<String, String> {
.and_then(|v| v.as_str())
.unwrap_or(label)
.to_string();
return Ok(format!("Pressed '{pressed}' in '{app_name}'."));
Ok(format!("Pressed '{pressed}' in '{app_name}'."))
}
#[cfg(target_os = "windows")]
{
@@ -252,9 +252,9 @@ pub fn ax_set_field_value(app_name: &str, label: &str, value: &str) -> Result<St
.and_then(|v| v.as_str())
.unwrap_or(label)
.to_string();
return Ok(format!(
Ok(format!(
"Set '{field}' in '{app_name}' to the provided value."
));
))
}
#[cfg(target_os = "windows")]
{
+2 -7
View File
@@ -104,20 +104,15 @@ pub type AgentGraphRunner =
/// How an agent's turn is driven. Selected per-agent via each folder's
/// `graph.rs::graph()` and injected onto [`AgentDefinition`][super::definition::AgentDefinition].
#[derive(Clone)]
#[derive(Clone, Default)]
pub enum AgentGraph {
/// Run the shared default sub-agent turn graph (`run_subagent_via_graph`).
#[default]
Default,
/// Run this agent's bespoke graph.
Custom(AgentGraphRunner),
}
impl Default for AgentGraph {
fn default() -> Self {
AgentGraph::Default
}
}
impl AgentGraph {
/// Build a custom graph selection from a runner fn. Sugar for
/// [`AgentGraph::Custom`] so a folder's `graph.rs` reads
@@ -5,8 +5,10 @@ use std::fmt;
/// How a message arriving during an active agent turn should be handled.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum QueueMode {
/// Abort the in-flight turn and start fresh (default, backward-compatible).
#[default]
Interrupt,
/// Inject the message at the next safe iteration boundary so the agent
/// sees it mid-turn without restarting.
@@ -24,12 +26,6 @@ pub enum QueueMode {
Parallel,
}
impl Default for QueueMode {
fn default() -> Self {
Self::Interrupt
}
}
impl fmt::Display for QueueMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -1159,7 +1159,7 @@ impl Agent {
// of agent-conversation recall, suppress the prior-chat and
// cross-chat blocks. Defaults to on for None / unset.
.with_agent_conversations(
profile.map_or(true, |p| p.include_agent_conversations),
profile.is_none_or(|p| p.include_agent_conversations),
),
))
.prompt_builder(prompt_builder)
@@ -854,22 +854,22 @@ fn mirror_worker_thread(
);
}
}
ConversationMessage::Chat(c) if c.role == "assistant" => {
if !c.content.trim().is_empty() {
iteration += 1;
append_worker_message(
workspace_dir,
thread_id,
agent_id,
task_id,
c.content.clone(),
"agent",
serde_json::json!({
"iteration": iteration,
"final": extra_final.is_none(),
}),
);
}
ConversationMessage::Chat(c)
if c.role == "assistant" && !c.content.trim().is_empty() =>
{
iteration += 1;
append_worker_message(
workspace_dir,
thread_id,
agent_id,
task_id,
c.content.clone(),
"agent",
serde_json::json!({
"iteration": iteration,
"final": extra_final.is_none(),
}),
);
}
_ => {}
}
@@ -246,7 +246,7 @@ impl Tool for DelegateToPersonalityTool {
run_queue: None,
};
match run_subagent(&definition, &prompt, options).await {
match run_subagent(definition, &prompt, options).await {
Ok(outcome) => {
tracing::debug!(
personality_id = %personality_id,
+1 -1
View File
@@ -151,7 +151,7 @@ fn reentrancy_key(workflow_id: &str, inputs: &Option<serde_json::Value>) -> Stri
match v {
serde_json::Value::Object(map) => {
let mut sorted: Vec<_> = map.iter().collect();
sorted.sort_by(|(left, _), (right, _)| left.cmp(right));
sorted.sort_by_key(|(left, _)| *left);
serde_json::Value::Object(
sorted
.into_iter()
+5 -7
View File
@@ -211,13 +211,11 @@ fn last_balanced_brace_object(text: &str) -> Option<String> {
}
depth += 1;
}
b'}' => {
if depth > 0 {
depth -= 1;
if depth == 0 {
if let Some(s) = start.take() {
best = Some((s, i + 1));
}
b'}' if depth > 0 => {
depth -= 1;
if depth == 0 {
if let Some(s) = start.take() {
best = Some((s, i + 1));
}
}
}
+1 -1
View File
@@ -226,7 +226,7 @@ impl Tool for CallMemoryAgentTool {
// Synchronous path — block until the memory agent finishes.
let started = std::time::Instant::now();
match run_subagent(&definition, &prompt, options).await {
match run_subagent(definition, &prompt, options).await {
Ok(outcome) => {
let elapsed = started.elapsed();
log::info!(
@@ -250,10 +250,7 @@ fn ownership_file_paths(ownership: Option<&str>) -> Result<Vec<PathBuf>, String>
};
let mut paths = Vec::new();
for raw in rest.split([',', '\n']) {
let trimmed = raw
.trim()
.trim_start_matches(|c: char| c == '-' || c == '*')
.trim();
let trimmed = raw.trim().trim_start_matches(['-', '*']).trim();
if trimmed.is_empty() {
continue;
}
@@ -265,19 +265,17 @@ impl Tool for WaitSubagentTool {
"[wait_subagent] outcome=unknown task_id={}",
resolved_task_id
);
Ok(ToolResult::error(format!(
"wait_subagent: no sub-agent was found for that reference. It may have already finished and \
been collected, or the task_id is wrong."
)))
Ok(ToolResult::error("wait_subagent: no sub-agent was found for that reference. It may have already finished and \
been collected, or the task_id is wrong.".to_string()))
}
Err(WaitError::NotOwned) => {
log::debug!(
"[wait_subagent] outcome=not_owned task_id={}",
resolved_task_id
);
Ok(ToolResult::error(format!(
"wait_subagent: that sub-agent was not started by this agent."
)))
Ok(ToolResult::error(
"wait_subagent: that sub-agent was not started by this agent.".to_string(),
))
}
}
}
@@ -241,7 +241,7 @@ pub fn enforce_workspace_path(
policy_id = %descriptor.policy_id,
"[workspace] workspace_violation_out_of_root"
);
let _ = publish_global(DomainEvent::WorkspaceViolation {
publish_global(DomainEvent::WorkspaceViolation {
path: rendered.clone(),
});
Err(WorktreeError::OutsideWorkspace(path.to_path_buf()))
+1 -1
View File
@@ -136,7 +136,7 @@ fn scrub_paths(input: &str) -> String {
// Skip past the username segment up to the next path
// separator (or end of input).
let rest = &input[i..];
match rest.find(|c: char| c == '/' || c == '\\') {
match rest.find(['/', '\\']) {
Some(end) => i += end,
None => i = input.len(),
}
+4 -12
View File
@@ -4,19 +4,15 @@ use serde::{Deserialize, Serialize};
/// The category of an artifact produced by the agent.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum ArtifactKind {
Presentation,
Document,
Image,
#[default]
Other,
}
impl Default for ArtifactKind {
fn default() -> Self {
Self::Other
}
}
impl ArtifactKind {
pub fn as_str(&self) -> &'static str {
match self {
@@ -42,18 +38,14 @@ impl ArtifactKind {
/// Lifecycle status of an artifact.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum ArtifactStatus {
#[default]
Pending,
Ready,
Failed,
}
impl Default for ArtifactStatus {
fn default() -> Self {
Self::Pending
}
}
impl ArtifactStatus {
pub fn as_str(&self) -> &'static str {
match self {
+1 -15
View File
@@ -11,10 +11,7 @@ mod types;
#[path = "../web_errors.rs"]
mod web_errors;
pub(crate) use web_errors::{
classify_inference_error, inference_budget_exceeded_user_message,
is_inference_budget_exceeded_error,
};
pub(crate) use web_errors::classify_inference_error;
#[cfg(any(test, debug_assertions))]
#[allow(unused_imports)]
pub(crate) use web_errors::{
@@ -51,12 +48,8 @@ pub use schemas::{
// Helpers re-exported for tests
pub(crate) use ops::{event_session_id_for, key_for};
pub(crate) use progress_bridge::spawn_progress_bridge;
pub(crate) use session::{compose_system_prompt_suffix, locale_reply_directive};
// Schema field helpers re-exported for tests
pub(crate) use schemas::{
json_output, optional_bool, optional_f64, optional_string, optional_u64, required_string,
};
// Test helpers (debug/test builds only)
#[cfg(any(test, debug_assertions))]
@@ -64,13 +57,6 @@ pub use ops::set_test_forced_run_chat_task_error;
#[cfg(any(test, debug_assertions))]
pub use ops::{set_test_run_chat_task_block, TestRunChatTaskBlock};
#[cfg(any(test, debug_assertions))]
pub(crate) use ops::THREAD_SESSIONS;
#[cfg(any(test, debug_assertions))]
pub(crate) use session::{normalize_model_override, provider_role_for_model_override};
#[cfg(any(test, debug_assertions))]
pub(crate) use types::WebChatParams;
#[cfg(any(test, debug_assertions))]
pub mod test_support {
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -186,10 +186,8 @@ pub(crate) async fn process_channel_runtime_message(
// a fresh agent turn would cancel the parked tool call. Any other text
// falls through to the normal dispatch (the user is redirecting). Mirrors
// the same intercept in `channels/providers/web.rs:493-525`.
if channel_has_approval_surface(&msg.channel) {
if try_route_approval_reply(&msg).await {
return;
}
if channel_has_approval_surface(&msg.channel) && try_route_approval_reply(&msg).await {
return;
}
// Fire typing indicator as early as possible — before any async I/O — so the
@@ -385,16 +383,14 @@ pub(crate) async fn process_channel_runtime_message(
}
}
}
AgentProgress::ToolCallStarted { tool_name, .. } => {
if accumulated.is_empty() {
let _ = channel
.update_draft(
&reply_target,
&draft_id,
&format!("Working ({})...", tool_name),
)
.await;
}
AgentProgress::ToolCallStarted { tool_name, .. } if accumulated.is_empty() => {
let _ = channel
.update_draft(
&reply_target,
&draft_id,
&format!("Working ({})...", tool_name),
)
.await;
}
_ => {}
}
@@ -444,7 +444,7 @@ pub async fn run_dispatch_harness(options: DispatchHarnessOptions) -> DispatchHa
inference_url: None,
reliability: Arc::new(ReliabilityConfig::default()),
provider_runtime_options: ProviderRuntimeOptions::default(),
workspace_dir: Arc::new(PathBuf::from(std::env::temp_dir())),
workspace_dir: Arc::new(std::env::temp_dir()),
message_timeout_secs: options.timeout_secs,
multimodal: MultimodalConfig::default(),
multimodal_files: MultimodalFileConfig::default(),
@@ -15,12 +15,14 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
/// - Token budget per background cycle
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize_repr, Deserialize_repr, JsonSchema)]
#[repr(u8)]
#[derive(Default)]
pub enum AgentActivityLevel {
/// No background processing. Syncs only on manual press.
Off = 0,
/// Sync sources once per day. No proactive messages.
Minimal = 1,
/// Sync every hour. Daily digest. Suggests actions. (default)
#[default]
Moderate = 2,
/// Sync every 10 min. Monitors channels, triages, drafts replies.
Active = 3,
@@ -95,12 +97,6 @@ impl AgentActivityLevel {
}
}
impl Default for AgentActivityLevel {
fn default() -> Self {
Self::Moderate
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -39,15 +39,11 @@ impl Default for CapabilityProviderConfig {
/// Trust state for an external capability provider.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum CapabilityProviderTrustState {
/// Provider metadata is accepted, but capabilities from it are not trusted.
#[default]
Untrusted,
/// Provider is explicitly trusted by local config.
Trusted,
}
impl Default for CapabilityProviderTrustState {
fn default() -> Self {
Self::Untrusted
}
}
+1 -6
View File
@@ -56,14 +56,9 @@ pub enum SandboxBackend {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
#[derive(Default)]
pub struct ResourceLimitsConfig {}
impl Default for ResourceLimitsConfig {
fn default() -> Self {
Self {}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct AuditConfig {
+1 -10
View File
@@ -17,6 +17,7 @@ fn default_diagram_viewer_refresh_interval_seconds() -> u64 {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
#[derive(Default)]
pub struct DashboardConfig {
#[serde(default)]
pub event_stream: EventStreamConfig,
@@ -26,16 +27,6 @@ pub struct DashboardConfig {
pub diagram_viewer: DiagramViewerConfig,
}
impl Default for DashboardConfig {
fn default() -> Self {
Self {
event_stream: EventStreamConfig::default(),
model_health: ModelHealthConfig::default(),
diagram_viewer: DiagramViewerConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct EventStreamConfig {
@@ -13,7 +13,7 @@ use anyhow::{Context, Result};
use std::collections::HashSet;
use std::path::Path;
use std::sync::{Mutex, OnceLock};
use tokio::fs::{self, File, OpenOptions};
use tokio::fs::{self, OpenOptions};
use tokio::io::AsyncWriteExt;
static WARNED_WORLD_READABLE_CONFIGS: OnceLock<Mutex<HashSet<std::path::PathBuf>>> =
+1 -7
View File
@@ -7,18 +7,12 @@ mod impl_load;
mod migrate;
mod secrets;
pub(crate) use env::EnvLookup;
pub(crate) use env::ProcessEnv;
pub use dirs::{
action_dir_env_override, active_user_marker_path, clear_active_user, default_action_dir,
default_projects_dir, default_root_openhuman_dir, pre_login_user_dir, read_active_user_id,
resolve_action_dir, user_openhuman_dir, write_active_user_id, ACTION_DIR_ENV_VAR,
MEMORY_SYNC_INTERVAL_SECS_ENV_VAR, PRE_LOGIN_USER_ID, PROJECTS_DIR_ENV_VAR,
resolve_action_dir, user_openhuman_dir, write_active_user_id, PRE_LOGIN_USER_ID,
};
pub(crate) use dirs::persist_active_workspace_config_dir;
// redact_url_for_log is pub(super) for the schema module; tests inside load
// can access it because they are a submodule and use `use super::*`.
pub(super) use migrate::redact_url_for_log;
+1 -11
View File
@@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize};
/// method below returns `false` regardless of these values.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
#[derive(Default)]
pub struct LocalAiUsage {
/// When true (and `runtime_enabled`), use the local model for embedding
/// generation instead of the cloud backend.
@@ -28,17 +29,6 @@ pub struct LocalAiUsage {
pub subconscious: bool,
}
impl Default for LocalAiUsage {
fn default() -> Self {
Self {
embeddings: false,
heartbeat: false,
learning_reflection: false,
subconscious: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct LocalAiConfig {
+6 -18
View File
@@ -14,8 +14,10 @@ use serde::{Deserialize, Serialize};
/// Controls whether the bot auto-joins meetings from the calendar.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum AutoJoinPolicy {
/// Prompt the user before every join (default).
#[default]
AskEachTime,
/// Always join without prompting.
Always,
@@ -23,17 +25,13 @@ pub enum AutoJoinPolicy {
Never,
}
impl Default for AutoJoinPolicy {
fn default() -> Self {
Self::AskEachTime
}
}
/// Controls whether post-call summaries are generated automatically.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum AutoSummarizePolicy {
/// Ask the user after the call ends (default).
#[default]
Ask,
/// Always generate a summary.
Always,
@@ -41,28 +39,18 @@ pub enum AutoSummarizePolicy {
Never,
}
impl Default for AutoSummarizePolicy {
fn default() -> Self {
Self::Ask
}
}
/// Which calendar data source feeds Google Meet detection and auto-join.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum CalendarProvider {
/// Composio-based Google Calendar sync (default; broad OAuth scopes).
#[default]
Composio,
/// Recall.ai Calendar V1 OAuth (less-invasive: read-only events + email).
Recall,
}
impl Default for CalendarProvider {
fn default() -> Self {
Self::Composio
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct MeetConfig {
@@ -7,8 +7,10 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum SchedulerGateMode {
/// Decide based on power + CPU + deployment-mode signals.
#[default]
Auto,
/// Always run background AI flat-out (server / power-user setting).
AlwaysOn,
@@ -26,12 +28,6 @@ impl SchedulerGateMode {
}
}
impl Default for SchedulerGateMode {
fn default() -> Self {
Self::Auto
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct SchedulerGateConfig {
+3 -14
View File
@@ -20,19 +20,12 @@ pub struct StorageProviderSection {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
#[derive(Default)]
pub struct StorageProviderConfig {
#[serde(default)]
pub provider: String,
}
impl Default for StorageProviderConfig {
fn default() -> Self {
Self {
provider: String::new(),
}
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema)]
#[allow(clippy::struct_excessive_bools)]
#[serde(default)]
@@ -175,8 +168,10 @@ impl std::fmt::Debug for MemoryConfig {
/// local-only and isn't governed by this enum.
#[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
#[derive(Default)]
pub enum LlmBackend {
/// Route through the OpenHuman backend (default).
#[default]
Cloud,
/// Use the local Ollama path configured via `llm_extractor_*` /
/// `llm_summariser_*`.
@@ -202,12 +197,6 @@ impl LlmBackend {
}
}
impl Default for LlmBackend {
fn default() -> Self {
Self::Cloud
}
}
fn default_llm_backend() -> LlmBackend {
LlmBackend::default()
}
+2 -6
View File
@@ -122,7 +122,9 @@ pub struct HttpHeader {
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind", rename_all = "snake_case")]
#[derive(Default)]
pub enum McpAuthConfig {
#[default]
None,
BearerToken {
token: String,
@@ -146,12 +148,6 @@ pub enum McpAuthConfig {
},
}
impl Default for McpAuthConfig {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
pub struct McpClientIdentityConfig {
+2 -2
View File
@@ -12,11 +12,11 @@ pub use http::{CurlConfig, HttpRequestConfig};
pub use integrations::{
ComposioConfig, ComputerControlConfig, IntegrationToggle, IntegrationsConfig,
PolymarketClobCredentials, PolymarketConfig, SecretsConfig, COMPOSIO_MODE_BACKEND,
COMPOSIO_MODE_DIRECT, INTEGRATION_MODE_BYO, INTEGRATION_MODE_MANAGED,
COMPOSIO_MODE_DIRECT,
};
pub use mcp::{
GitbooksConfig, HttpHeader, McpAuthConfig, McpClientConfig, McpClientIdentityConfig,
McpRegistryAuthConfig, McpServerConfig,
McpServerConfig,
};
pub use multimodal::{MultimodalConfig, MultimodalFileConfig};
pub use search::{
+2 -6
View File
@@ -6,19 +6,15 @@ use serde::{Deserialize, Serialize};
/// How `update.run` should complete after staging a new binary.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum UpdateRestartStrategy {
/// Request an in-process self-restart immediately after staging.
#[default]
SelfReplace,
/// Stage the new binary and leave restart to an external supervisor.
Supervisor,
}
impl Default for UpdateRestartStrategy {
fn default() -> Self {
Self::SelfReplace
}
}
/// Configuration for periodic self-update checks against GitHub Releases.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(default)]
-1
View File
@@ -3,7 +3,6 @@ mod helpers;
mod schema_defs;
pub use controllers::{all_controller_schemas, all_registered_controllers};
pub use schema_defs::schemas;
// Re-export items that schemas_tests.rs accesses via `use super::*`.
// The test module is `schemas::tests` so `super::` resolves to `schemas`.
+5 -6
View File
@@ -1263,7 +1263,7 @@ impl AuthProfilesStore {
let is_already_exists = e
.chain()
.find_map(|e| e.downcast_ref::<std::io::Error>())
.map_or(false, |ioe| ioe.kind() == std::io::ErrorKind::AlreadyExists);
.is_some_and(|ioe| ioe.kind() == std::io::ErrorKind::AlreadyExists);
if is_already_exists {
// A lock file recording our own pid can only be a
@@ -1374,15 +1374,14 @@ impl AuthProfilesStore {
.modified()
.ok()
.and_then(|mtime| std::time::SystemTime::now().duration_since(mtime).ok());
let too_old = age.map_or(false, |a| a >= Duration::from_millis(STALE_LOCK_AGE_MS));
let too_old = age.is_some_and(|a| a >= Duration::from_millis(STALE_LOCK_AGE_MS));
// A pidless lock needs only a short grace: no healthy holder leaves the
// file without a `pid=` line for more than the microsecond gap between
// `create_new` and the write, so anything older is abandoned. If mtime
// is unreadable (clock skew, platform limitation) default to stale —
// no legitimate in-flight writer would be undetectable for that long.
let malformed_too_old = age.map_or(true, |a| {
a >= Duration::from_millis(MALFORMED_LOCK_GRACE_MS)
});
let malformed_too_old =
age.is_none_or(|a| a >= Duration::from_millis(MALFORMED_LOCK_GRACE_MS));
let content = match fs::read_to_string(&self.lock_path) {
Ok(s) => s,
@@ -1616,7 +1615,7 @@ fn in_process_lock_for(path: &Path) -> &'static Mutex<()> {
.unwrap_or_else(|poisoned| poisoned.into_inner());
// Deref the `&mut &'static Mutex<()>` to copy the `'static` reference out,
// so the returned handle is not tied to the dropped `map` guard.
*map.entry(key)
map.entry(key)
.or_insert_with(|| &*Box::leak(Box::new(Mutex::new(()))))
}
+22 -25
View File
@@ -636,9 +636,7 @@ async fn execute_job_with_retry(
&& !local_unreachable
&& !permanent_config_halt
{
let report_message = last_agent_error
.as_deref()
.unwrap_or_else(|| last_output.as_str());
let report_message = last_agent_error.as_deref().unwrap_or(last_output.as_str());
crate::core::observability::report_error(
report_message,
"cron",
@@ -1188,29 +1186,28 @@ async fn deliver_if_configured(
// Announce delivery — the cron job specifies the exact channel
// and target. Used for explicit channel-targeted output.
"announce" => {
if deliver_to_chat {
let channel = delivery.channel.as_deref().ok_or_else(|| {
anyhow::anyhow!("delivery.channel is required for announce mode")
})?;
let target = delivery
.to
.as_deref()
.ok_or_else(|| anyhow::anyhow!("delivery.to is required for announce mode"))?;
"announce" if deliver_to_chat => {
let channel = delivery
.channel
.as_deref()
.ok_or_else(|| anyhow::anyhow!("delivery.channel is required for announce mode"))?;
let target = delivery
.to
.as_deref()
.ok_or_else(|| anyhow::anyhow!("delivery.to is required for announce mode"))?;
tracing::debug!(
job_id = %job.id,
channel = %channel,
target = %target,
"[cron] publishing CronDeliveryRequested event"
);
publish_global(DomainEvent::CronDeliveryRequested {
job_id: job.id.clone(),
channel: channel.to_string(),
target: target.to_string(),
output: output.to_string(),
});
}
tracing::debug!(
job_id = %job.id,
channel = %channel,
target = %target,
"[cron] publishing CronDeliveryRequested event"
);
publish_global(DomainEvent::CronDeliveryRequested {
job_id: job.id.clone(),
channel: channel.to_string(),
target: target.to_string(),
output: output.to_string(),
});
}
// No delivery configured — output is stored in last_output only.
+6
View File
@@ -15,6 +15,12 @@ use super::jail::{Jail, JailBackend};
pub struct SeatbeltBackend;
impl Default for SeatbeltBackend {
fn default() -> Self {
Self::new()
}
}
impl SeatbeltBackend {
pub fn new() -> Self {
Self
+1 -1
View File
@@ -81,7 +81,7 @@ pub(crate) fn check_handoff_with_items(
let matched = if keyword.contains(' ') {
lower.contains(keyword)
} else {
tokens.iter().any(|t| *t == keyword)
tokens.contains(&keyword)
};
if !matched {
continue;
+3 -3
View File
@@ -93,7 +93,7 @@ pub fn start_session(
drop(guard);
// Publish session-started event for Socket.IO bridge / subscribers.
let _ = crate::core::event_bus::publish_global(
crate::core::event_bus::publish_global(
crate::core::event_bus::DomainEvent::CompanionSessionStarted {
session_id: session_id.clone(),
ttl_secs,
@@ -125,7 +125,7 @@ pub fn stop_session(
);
drop(guard);
let _ = crate::core::event_bus::publish_global(
crate::core::event_bus::publish_global(
crate::core::event_bus::DomainEvent::CompanionSessionEnded {
session_id,
reason: reason.clone(),
@@ -170,7 +170,7 @@ pub fn session_status() -> CompanionSessionStatus {
info!(
"{LOG_PREFIX} auto-expiring stale session id={stale_id} turns={turn_count}"
);
let _ = crate::core::event_bus::publish_global(
crate::core::event_bus::publish_global(
crate::core::event_bus::DomainEvent::CompanionSessionEnded {
session_id: stale_id,
reason: "ttl_expired".into(),
+3 -2
View File
@@ -240,7 +240,7 @@ async fn handle_tunnel_frame(channel_id: &str, payload_b64: &str) {
};
// Decrypt: nonce(24) || ciphertext+tag at offset 33.
let inner_frame = &frame_bytes[33..];
match {
let res = {
// TunnelCipher::open expects version(1)||nonce(24)||ct+tag, but we already
// stripped the eph_pub prefix. Reconstruct a plain open call by using
// XChaCha20 directly on nonce||ct (inner_frame).
@@ -256,7 +256,8 @@ async fn handle_tunnel_frame(channel_id: &str, payload_b64: &str) {
aead.decrypt(nonce, &inner_frame[24..])
.map_err(|_| "[devices/bus] AEAD decrypt failed on handshake frame".to_string())
}
} {
};
match res {
Ok(plaintext_bytes) => match String::from_utf8(plaintext_bytes) {
Ok(s) => parse_handshake_payload(&s),
Err(_) => {
+1 -1
View File
@@ -457,7 +457,7 @@ impl EmbeddingProvider for OllamaEmbedding {
// Reconstruct full-length result with zero-vectors for blank positions.
let mut result = vec![Vec::new(); texts.len()];
for ((orig_idx, _), embedding) in live.iter().zip(payload.embeddings.into_iter()) {
for ((orig_idx, _), embedding) in live.iter().zip(payload.embeddings) {
result[*orig_idx] = embedding;
}
+6
View File
@@ -42,6 +42,12 @@ pub struct FileStateCoordinator {
pub(crate) path_locks: RwLock<HashMap<PathBuf, Arc<Mutex<()>>>>,
}
impl Default for FileStateCoordinator {
fn default() -> Self {
Self::new()
}
}
impl FileStateCoordinator {
pub fn new() -> Self {
Self {
+12 -9
View File
@@ -1319,15 +1319,16 @@ impl Tool for DryRunWorkflowTool {
.iter()
.filter(|step| tool_call_node_ids.contains(step.node_id.as_str()))
.flat_map(|step| {
step.diagnostics.iter().filter_map(|diag| {
(diag.location == "args" || diag.location.starts_with("args.")).then(|| {
step.diagnostics
.iter()
.filter(|&diag| (diag.location == "args" || diag.location.starts_with("args.")))
.map(|diag| {
json!({
"node_id": step.node_id,
"location": diag.location,
"expression": diag.expression,
})
})
})
})
.collect();
@@ -1345,8 +1346,10 @@ impl Tool for DryRunWorkflowTool {
.iter()
.filter(|step| agent_node_ids.contains(step.node_id.as_str()))
.flat_map(|step| {
step.diagnostics.iter().filter_map(|diag| {
(diag.location == "prompt").then(|| {
step.diagnostics
.iter()
.filter(|&diag| (diag.location == "prompt"))
.map(|diag| {
json!({
"node_id": step.node_id,
"location": diag.location,
@@ -1355,7 +1358,6 @@ impl Tool for DryRunWorkflowTool {
make the prompt a plain instruction.",
})
})
})
})
.collect();
@@ -1370,8 +1372,10 @@ impl Tool for DryRunWorkflowTool {
.iter()
.filter(|step| agent_node_ids.contains(step.node_id.as_str()))
.flat_map(|step| {
step.diagnostics.iter().filter_map(|diag| {
(diag.location == "input_context").then(|| {
step.diagnostics
.iter()
.filter(|&diag| (diag.location == "input_context"))
.map(|diag| {
json!({
"node_id": step.node_id,
"location": diag.location,
@@ -1381,7 +1385,6 @@ impl Tool for DryRunWorkflowTool {
trigger), not an expression that resolves to null.",
})
})
})
})
.collect();
+1 -1
View File
@@ -79,7 +79,7 @@ fn read_string_array(v: &Value, key: &str) -> Vec<String> {
.filter_map(|e| e.as_str())
.map(str::trim)
.filter(|s| !s.is_empty())
.map(|s| truncate(s))
.map(truncate)
.collect()
})
.unwrap_or_default()
+1 -1
View File
@@ -1443,7 +1443,7 @@ fn title_case_toolkit(toolkit: &str) -> String {
return String::new();
}
trimmed
.split(|c| c == '_' || c == '-' || c == ' ')
.split(['_', '-', ' '])
.filter(|w| !w.is_empty())
.map(|word| {
let mut chars = word.chars();
+2 -6
View File
@@ -234,8 +234,10 @@ pub struct FlowRun {
/// actually saved via `flows_create`, the frontend marks it `Built`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum SuggestionStatus {
/// Freshly discovered, awaiting the user's decision. The default.
#[default]
New,
/// The user dismissed the card; kept for dedupe, never re-surfaced.
Dismissed,
@@ -243,12 +245,6 @@ pub enum SuggestionStatus {
Built,
}
impl Default for SuggestionStatus {
fn default() -> Self {
Self::New
}
}
impl SuggestionStatus {
/// The stable lowercase token persisted in SQLite / crossed over RPC.
pub fn as_str(self) -> &'static str {
-1
View File
@@ -41,7 +41,6 @@ mod ollama;
pub(crate) mod process_util;
pub mod profile;
pub(crate) mod provider;
pub(crate) use model_requirements::{evaluate_context, ContextEligibility, MIN_CONTEXT_TOKENS};
pub(crate) use ollama::{
ollama_base_url, ollama_base_url_from_config, validate_ollama_url, OLLAMA_BASE_URL,
};
@@ -7,8 +7,6 @@ mod server;
mod util;
// Re-export free functions that form the public/crate API of this module.
pub(crate) use util::interrupted_pull_settle_window_secs;
pub(crate) use util::kill_pid_by_id;
pub(crate) use util::test_ollama_connection;
#[cfg(test)]
@@ -388,7 +388,7 @@ pub(crate) fn prompt_cache_for_compatible_slug(
// `openai:gpt-5.1` style slug still resolves to the `openai` family.
let normalized = slug.trim().to_ascii_lowercase();
let family = normalized
.split(|c| c == ':' || c == '/' || c == '-')
.split([':', '/', '-'])
.next()
.unwrap_or("")
.trim();
@@ -9,8 +9,7 @@ use super::compatible_parse::{
parse_tool_calls_from_content_json,
};
use super::compatible_types::{
ApiChatResponse, Message, MessageContent, NativeChatRequest, NativeMessage, ResponsesRequest,
ToolCall,
ApiChatResponse, MessageContent, NativeMessage, ResponsesRequest, ToolCall,
};
use super::OpenAiCompatibleProvider;
@@ -4,7 +4,7 @@ use crate::openhuman::inference::provider::ProviderDelta;
use super::compatible_dump::dump_response_if_enabled;
use super::compatible_repeat::{StreamRepeatDetector, STREAM_REPEAT_THRESHOLD};
use super::compatible_types::{
ApiChatResponse, ApiUsage, Choice, Function, NativeChatRequest, OpenHumanMeta, ResponseMessage,
ApiChatResponse, ApiUsage, Choice, NativeChatRequest, OpenHumanMeta, ResponseMessage,
StreamChunkResponse, StreamingToolCall, ToolCall,
};
use super::OpenAiCompatibleProvider;
+1 -1
View File
@@ -2100,7 +2100,7 @@ pub(super) fn redact_endpoint(url: &str) -> String {
if let Some(rest) = trimmed.split_once("://") {
let scheme = rest.0;
let authority = rest.1.split('/').next().unwrap_or("");
let host = authority.split('@').last().unwrap_or(authority);
let host = authority.split('@').next_back().unwrap_or(authority);
let host_no_query = host.split('?').next().unwrap_or(host);
return format!("{}://{}", scheme, host_no_query);
}
@@ -311,7 +311,7 @@ pub fn parse_models_response(body: &serde_json::Value) -> Result<Vec<ModelInfo>,
let is_success_envelope = obj
.get("object")
.and_then(|value| value.as_str())
.map_or(true, |object| object.eq_ignore_ascii_case("list"));
.is_none_or(|object| object.eq_ignore_ascii_case("list"));
if data_value.is_null() && is_success_envelope {
log::info!(
+53 -49
View File
@@ -21,7 +21,7 @@
//! wrapper stays authoritative for single-attempt retry on the live path.
use super::traits::{
ChatMessage, ChatRequest, ChatResponse, StreamChunk, StreamError, StreamOptions, StreamResult,
ChatMessage, ChatRequest, ChatResponse, StreamChunk, StreamOptions, StreamResult,
};
use super::Provider;
use crate::openhuman::inference::provider::record_resolved_provider_route;
@@ -243,18 +243,19 @@ impl Provider for ReliableProvider {
);
// On rate-limit, try rotating API key
if rate_limited && !non_retryable_rate_limit {
if self.rotate_key().is_some() {
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if rate_limited
&& !non_retryable_rate_limit
&& self.rotate_key().is_some()
{
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if non_retryable {
@@ -379,18 +380,19 @@ impl Provider for ReliableProvider {
&error_detail,
);
if rate_limited && !non_retryable_rate_limit {
if self.rotate_key().is_some() {
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if rate_limited
&& !non_retryable_rate_limit
&& self.rotate_key().is_some()
{
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if non_retryable {
@@ -544,18 +546,19 @@ impl Provider for ReliableProvider {
&error_detail,
);
if rate_limited && !non_retryable_rate_limit {
if self.rotate_key().is_some() {
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if rate_limited
&& !non_retryable_rate_limit
&& self.rotate_key().is_some()
{
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if non_retryable {
@@ -673,18 +676,19 @@ impl Provider for ReliableProvider {
&error_detail,
);
if rate_limited && !non_retryable_rate_limit {
if self.rotate_key().is_some() {
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if rate_limited
&& !non_retryable_rate_limit
&& self.rotate_key().is_some()
{
tracing::info!(
provider = provider_name,
error = %error_detail,
key_slot = %rotated_key_log_detail(
self.key_index.load(Ordering::Relaxed),
self.api_keys.len()
),
"Rate limited, rotated API key"
);
}
if non_retryable {
+1 -1
View File
@@ -51,7 +51,7 @@ pub(super) fn hex_encode(data: &[u8]) -> String {
/// Decode a hex string into bytes.
pub(super) fn hex_decode(hex: &str) -> Result<Vec<u8>, String> {
if hex.len() % 2 != 0 {
if !hex.len().is_multiple_of(2) {
return Err("hex string has odd length".to_string());
}
(0..hex.len())
+1 -1
View File
@@ -294,7 +294,7 @@ impl SecretStore {
};
let hex_key = read_result.with_context(|| {
let mut msg = format!(
let msg = format!(
"Failed to read secret key file at {}",
self.key_path.display()
);
+1 -1
View File
@@ -232,7 +232,7 @@ pub fn record_turn(
// ── B. Edit-window detector ───────────────────────────────────────
if let Some(prev_at) = prev_agent_at {
let gap = user_timestamp - prev_at;
if gap >= 0.0 && gap < EDIT_WINDOW_SECS {
if (0.0..EDIT_WINDOW_SECS).contains(&gap) {
let lower = user_message.to_ascii_lowercase();
// Pattern → (key, value) pairs.
let patterns: &[(&str, &str, &str)] = &[
+7 -7
View File
@@ -212,7 +212,7 @@ pub fn parse_signature(
if loc_idx < sig_lines.len() {
let t = sig_lines[loc_idx].trim();
// Skip if already matched as role or employer.
let already_used = role_line_idx.map_or(false, |ri| ri == loc_idx);
let already_used = role_line_idx == Some(loc_idx);
if !already_used {
if let Some(loc) = extract_location(t) {
candidates.push(LearningCandidate {
@@ -361,7 +361,7 @@ fn extract_timezone(s: &str) -> Option<&str> {
let after = &s[pos + tz.len()..];
let after_ok = after.is_empty()
|| after.starts_with(|c: char| !c.is_alphabetic())
|| after.starts_with(|c: char| c == '+' || c == '-');
|| after.starts_with(['+', '-']);
if before_ok && after_ok {
// Grab "UTC+5:30" or "GMT-7" style suffix.
if tz.starts_with("UTC") || tz.starts_with("GMT") {
@@ -419,10 +419,10 @@ fn extract_employer_pattern(s: &str) -> Option<String> {
", inc", " inc.", " llc", " ltd", " limited", " corp", " co.",
];
for suffix in corp_suffixes {
if lower.ends_with(suffix) || lower.contains(&format!("{suffix} ")) {
if is_plausible_employer(t) {
return Some(clean_employer(t));
}
if (lower.ends_with(suffix) || lower.contains(&format!("{suffix} ")))
&& is_plausible_employer(t)
{
return Some(clean_employer(t));
}
}
None
@@ -442,7 +442,7 @@ fn extract_location(s: &str) -> Option<String> {
// City: 2-30 chars, no digits, starts with uppercase.
let city_ok = city.len() >= 2
&& city.len() <= 30
&& city.chars().next().map_or(false, |c| c.is_uppercase())
&& city.chars().next().is_some_and(|c| c.is_uppercase())
&& !city.chars().any(|c| c.is_ascii_digit());
// Region: 2-20 chars.
let region_ok = region.len() >= 2 && region.len() <= 20;
+2 -4
View File
@@ -1021,10 +1021,8 @@ fn handle_reset_cache(_params: Map<String, Value>) -> ControllerFuture {
// Delete all non-Pinned rows.
let mut deleted = 0usize;
for f in &all {
if f.user_state != UserState::Pinned {
if cache.delete(&f.key).unwrap_or(false) {
deleted += 1;
}
if f.user_state != UserState::Pinned && cache.delete(&f.key).unwrap_or(false) {
deleted += 1;
}
}
@@ -166,13 +166,13 @@ fn find_phrase_snippet(original: &str, lower: &str, phrases: &[&str]) -> Option<
// "I prefer X").
let prefix = &original[..start];
let sentence_start = prefix
.rfind(|c: char| matches!(c, '.' | '!' | '?' | '\n'))
.rfind(['.', '!', '?', '\n'])
.map(|i| i + 1)
.unwrap_or(0);
let tail = &original[sentence_start..];
let mut end = tail.len();
if let Some(rel) = tail.find(|c: char| matches!(c, '\n')) {
if let Some(rel) = tail.find('\n') {
end = end.min(rel);
}
if let Some(rel) = tail.find(['.', '!', '?']) {
+2 -3
View File
@@ -288,10 +288,9 @@ fn is_executable_file(path: &Path) -> bool {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
return path
.metadata()
path.metadata()
.map(|meta| meta.permissions().mode() & 0o111 != 0)
.unwrap_or(false);
.unwrap_or(false)
}
#[cfg(not(unix))]
{
+7 -7
View File
@@ -276,7 +276,7 @@ pub async fn mcp_clients_install(
server.qualified_name
);
let _ = publish_global(DomainEvent::McpServerInstalled {
publish_global(DomainEvent::McpServerInstalled {
server_id: server_id.clone(),
qualified_name: server.qualified_name.clone(),
});
@@ -418,7 +418,7 @@ pub async fn mcp_clients_connect(
let tool_count = tools.len() as u32;
let _ = publish_global(DomainEvent::McpServerConnected {
publish_global(DomainEvent::McpServerConnected {
server_id: server_id.trim().to_string(),
tool_count,
});
@@ -471,7 +471,7 @@ pub async fn mcp_clients_set_enabled(
if !enabled {
connections::disconnect(&server_id).await;
connections::clear_last_error(&server_id).await;
let _ = publish_global(DomainEvent::McpServerDisconnected {
publish_global(DomainEvent::McpServerDisconnected {
server_id: server_id.clone(),
reason: Some("disabled".to_string()),
});
@@ -496,7 +496,7 @@ pub async fn mcp_clients_disconnect(server_id: String) -> Result<RpcOutcome<Valu
connections::disconnect(server_id.trim()).await;
let _ = publish_global(DomainEvent::McpServerDisconnected {
publish_global(DomainEvent::McpServerDisconnected {
server_id: server_id.trim().to_string(),
reason: None,
});
@@ -551,7 +551,7 @@ pub async fn mcp_clients_update_env(
// Drop any live session so the reconnect picks up the new env.
connections::disconnect(server_id).await;
let _ = publish_global(DomainEvent::McpServerDisconnected {
publish_global(DomainEvent::McpServerDisconnected {
server_id: server_id.to_string(),
reason: Some("env reconfigured".to_string()),
});
@@ -591,7 +591,7 @@ pub async fn mcp_clients_update_env(
match connections::connect(config, &server).await {
Ok(tools) => {
let tool_count = tools.len() as u32;
let _ = publish_global(DomainEvent::McpServerConnected {
publish_global(DomainEvent::McpServerConnected {
server_id: server_id.to_string(),
tool_count,
});
@@ -786,7 +786,7 @@ pub async fn mcp_clients_tool_call(
let elapsed_ms = start.elapsed().as_millis() as u64;
let success = result.is_ok();
let _ = publish_global(DomainEvent::McpClientToolExecuted {
publish_global(DomainEvent::McpClientToolExecuted {
server_id: server_id.trim().to_string(),
tool_name: tool_name.trim().to_string(),
success,
@@ -610,7 +610,7 @@ impl OfficialServer {
.map(|(_, s)| s)
.or_else(|| raw.rsplit_once('.').map(|(_, s)| s))
.unwrap_or(raw);
segment.replace('-', " ").replace('_', " ")
segment.replace(['-', '_'], " ")
}
fn into_summary(self) -> SmitheryServerSummary {
+2 -2
View File
@@ -93,7 +93,7 @@ pub async fn mcp_setup_request_secret(
let (r, rx) = setup::mint_request(&key_name).await;
let _ = publish_global(DomainEvent::McpSetupSecretRequested {
publish_global(DomainEvent::McpSetupSecretRequested {
ref_id: r.as_str().to_string(),
key_name: key_name.clone(),
prompt: prompt.clone(),
@@ -308,7 +308,7 @@ pub async fn mcp_setup_install_and_connect(
store::insert_server(config, &server).map_err(|e| e.to_string())?;
store::set_env_values(config, &server_id, &env_map).map_err(|e| e.to_string())?;
let _ = publish_global(DomainEvent::McpServerInstalled {
publish_global(DomainEvent::McpServerInstalled {
server_id: server_id.clone(),
qualified_name: server.qualified_name.clone(),
});
+1 -1
View File
@@ -296,7 +296,7 @@ pub fn update_server_config_conn(
}
pub fn list_servers(config: &Config) -> Result<Vec<InstalledServer>> {
with_connection(config, |conn| list_servers_conn(conn))
with_connection(config, list_servers_conn)
}
pub fn list_servers_conn(conn: &Connection) -> Result<Vec<InstalledServer>> {
+2 -2
View File
@@ -146,7 +146,7 @@ async fn handle_post(
record.protocol_version.clone()
};
if protocol_version.as_deref() != Some(expected_protocol.as_str()) {
if protocol_version != Some(expected_protocol.as_str()) {
log_request_rejected(
"protocol mismatch",
Some(session_id),
@@ -245,7 +245,7 @@ async fn handle_get(State(state): State<AppState>, headers: HeaderMap) -> Respon
record.protocol_version.clone()
};
if protocol_version.as_deref() != Some(expected_protocol.as_str()) {
if protocol_version != Some(expected_protocol.as_str()) {
log_request_rejected(
"protocol mismatch",
Some(session_id),
+1 -2
View File
@@ -10,8 +10,7 @@ use crate::openhuman::security::{SecurityPolicy, ToolOperation};
use super::super::write_dispatch;
use super::params::{build_rpc_params, validate_controller_params};
use super::specs::{
base_tool_specs, list_tools_result_for_config, list_tools_result_from_specs, searxng_tool_spec,
tool_specs,
base_tool_specs, list_tools_result_for_config, list_tools_result_from_specs, tool_specs,
};
use super::types::ToolCallError;
+1 -4
View File
@@ -13,10 +13,7 @@ mod types;
// Public API consumed by the rest of `mcp_server`
pub use dispatch::{call_tool, list_tools_result, tool_error, tool_success};
pub use specs::{
base_tool_specs, list_tools_result_for_config, list_tools_result_from_specs, searxng_tool_spec,
tool_specs,
};
pub use specs::tool_specs;
pub use types::{McpToolSpec, ToolCallError};
// Re-exports needed by the companion test module via `use super::*`.
+2 -2
View File
@@ -56,7 +56,7 @@ pub(crate) fn strip_for_speech(text: &str) -> String {
continue;
}
let cleaned: String = trimmed
.trim_start_matches(|c: char| c == '-' || c == '*' || c == '#' || c == '>')
.trim_start_matches(['-', '*', '#', '>'])
.trim()
.chars()
.filter(|c| !matches!(c, '*' | '`' | '_' | '#'))
@@ -112,7 +112,7 @@ pub(super) fn strip_untagged_reasoning(text: &str) -> String {
"responding with",
];
let sentences: Vec<&str> = text
.split_inclusive(|c: char| matches!(c, '.' | '!' | '?'))
.split_inclusive(['.', '!', '?'])
.map(str::trim)
.filter(|s| !s.is_empty())
.collect();
+1 -1
View File
@@ -11,7 +11,7 @@ use std::sync::Arc;
use anyhow::Result;
use async_trait::async_trait;
use crate::openhuman::config::{Config, DEFAULT_CLOUD_LLM_MODEL};
use crate::openhuman::config::Config;
use crate::openhuman::inference::provider::{
create_chat_model_with_model_id, provider_for_role, UsageInfo,
};
+1 -1
View File
@@ -279,7 +279,7 @@ async fn persist(
let all_results: Vec<(ScoreResult, i64)> = chunks
.iter()
.zip(scores.into_iter())
.zip(scores)
.map(|(chunk, result)| (result, chunk.metadata.timestamp.timestamp_millis()))
.collect();
let dropped = all_results.iter().filter(|(r, _)| !r.kept).count();
+3 -3
View File
@@ -85,7 +85,7 @@ pub async fn wipe_all_rpc(config: &Config) -> Result<RpcOutcome<WipeAllResponse>
let is_not_found = e
.chain()
.find_map(|e| e.downcast_ref::<std::io::Error>())
.map_or(false, |ioe| ioe.kind() == std::io::ErrorKind::NotFound);
.is_some_and(|ioe| ioe.kind() == std::io::ErrorKind::NotFound);
if !is_not_found {
log::warn!(
"[memory_tree::read::wipe] failed to remove dir={} err={:#}",
@@ -213,7 +213,7 @@ pub async fn reset_tree_rpc(config: &Config) -> Result<RpcOutcome<ResetTreeRespo
let is_not_found = e
.chain()
.find_map(|e| e.downcast_ref::<std::io::Error>())
.map_or(false, |ioe| ioe.kind() == std::io::ErrorKind::NotFound);
.is_some_and(|ioe| ioe.kind() == std::io::ErrorKind::NotFound);
if !is_not_found {
log::warn!(
"[memory_tree::read::reset_tree] failed to remove wiki/summaries: {:#}",
@@ -245,7 +245,7 @@ pub async fn flush_source_tree_rpc(
source_scope: &str,
) -> Result<RpcOutcome<FlushSourceTreeResponse>, String> {
use crate::openhuman::memory::tree_source::get_or_create_source_tree;
use crate::openhuman::memory_tree::tree::bucket_seal::LabelStrategy;
use crate::openhuman::memory_tree::tree::flush::force_flush_tree;
use crate::openhuman::memory_tree::tree::TreeFactory;
use std::collections::HashSet;
+1 -2
View File
@@ -371,8 +371,7 @@ fn collect_contacts_graph(cfg: &Config) -> Result<(Vec<GraphNode>, Vec<GraphEdge
let edges: Vec<(String, String, String)> = if chunk_ids.is_empty() {
Vec::new()
} else {
let placeholders = std::iter::repeat("?")
.take(chunk_ids.len())
let placeholders = std::iter::repeat_n("?", chunk_ids.len())
.collect::<Vec<_>>()
.join(",");
let sql = format!(
-1
View File
@@ -27,7 +27,6 @@ pub use registry::{all_controller_schemas, all_registered_controllers};
// Re-export the NAMESPACE constant so schema_tests.rs can reference it via
// `super::NAMESPACE` the same way the original flat module did.
pub(crate) use definitions::NAMESPACE;
#[cfg(test)]
#[path = "../schema_tests.rs"]
+1 -1
View File
@@ -32,7 +32,7 @@ use chrono::{DateTime, Utc};
use crate::openhuman::config::Config;
use crate::openhuman::memory_store::content::raw::raw_source_dir;
use crate::openhuman::memory_store::trees::types::{Tree, TreeKind, TreeStatus};
use crate::openhuman::memory_store::trees::types::Tree;
/// Filename of the per-source registry mirror inside `raw/<source_slug>/`.
pub const SOURCE_FILE_NAME: &str = "_source.md";
@@ -258,7 +258,7 @@ impl InvertedIndex {
// Phase 2: verify each candidate by exact substring match.
// Count distinct terms per doc for the score.
let mut hit_counts: HashMap<u32, usize> = HashMap::new();
for (term, candidates) in terms.iter().zip(per_term.into_iter()) {
for (term, candidates) in terms.iter().zip(per_term) {
for doc_id in candidates {
let Some(entry) = self.docs[doc_id as usize].as_ref() else {
continue;
+1 -1
View File
@@ -5,7 +5,7 @@
//! upserts so the user can hand-edit it in Obsidian without losing edits.
use std::fs;
use std::path::{Path, PathBuf};
use std::path::PathBuf;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
+3 -3
View File
@@ -363,7 +363,7 @@ fn settle_job(config: &Config, job: &Job, result: Result<JobOutcome>) -> Result<
job.id,
job.kind.as_str()
);
mark_done(config, &job)?;
mark_done(config, job)?;
}
Ok(JobOutcome::Defer { until_ms, reason }) => {
// Defer is normal operation (transient blocker, e.g. rate
@@ -382,7 +382,7 @@ fn settle_job(config: &Config, job: &Job, result: Result<JobOutcome>) -> Result<
until_ms,
scrub_for_log(&reason)
);
mark_deferred(config, &job, until_ms, &reason)?;
mark_deferred(config, job, until_ms, &reason)?;
}
Err(err) => {
// Preserve the full anyhow cause chain in the persisted
@@ -404,7 +404,7 @@ fn settle_job(config: &Config, job: &Job, result: Result<JobOutcome>) -> Result<
typed.map(|f| f.code.as_str()),
scrub_for_log(&message)
);
mark_failed_typed(config, &job, &message, typed)?;
mark_failed_typed(config, job, &message, typed)?;
}
}
Ok(())
@@ -196,7 +196,7 @@ impl Tool for MemoryVectorSearchTool {
.iter()
.map(|(idx, score, emb)| MmrCandidate {
index: *idx,
embedding: *emb,
embedding: emb,
relevance: *score,
})
.collect();
+2 -4
View File
@@ -214,10 +214,8 @@ pub fn apply_kind_defaults(entry: &mut MemorySourceEntry) {
entry.max_items = Some(20);
}
}
SourceKind::TwitterQuery => {
if entry.since_days.is_none() {
entry.since_days = Some(7);
}
SourceKind::TwitterQuery if entry.since_days.is_none() => {
entry.since_days = Some(7);
}
// Folder / WebPage / Composio: no defaults to apply here.
// Composio defaults are set at upsert time in registry::upsert_composio_source.
+1 -1
View File
@@ -435,7 +435,7 @@ async fn sync_items_individually(
let done = processed.fetch_add(1, Ordering::Relaxed) + 1;
let new = ingested.load(Ordering::Relaxed);
if done % 10 == 0 || done == total {
if done.is_multiple_of(10) || done == total {
emit_sync_stage(
MemorySyncTrigger::Manual,
MemorySyncStage::Ingesting,
@@ -479,7 +479,7 @@ pub(crate) fn get_or_init_connection(config: &Config) -> Result<Arc<PMutex<Conne
// status for `memory_tree_db` instead of "unknown" until the first
// failure. Fires once per path for the process lifetime.
if breaker.mark_startup_emitted() {
let _ = crate::core::event_bus::publish_global(
crate::core::event_bus::publish_global(
crate::core::event_bus::DomainEvent::SystemStartup {
component: "memory_tree_db".to_string(),
},
@@ -494,7 +494,7 @@ pub(crate) fn get_or_init_connection(config: &Config) -> Result<Arc<PMutex<Conne
"[memory_tree] circuit breaker recovered for {}: DB init succeeded after a prior trip",
db_path.display()
);
let _ = crate::core::event_bus::publish_global(
crate::core::event_bus::publish_global(
crate::core::event_bus::DomainEvent::HealthChanged {
component: "memory_tree_db".to_string(),
healthy: true,
@@ -522,7 +522,7 @@ pub(crate) fn get_or_init_connection(config: &Config) -> Result<Arc<PMutex<Conne
db_path.display(),
CB_THRESHOLD
);
let _ = crate::core::event_bus::publish_global(
crate::core::event_bus::publish_global(
crate::core::event_bus::DomainEvent::HealthChanged {
component: "memory_tree_db".to_string(),
healthy: false,
@@ -87,7 +87,7 @@ pub(super) fn migrate_legacy_embeddings_to_sidecar(
// no-op job on every DB open — which would otherwise pollute the jobs
// table for unrelated callers/tests. Enqueued atomically with the
// migration; dedupe key = signature, so exactly one chain per space.
let has_uncovered = has_uncovered_reembed_work(&*tx, &sig)?;
let has_uncovered = has_uncovered_reembed_work(&tx, &sig)?;
if has_uncovered {
let backfill_job = crate::openhuman::memory_queue::types::NewJob::reembed_backfill(
&crate::openhuman::memory_queue::types::ReembedBackfillPayload {
+1 -1
View File
@@ -312,7 +312,7 @@ fn split_email_messages(md: &str) -> Vec<String> {
} else {
n
};
let piece_lines: Vec<&str> = lines[start..end].iter().copied().collect();
let piece_lines: Vec<&str> = lines[start..end].to_vec();
let piece = piece_lines.join("\n").trim_end().to_string();
if !piece.is_empty() {
pieces.push(piece);
+1 -1
View File
@@ -235,7 +235,7 @@ pub fn slug_account_email(email: &str) -> String {
let mut out = String::with_capacity(lower.len() + 8);
let mut last_dash = true;
let mut chars = lower.chars().peekable();
while let Some(ch) = chars.next() {
for ch in chars {
match ch {
'@' => {
if !last_dash {
+8 -8
View File
@@ -244,11 +244,11 @@ fn collect_redactions_inner(norm: &str, include_bare_numeric: bool) -> Vec<Hit>
});
// IBAN before credit card: CC can match an IBAN tail of all digits.
push_checksum(&mut hits, norm, &IBAN_RE, PII_IBAN, |s| valid_iban(s));
push_checksum(&mut hits, norm, &IBAN_RE, PII_IBAN, valid_iban);
if include_bare_numeric {
// Credit card before bare CPF/CNPJ to avoid catching a 13-19 digit run as CPF/CNPJ.
push_checksum(&mut hits, norm, &CC_RE, PII_CC, |s| valid_luhn(s));
push_checksum(&mut hits, norm, &CC_RE, PII_CC, valid_luhn);
push_checksum(&mut hits, norm, &CNPJ_BARE_RE, PII_CNPJ, |s| {
valid_cnpj(digits(s).as_slice())
@@ -266,10 +266,10 @@ fn collect_redactions_inner(norm: &str, include_bare_numeric: bool) -> Vec<Hit>
valid_verhoeff(digits(digits_str).as_slice())
});
push_checksum(&mut hits, norm, &DNI_RE, PII_DNI, |s| valid_dni_es(s));
push_checksum(&mut hits, norm, &NIE_RE, PII_DNI, |s| valid_nie_es(s));
push_checksum(&mut hits, norm, &NINO_RE, PII_NINO, |s| valid_nino(s));
push_checksum(&mut hits, norm, &SSN_RE, PII_SSN, |s| valid_ssn(s));
push_checksum(&mut hits, norm, &DNI_RE, PII_DNI, valid_dni_es);
push_checksum(&mut hits, norm, &NIE_RE, PII_DNI, valid_nie_es);
push_checksum(&mut hits, norm, &NINO_RE, PII_NINO, valid_nino);
push_checksum(&mut hits, norm, &SSN_RE, PII_SSN, valid_ssn);
push_simple(&mut hits, norm, &RRN_RE, PII_RRN);
push_simple(&mut hits, norm, &RFC_RE, PII_RFC);
push_simple(&mut hits, norm, &PAN_IN_RE, PII_PAN_IN);
@@ -349,7 +349,7 @@ fn dedupe_overlaps(hits: &mut Vec<Hit>) {
});
let mut kept: Vec<Hit> = Vec::with_capacity(hits.len());
for h in hits.drain(..) {
let overlaps = kept.last().map_or(false, |k| h.start < k.end);
let overlaps = kept.last().is_some_and(|k| h.start < k.end);
if !overlaps {
kept.push(h);
}
@@ -547,7 +547,7 @@ fn valid_luhn(s: &str) -> bool {
sum += v;
alt = !alt;
}
sum % 10 == 0
sum.is_multiple_of(10)
}
// IBAN mod-97. Steps: strip spaces, move first 4 chars to end, expand letters
@@ -373,7 +373,7 @@ pub fn profile_upsert_full(
.cue_families
.as_ref()
.filter(|m| !m.is_empty())
.map(|m| serde_json::to_string(m))
.map(serde_json::to_string)
.transpose()?;
// Derive class from the facet's own class field or fall back to key prefix.
@@ -87,7 +87,7 @@ pub fn canonicalise(
if let Some(unsub) = &msg.list_unsubscribe {
md.push_str(&format!("List-Unsubscribe: {}\n", unsub));
}
md.push_str("\n");
md.push('\n');
let cleaned = email_clean::clean_body(msg.body.trim());
if cleaned.is_empty() {
md.push('\n');
@@ -128,7 +128,7 @@ pub fn apply_response_level_markdown(data: &mut Value, top_md: &str) {
);
return;
};
for (msg, slice) in messages.iter_mut().zip(slices.into_iter()) {
for (msg, slice) in messages.iter_mut().zip(slices) {
if let Some(obj) = msg.as_object_mut() {
obj.insert("markdownFormatted".to_string(), Value::String(slice));
}
@@ -270,7 +270,7 @@ impl IncrementalSource for NotionSource {
// old per-call `record_requests(1)`.
state.record_requests(fetch_count as u32);
for (p, body) in pending.iter_mut().zip(bodies.into_iter()) {
for (p, body) in pending.iter_mut().zip(bodies) {
p.markdown_body = body;
}
}
@@ -55,7 +55,9 @@ fn pages_for_max_items(max_items: u32, page_size: u32) -> u32 {
return u32::MAX;
}
// Widen to u64 before the addition to prevent overflow for large cap values.
(((max_items as u64) + (page_size as u64) - 1) / (page_size as u64)).min(u32::MAX as u64) as u32
(max_items as u64)
.div_ceil(page_size as u64)
.min(u32::MAX as u64) as u32
}
/// Single source of truth for the per-sync `max_items` cap.
+2 -2
View File
@@ -226,7 +226,7 @@ pub async fn run_github_sync(
// ── Phase 2: fold cost + ingest in batch order (serial) ──
for (batch_idx, ((batch_inputs, batch_labels, batch_basenames), output)) in
batches.into_iter().zip(outputs.into_iter()).enumerate()
batches.into_iter().zip(outputs).enumerate()
{
let batch_input_tokens: u64 = batch_inputs.iter().map(|i| i.token_count as u64).sum();
@@ -499,7 +499,7 @@ async fn read_items_buffered(
child_basenames.push(raw_path);
}
processed += 1;
if processed % 100 == 0 || processed == total {
if processed.is_multiple_of(100) || processed == total {
emit_sync_stage(
MemorySyncTrigger::Manual,
MemorySyncStage::Ingesting,
+1 -1
View File
@@ -98,7 +98,7 @@ impl ToolMemoryCaptureHook {
.unwrap_or_else(|| "__unscoped__".to_string());
let mut out = Vec::new();
for raw_line in trimmed.split(|c: char| matches!(c, '.' | '\n' | ';')) {
for raw_line in trimmed.split(['.', '\n', ';']) {
let line = raw_line.trim();
if line.is_empty() {
continue;
+4 -12
View File
@@ -25,8 +25,10 @@ use serde::{Deserialize, Serialize};
/// and retrieval (to sort high-priority guidance ahead of advisory notes).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum ToolMemoryPriority {
/// Soft suggestion — surfaced on demand, not eagerly injected.
#[default]
Normal,
/// Important guidance — eagerly injected at tool-selection time.
High,
@@ -35,12 +37,6 @@ pub enum ToolMemoryPriority {
Critical,
}
impl Default for ToolMemoryPriority {
fn default() -> Self {
Self::Normal
}
}
impl ToolMemoryPriority {
/// True for priorities that must be eagerly surfaced to the agent
/// (Critical/High rules are both pinned into the system prompt and
@@ -56,6 +52,7 @@ impl ToolMemoryPriority {
/// edicts apart from auto-captured observations.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[derive(Default)]
pub enum ToolMemorySource {
/// User explicitly asked the agent to remember this rule.
UserExplicit,
@@ -63,15 +60,10 @@ pub enum ToolMemorySource {
/// repeated correction, etc.).
PostTurn,
/// Written by another subsystem (e.g. an integration provisioner).
#[default]
Programmatic,
}
impl Default for ToolMemorySource {
fn default() -> Self {
Self::Programmatic
}
}
/// A single tool-scoped memory rule.
///
/// Stored under the `tool-{tool_name}` namespace as a KV entry keyed by
@@ -139,7 +139,7 @@ async fn rerank_by_semantic_similarity(
let mut decorated: Vec<(f32, bool, RetrievalHit)> = hits
.into_iter()
.zip(embeddings.into_iter())
.zip(embeddings)
.map(|(h, emb)| match emb {
Some(v) if v.len() == query_vec.len() => {
let sim = cosine_similarity(&query_vec, &v);
+1 -1
View File
@@ -110,7 +110,7 @@ const MAX_BATCH_TOKENS: usize = 1_000_000;
const CHARS_PER_TOKEN_ESTIMATE: usize = 4;
fn estimate_tokens(text: &str) -> usize {
(text.len() + CHARS_PER_TOKEN_ESTIMATE - 1) / CHARS_PER_TOKEN_ESTIMATE
text.len().div_ceil(CHARS_PER_TOKEN_ESTIMATE)
}
/// Split `texts` into sub-batches that respect the batch API limits:
@@ -274,7 +274,7 @@ pub async fn rebuild_tree(
// re-written above), logging each failure for diagnosis; the rebuild still
// returns the resulting status rather than erroring out wholesale.
let mut failed: Vec<String> = Vec::new();
let mut propagate = |id: &str, level: NodeLevel| {
let propagate = |id: &str, level: NodeLevel| {
let node_id = id.to_string();
async move {
if let Err(e) = propagate_node(config, provider, namespace, &node_id, level).await {
@@ -273,7 +273,7 @@ fn looks_like_openhuman(url: &str) -> bool {
let without_scheme = lower.split("://").nth(1).unwrap_or(&lower);
// Strip userinfo and take only the host[:port] part before any path.
let authority = without_scheme.split('/').next().unwrap_or("");
let host = authority.split('@').last().unwrap_or(authority);
let host = authority.split('@').next_back().unwrap_or(authority);
let host_no_port = host.split(':').next().unwrap_or(host);
host_no_port == "api.openhuman.ai"
|| host_no_port.ends_with(".openhuman.ai")
+2 -2
View File
@@ -160,7 +160,7 @@ mod imp {
}
});
store.requestAccessForEntityType_completionHandler(CNEntityType::Contacts, &*block);
store.requestAccessForEntityType_completionHandler(CNEntityType::Contacts, &block);
rx.recv().map_err(|_| {
AddressBookError::Other("contacts permission callback never fired".into())
@@ -251,7 +251,7 @@ mod imp {
let ok = store.enumerateContactsWithFetchRequest_error_usingBlock(
&request,
Some(&mut error),
&*block,
&block,
);
if !ok {
let msg = error
+1 -2
View File
@@ -453,8 +453,7 @@ impl PeopleStore {
let ids: Vec<PersonId> = person_ids.to_vec();
tokio::task::spawn_blocking(move || -> SqlResult<HashMap<PersonId, Vec<Interaction>>> {
let guard = conn.blocking_lock();
let placeholders = std::iter::repeat("?")
.take(ids.len())
let placeholders = std::iter::repeat_n("?", ids.len())
.collect::<Vec<_>>()
.join(",");
let sql = format!(
+1 -1
View File
@@ -43,7 +43,7 @@ fn public_url_regex() -> &'static Regex {
/// Strip trailing sentence punctuation (`.`, `,`, `;`, `:`, `!`) so that
/// "see https://example.com/path." doesn't capture the period.
fn trim_trailing_punct(s: &str) -> &str {
s.trim_end_matches(|c: char| matches!(c, '.' | ',' | ';' | ':' | '!'))
s.trim_end_matches(['.', ',', ';', ':', '!'])
}
/// Shorten a single URL, persisting it in the global store. Idempotent.
+1 -1
View File
@@ -281,7 +281,7 @@ fn resolve_from_system(system: SystemNode) -> Result<ResolvedNode> {
.unwrap_or_default();
let version = system
.version
.trim_start_matches(|c: char| c == 'v' || c == 'V')
.trim_start_matches(['v', 'V'])
.trim()
.to_string();
build_resolved(bin_dir, version, NodeSource::System)

Some files were not shown because too many files have changed in this diff Show More