mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
Feat/orch fixes (#4720)
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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")]
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()))
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>>> =
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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::{
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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(()))))
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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(_) => {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
|
||||
@@ -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)] = &[
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(['.', '!', '?']) {
|
||||
|
||||
@@ -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))]
|
||||
{
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
});
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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::*`.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user