From 0ce9434a094a4a6d7e96ccc0101ba9ccf2907888 Mon Sep 17 00:00:00 2001 From: argenis de la rosa Date: Thu, 5 Mar 2026 01:23:45 -0500 Subject: [PATCH] fix(pr-2804): resolve main merge conflicts for dashboard release --- README.md | 8 +- docs/one-click-bootstrap.md | 16 ++- docs/troubleshooting.md | 4 +- src/channels/mod.rs | 237 +++++++++++++++++++++++++++--------- web/src/index.css | 114 +++++++++++++++++ 5 files changed, 316 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 77b3b5cc5..704c36556 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Built by students and members of the Harvard, MIT, and Sundai.Club communities.

- 🌐 Languages: English · 简体中文 · 日本語 · Русский · Français · Tiếng Việt · Ελληνικά · Español (coming soon) + 🌐 Languages: English · 简体中文 · Español · Português · Italiano · 日本語 · Русский · Français · Tiếng Việt · Ελληνικά

@@ -83,6 +83,12 @@ Use this board for important notices (breaking changes, security advisories, mai ## Quick Start +### Option 0: One-line Installer (Default TUI Onboarding) + +```bash +curl -fsSL https://zeroclawlabs.ai/install.sh | bash +``` + ### Option 1: Homebrew (macOS/Linuxbrew) ```bash diff --git a/docs/one-click-bootstrap.md b/docs/one-click-bootstrap.md index 342d3fada..9cd4ae5ae 100644 --- a/docs/one-click-bootstrap.md +++ b/docs/one-click-bootstrap.md @@ -2,7 +2,7 @@ This page defines the fastest supported path to install and initialize ZeroClaw. -Last verified: **February 20, 2026**. +Last verified: **March 4, 2026**. ## Option 0: Homebrew (macOS/Linuxbrew) @@ -22,6 +22,7 @@ What it does by default: 1. `cargo build --release --locked` 2. `cargo install --path . --force --locked` +3. In interactive no-flag sessions, launches TUI onboarding (`zeroclaw onboard --interactive-ui`) ### Resource preflight and pre-built flow @@ -50,7 +51,8 @@ To bypass pre-built flow and force source compilation: ## Dual-mode bootstrap -Default behavior is **app-only** (build/install ZeroClaw) and expects existing Rust toolchain. +Default behavior builds/install ZeroClaw and, for interactive no-flag runs, starts TUI onboarding. +It still expects an existing Rust toolchain unless you enable bootstrap flags below. For fresh machines, enable environment bootstrap explicitly: @@ -72,8 +74,16 @@ Notes: curl -fsSL https://zeroclawlabs.ai/install.sh | bash ``` +Equivalent GitHub-hosted installer entrypoint: + +```bash +curl -fsSL https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/main/install.sh | bash +``` + For high-security environments, prefer Option A so you can review the script before execution. +No-arg interactive runs default to full-screen TUI onboarding. + Legacy compatibility: ```bash @@ -124,6 +134,8 @@ ZEROCLAW_API_KEY="sk-..." ZEROCLAW_PROVIDER="openrouter" ./bootstrap.sh --onboar ./bootstrap.sh --interactive-onboard ``` +This launches the full-screen TUI onboarding flow (`zeroclaw onboard --interactive-ui`). + ## Useful flags - `--install-system-deps` diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4bff49713..9e5a9236b 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -404,10 +404,12 @@ Both still work: ```bash curl -fsSL https://zeroclawlabs.ai/install.sh | bash +curl -fsSL https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/main/install.sh | bash curl -fsSL https://raw.githubusercontent.com/zeroclaw-labs/zeroclaw/main/scripts/install.sh | bash ``` -`install.sh` remains a compatibility entry and forwards/falls back to bootstrap behavior for older docs and links. +Root `install.sh` is the canonical remote entrypoint and defaults to TUI onboarding for no-arg interactive sessions. +`scripts/install.sh` remains a compatibility entry and forwards/falls back to bootstrap behavior. ## Still Stuck? diff --git a/src/channels/mod.rs b/src/channels/mod.rs index f6f7725af..7704bc8e8 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -272,6 +272,7 @@ struct RuntimeConfigState { defaults: ChannelRuntimeDefaults, perplexity_filter: crate::config::PerplexityFilterConfig, outbound_leak_guard: crate::config::OutboundLeakGuardConfig, + canary_tokens: bool, last_applied_stamp: Option, } @@ -279,6 +280,7 @@ struct RuntimeConfigState { struct RuntimeAutonomyPolicy { auto_approve: Vec, always_ask: Vec, + command_context_rules: Vec, non_cli_excluded_tools: Vec, non_cli_approval_approvers: Vec, non_cli_natural_language_approval_mode: NonCliNaturalLanguageApprovalMode, @@ -286,6 +288,7 @@ struct RuntimeAutonomyPolicy { HashMap, perplexity_filter: crate::config::PerplexityFilterConfig, outbound_leak_guard: crate::config::OutboundLeakGuardConfig, + canary_tokens: bool, } fn runtime_config_store() -> &'static Mutex> { @@ -856,7 +859,7 @@ fn normalize_cached_channel_turns(turns: Vec) -> Vec { } fn supports_runtime_model_switch(channel_name: &str) -> bool { - !channel_name.eq_ignore_ascii_case("cli") + matches!(channel_name, "telegram" | "discord") } fn parse_runtime_command(channel_name: &str, content: &str) -> Option { @@ -1106,6 +1109,7 @@ fn runtime_autonomy_policy_from_config(config: &Config) -> RuntimeAutonomyPolicy RuntimeAutonomyPolicy { auto_approve: config.autonomy.auto_approve.clone(), always_ask: config.autonomy.always_ask.clone(), + command_context_rules: config.autonomy.command_context_rules.clone(), non_cli_excluded_tools: config.autonomy.non_cli_excluded_tools.clone(), non_cli_approval_approvers: config.autonomy.non_cli_approval_approvers.clone(), non_cli_natural_language_approval_mode: config @@ -1117,6 +1121,7 @@ fn runtime_autonomy_policy_from_config(config: &Config) -> RuntimeAutonomyPolicy .clone(), perplexity_filter: config.security.perplexity_filter.clone(), outbound_leak_guard: config.security.outbound_leak_guard.clone(), + canary_tokens: config.security.canary_tokens, } } @@ -1187,6 +1192,19 @@ fn runtime_outbound_leak_guard_snapshot( } crate::config::OutboundLeakGuardConfig::default() } + +fn runtime_canary_tokens_snapshot(ctx: &ChannelRuntimeContext) -> bool { + if let Some(config_path) = runtime_config_path(ctx) { + let store = runtime_config_store() + .lock() + .unwrap_or_else(|e| e.into_inner()); + if let Some(state) = store.get(&config_path) { + return state.canary_tokens; + } + } + false +} + fn snapshot_non_cli_excluded_tools(ctx: &ChannelRuntimeContext) -> Vec { ctx.non_cli_excluded_tools .lock() @@ -1713,6 +1731,7 @@ async fn maybe_apply_runtime_config_update(ctx: &ChannelRuntimeContext) -> Resul defaults: next_defaults.clone(), perplexity_filter: next_autonomy_policy.perplexity_filter.clone(), outbound_leak_guard: next_autonomy_policy.outbound_leak_guard.clone(), + canary_tokens: next_autonomy_policy.canary_tokens, last_applied_stamp: Some(stamp), }, ); @@ -1721,6 +1740,7 @@ async fn maybe_apply_runtime_config_update(ctx: &ChannelRuntimeContext) -> Resul ctx.approval_manager.replace_runtime_non_cli_policy( &next_autonomy_policy.auto_approve, &next_autonomy_policy.always_ask, + &next_autonomy_policy.command_context_rules, &next_autonomy_policy.non_cli_approval_approvers, next_autonomy_policy.non_cli_natural_language_approval_mode, &next_autonomy_policy.non_cli_natural_language_approval_mode_by_channel, @@ -1747,6 +1767,7 @@ async fn maybe_apply_runtime_config_update(ctx: &ChannelRuntimeContext) -> Resul outbound_leak_guard_enabled = next_autonomy_policy.outbound_leak_guard.enabled, outbound_leak_guard_action = ?next_autonomy_policy.outbound_leak_guard.action, outbound_leak_guard_sensitivity = next_autonomy_policy.outbound_leak_guard.sensitivity, + canary_tokens = next_autonomy_policy.canary_tokens, "Applied updated channel runtime config from disk" ); @@ -2047,6 +2068,31 @@ async fn create_resilient_provider_nonblocking( .context("failed to join provider initialization task")? } +async fn create_routed_provider_nonblocking( + provider_name: &str, + api_key: Option, + api_url: Option, + reliability: crate::config::ReliabilityConfig, + model_routes: Vec, + default_model: String, + provider_runtime_options: providers::ProviderRuntimeOptions, +) -> anyhow::Result> { + let provider_name = provider_name.to_string(); + tokio::task::spawn_blocking(move || { + providers::create_routed_provider_with_options( + &provider_name, + api_key.as_deref(), + api_url.as_deref(), + &reliability, + &model_routes, + &default_model, + &provider_runtime_options, + ) + }) + .await + .context("failed to join routed provider initialization task")? +} + fn build_models_help_response(current: &ChannelRouteSelection, workspace_dir: &Path) -> String { let mut response = String::new(); let _ = writeln!( @@ -2258,7 +2304,6 @@ async fn handle_runtime_command_if_needed( /// - Grant session and persistent runtime grants /// - Persist to config /// - Clear exclusions - /// /// Returns the approval success message. async fn handle_confirm_tool_approval_side_effects( ctx: &ChannelRuntimeContext, @@ -2301,7 +2346,7 @@ async fn handle_runtime_command_if_needed( /// /// This path confirms only the current pending request and intentionally does /// not persist approval policy changes for normal tools. - fn handle_pending_runtime_approval_side_effects( + async fn handle_pending_runtime_approval_side_effects( ctx: &ChannelRuntimeContext, request_id: &str, tool_name: &str, @@ -2785,7 +2830,8 @@ async fn handle_runtime_command_if_needed( ctx, &request_id, &req.tool_name, - ); + ) + .await; runtime_trace::record_event( "approval_request_approved", Some(source_channel), @@ -3818,6 +3864,7 @@ or tune thresholds in config.", &excluded_tools_snapshot, progress_mode, ctx.safety_heartbeat.clone(), + runtime_canary_tokens_snapshot(ctx.as_ref()), ), ), ) => LlmExecutionResult::Completed(result), @@ -4943,6 +4990,7 @@ fn collect_configured_channels( ) .with_group_reply_allowed_senders(dc.group_reply_allowed_sender_ids()) .with_ack_reaction(config.channels_config.ack_reaction.discord.clone()) + .with_transcription(config.transcription.clone()) .with_workspace_dir(config.workspace_dir.clone()), ), }); @@ -5364,6 +5412,7 @@ pub async fn start_channels(config: Config) -> Result<()> { } let provider_name = resolved_default_provider(&config); + let model = resolved_default_model(&config); let provider_runtime_options = providers::ProviderRuntimeOptions { auth_profile_override: None, provider_api_url: config.api_url.clone(), @@ -5373,15 +5422,18 @@ pub async fn start_channels(config: Config) -> Result<()> { reasoning_enabled: config.runtime.reasoning_enabled, reasoning_level: config.effective_provider_reasoning_level(), custom_provider_api_mode: config.provider_api.map(|mode| mode.as_compatible_mode()), + custom_provider_auth_header: config.effective_custom_provider_auth_header(), max_tokens_override: None, model_support_vision: config.model_support_vision, }; let provider: Arc = Arc::from( - create_resilient_provider_nonblocking( + create_routed_provider_nonblocking( &provider_name, config.api_key.clone(), config.api_url.clone(), config.reliability.clone(), + config.model_routes.clone(), + model.clone(), provider_runtime_options.clone(), ) .await?, @@ -5404,6 +5456,7 @@ pub async fn start_channels(config: Config) -> Result<()> { defaults: runtime_defaults_from_config(&config), perplexity_filter: config.security.perplexity_filter.clone(), outbound_leak_guard: config.security.outbound_leak_guard.clone(), + canary_tokens: config.security.canary_tokens, last_applied_stamp: initial_stamp, }, ); @@ -5420,7 +5473,6 @@ pub async fn start_channels(config: Config) -> Result<()> { &config.autonomy, &config.workspace_dir, )); - let model = resolved_default_model(&config); let temperature = config.default_temperature; let mem: Arc = Arc::from(memory::create_memory_with_storage( &config.memory, @@ -5792,6 +5844,7 @@ mod tests { use crate::memory::{Memory, MemoryCategory, SqliteMemory}; use crate::observability::NoopObserver; use crate::providers::{ChatMessage, Provider}; + use crate::security::AutonomyLevel; use crate::tools::{Tool, ToolResult}; use std::collections::{HashMap, HashSet}; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -5863,7 +5916,7 @@ mod tests { } #[test] - fn parse_runtime_command_allows_runtime_commands_on_non_cli_channels() { + fn parse_runtime_command_allows_approval_commands_on_non_model_channels() { assert_eq!( parse_runtime_command("slack", "/approve-request shell"), Some(ChannelRuntimeCommand::RequestToolApproval( @@ -5908,16 +5961,7 @@ mod tests { parse_runtime_command("slack", "/approvals"), Some(ChannelRuntimeCommand::ListApprovals) ); - assert_eq!( - parse_runtime_command("slack", "/models"), - Some(ChannelRuntimeCommand::ShowProviders) - ); - } - - #[test] - fn parse_runtime_command_keeps_model_switch_disabled_on_cli_channel() { - assert_eq!(parse_runtime_command("cli", "/models"), None); - assert_eq!(parse_runtime_command("cli", "/model"), None); + assert_eq!(parse_runtime_command("slack", "/models"), None); } #[test] @@ -6221,7 +6265,7 @@ mod tests { max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(histories)), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -6904,7 +6948,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -6969,6 +7013,14 @@ BTC is currently around $65,000 based on latest tool output."# let mut channels_by_name = HashMap::new(); channels_by_name.insert(channel.name().to_string(), channel); + + let autonomy_cfg = crate::config::AutonomyConfig { + level: AutonomyLevel::Full, + auto_approve: vec!["mock_price".to_string()], + ..crate::config::AutonomyConfig::default() + }; + let _approval_manager = Arc::new(ApprovalManager::from_config(&autonomy_cfg)); + let runtime_ctx = Arc::new(ChannelRuntimeContext { channels_by_name: Arc::new(channels_by_name), provider: Arc::new(ToolCallingProvider), @@ -6983,7 +7035,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -7035,6 +7087,14 @@ BTC is currently around $65,000 based on latest tool output."# let mut channels_by_name = HashMap::new(); channels_by_name.insert(channel.name().to_string(), channel); + + let autonomy_cfg = crate::config::AutonomyConfig { + level: AutonomyLevel::Full, + auto_approve: vec!["mock_price".to_string()], + ..crate::config::AutonomyConfig::default() + }; + let _approval_manager = Arc::new(ApprovalManager::from_config(&autonomy_cfg)); + let runtime_ctx = Arc::new(ChannelRuntimeContext { channels_by_name: Arc::new(channels_by_name), provider: Arc::new(ToolCallingProvider), @@ -7049,7 +7109,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -7115,6 +7175,14 @@ BTC is currently around $65,000 based on latest tool output."# let mut channels_by_name = HashMap::new(); channels_by_name.insert(channel.name().to_string(), channel); + + let autonomy_cfg = crate::config::AutonomyConfig { + level: AutonomyLevel::Full, + auto_approve: vec!["mock_price".to_string()], + ..crate::config::AutonomyConfig::default() + }; + let _approval_manager = Arc::new(ApprovalManager::from_config(&autonomy_cfg)); + let runtime_ctx = Arc::new(ChannelRuntimeContext { channels_by_name: Arc::new(channels_by_name), provider: Arc::new(ToolCallingProvider), @@ -7129,7 +7197,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -7194,6 +7262,14 @@ BTC is currently around $65,000 based on latest tool output."# let mut channels_by_name = HashMap::new(); channels_by_name.insert(channel.name().to_string(), channel); + + let autonomy_cfg = crate::config::AutonomyConfig { + level: AutonomyLevel::Full, + auto_approve: vec!["mock_price".to_string()], + ..crate::config::AutonomyConfig::default() + }; + let _approval_manager = Arc::new(ApprovalManager::from_config(&autonomy_cfg)); + let runtime_ctx = Arc::new(ChannelRuntimeContext { channels_by_name: Arc::new(channels_by_name), provider: Arc::new(ToolCallingProvider), @@ -7208,7 +7284,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -7280,7 +7356,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -7332,6 +7408,14 @@ BTC is currently around $65,000 based on latest tool output."# let mut channels_by_name = HashMap::new(); channels_by_name.insert(channel.name().to_string(), channel); + + let autonomy_cfg = crate::config::AutonomyConfig { + level: AutonomyLevel::Full, + auto_approve: vec!["mock_price".to_string()], + ..crate::config::AutonomyConfig::default() + }; + let _approval_manager = Arc::new(ApprovalManager::from_config(&autonomy_cfg)); + let runtime_ctx = Arc::new(ChannelRuntimeContext { channels_by_name: Arc::new(channels_by_name), provider: Arc::new(ToolCallingAliasProvider), @@ -7346,7 +7430,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -7422,7 +7506,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -7526,7 +7610,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -7667,7 +7751,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -7756,7 +7840,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -7834,7 +7918,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -7995,7 +8079,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8110,7 +8194,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8220,7 +8304,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8315,7 +8399,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8417,7 +8501,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8517,7 +8601,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8668,7 +8752,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8765,7 +8849,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -8915,7 +8999,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -9035,7 +9119,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -9135,7 +9219,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -9257,7 +9341,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -9377,7 +9461,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -9456,7 +9540,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -9539,6 +9623,7 @@ BTC is currently around $65,000 based on latest tool output."# }, perplexity_filter: crate::config::PerplexityFilterConfig::default(), outbound_leak_guard: crate::config::OutboundLeakGuardConfig::default(), + canary_tokens: true, last_applied_stamp: None, }, ); @@ -9558,7 +9643,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(provider_cache_seed)), @@ -9745,7 +9830,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -9883,6 +9968,40 @@ BTC is currently around $65,000 based on latest tool output."# store.remove(&config_path); } + #[tokio::test] + async fn start_channels_uses_model_routes_when_global_provider_key_is_missing() { + let temp = tempfile::TempDir::new().expect("temp dir"); + let workspace_dir = temp.path().join("workspace"); + std::fs::create_dir_all(&workspace_dir).expect("workspace dir"); + + let mut cfg = Config::default(); + cfg.workspace_dir = workspace_dir; + cfg.config_path = temp.path().join("config.toml"); + cfg.default_provider = None; + cfg.api_key = None; + cfg.default_model = Some("hint:fast".to_string()); + cfg.model_routes = vec![crate::config::ModelRouteConfig { + hint: "fast".to_string(), + provider: "openai-codex".to_string(), + model: "gpt-5.3-codex".to_string(), + max_tokens: Some(512), + api_key: Some("route-specific-key".to_string()), + transport: Some("sse".to_string()), + }]; + + let config_path = cfg.config_path.clone(); + let result = start_channels(cfg).await; + let mut store = runtime_config_store() + .lock() + .unwrap_or_else(|e| e.into_inner()); + store.remove(&config_path); + + assert!( + result.is_ok(), + "start_channels should support routed providers without global credentials: {result:?}" + ); + } + #[tokio::test] async fn process_channel_message_respects_configured_max_tool_iterations_above_default() { let channel_impl = Arc::new(RecordingChannel::default()); @@ -9907,7 +10026,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 12, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -9975,7 +10094,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 3, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -10155,7 +10274,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -10245,7 +10364,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -10347,7 +10466,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -10431,7 +10550,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -10500,7 +10619,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 10, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -11131,7 +11250,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -11227,7 +11346,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -11322,7 +11441,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -11421,7 +11540,7 @@ BTC is currently around $65,000 based on latest tool output."# max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(histories)), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -12249,7 +12368,7 @@ BTC is currently around $65,000 based on latest tool output."#; max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), @@ -12325,7 +12444,7 @@ BTC is currently around $65,000 based on latest tool output."#; max_tool_iterations: 5, min_relevance_score: 0.0, conversation_histories: Arc::new(Mutex::new(HashMap::new())), - conversation_locks: Arc::default(), + conversation_locks: Default::default(), session_config: crate::config::AgentSessionConfig::default(), session_manager: None, provider_cache: Arc::new(Mutex::new(HashMap::new())), diff --git a/web/src/index.css b/web/src/index.css index 62cf2e002..840137e44 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -253,14 +253,33 @@ body { } .pairing-shell { + position: relative; + isolation: isolate; min-height: 100dvh; background: radial-gradient(circle at 20% 5%, rgba(64, 141, 255, 0.24), transparent 35%), radial-gradient(circle at 75% 92%, rgba(0, 193, 255, 0.13), transparent 35%), linear-gradient(155deg, #020816 0%, #030c20 58%, #030915 100%); + overflow: hidden; +} + +.pairing-shell::after { + content: ""; + position: absolute; + inset: -20%; + background: + radial-gradient(circle at 10% 20%, rgba(84, 173, 255, 0.25), transparent 60%), + radial-gradient(circle at 85% 80%, rgba(0, 204, 255, 0.22), transparent 60%); + filter: blur(12px); + opacity: 0.7; + animation: pairingSpotlightSweep 18s ease-in-out infinite; + pointer-events: none; + z-index: -1; } .pairing-card { + position: relative; + overflow: hidden; border: 1px solid #2956a8; background: linear-gradient(155deg, rgba(9, 27, 68, 0.9), rgba(4, 15, 35, 0.94)); @@ -271,6 +290,33 @@ body { 0 0 28px -18px rgba(76, 184, 255, 0.82); } +.pairing-card::before { + content: ""; + position: absolute; + inset: -1px; + border-radius: inherit; + background: linear-gradient(135deg, transparent 10%, rgba(102, 186, 255, 0.5), transparent 80%); + mix-blend-mode: screen; + opacity: 0.0; + transform: translateX(-65%); + animation: pairingCardSweep 7.5s ease-in-out infinite; + pointer-events: none; +} + +.pairing-brand { + background-image: linear-gradient(120deg, #5bc0ff 0%, #f9e775 28%, #5bc0ff 56%, #f9e775 100%); + background-size: 260% 260%; + background-clip: text; + -webkit-background-clip: text; + color: transparent; + text-shadow: + 0 0 14px rgba(96, 189, 255, 0.8), + 0 0 32px rgba(46, 138, 255, 0.9), + 0 0 42px rgba(252, 238, 147, 0.85); + letter-spacing: 0.18em; + animation: pairingElectricCharge 5.4s ease-in-out infinite; +} + :is(div, section, article)[class*="bg-gray-900"][class*="rounded-xl"][class*="border"], :is(div, section, article)[class*="bg-gray-900"][class*="rounded-lg"][class*="border"], :is(div, section, article)[class*="bg-gray-950"][class*="rounded-lg"][class*="border"] { @@ -398,6 +444,72 @@ body { } } +@keyframes pairingElectricCharge { + 0%, + 100% { + background-position: 0% 50%; + text-shadow: + 0 0 14px rgba(86, 177, 255, 0.5), + 0 0 32px rgba(36, 124, 255, 0.7), + 0 0 38px rgba(252, 238, 147, 0.6); + transform: translateY(0) scale(1); + } + 35% { + background-position: 80% 50%; + text-shadow: + 0 0 26px rgba(138, 218, 255, 1), + 0 0 52px rgba(56, 176, 255, 1), + 0 0 60px rgba(252, 238, 147, 1); + transform: translateY(-1px) scale(1.06); + } + 60% { + background-position: 50% 50%; + text-shadow: + 0 0 18px rgba(86, 177, 255, 0.7), + 0 0 36px rgba(36, 124, 255, 0.8), + 0 0 44px rgba(252, 238, 147, 0.7); + transform: translateY(0) scale(1.02); + } +} + +@keyframes pairingSpotlightSweep { + 0% { + transform: translate3d(-12%, 8%, 0) scale(1); + opacity: 0.45; + } + 30% { + transform: translate3d(10%, -4%, 0) scale(1.06); + opacity: 0.7; + } + 55% { + transform: translate3d(16%, 10%, 0) scale(1.1); + opacity: 0.6; + } + 80% { + transform: translate3d(-8%, -6%, 0) scale(1.04); + opacity: 0.5; + } + 100% { + transform: translate3d(-12%, 8%, 0) scale(1); + opacity: 0.45; + } +} + +@keyframes pairingCardSweep { + 0%, + 100% { + transform: translateX(-70%); + opacity: 0; + } + 25% { + opacity: 0.55; + } + 50% { + transform: translateX(55%); + opacity: 0; + } +} + @keyframes topGlowSweep { 0%, 100% { @@ -475,6 +587,8 @@ body { .glass-header::after, .electric-card::after, .app-shell::after, + .pairing-shell::after, + .pairing-card::before, .motion-rise, .electric-loader { animation: none !important;