From e9eea7044515af22e53060f564170a348eb1e6da Mon Sep 17 00:00:00 2001 From: Hunter B Date: Sun, 31 May 2026 21:45:31 -0700 Subject: [PATCH] =?UTF-8?q?release:=20v0.8.48=20=E2=80=94=20liveness=20wat?= =?UTF-8?q?chdog=20fix,=20Qwen=203.7=20removal,=20provider/docs=20sync?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix false 'Turn stalled' during long active turns with running tools. Add turn_last_activity_at tracking and active-tool awareness to reconcile_turn_liveness(). Three new tests cover the fix. - Remove Qwen 3.7 Max OpenRouter preset from registry, picker, docs, and tests. Qwen 3.7 Max is a hosted model; the preset will return when an open-weight Qwen 3.7 release ships. MiniMax M3 remains as a full 1M-context multimodal route. - Sync root CHANGELOG to crates/tui/CHANGELOG for crates.io packaging. Update docs/CONFIGURATION.md, docs/PROVIDERS.md, and README to reflect the Qwen 3.7 removal. Regenerate web facts timestamp. --- CHANGELOG.md | 10 +- README.md | 1 - crates/agent/src/lib.rs | 15 - crates/config/src/lib.rs | 10 - crates/tui/CHANGELOG.md | 10 +- crates/tui/src/client/chat.rs | 1 - crates/tui/src/config.rs | 13 - crates/tui/src/main.rs | 7 +- crates/tui/src/models.rs | 11 +- crates/tui/src/tui/app.rs | 5 + crates/tui/src/tui/model_picker.rs | 6 +- crates/tui/src/tui/ui.rs | 58 +++- crates/tui/src/tui/ui/tests.rs | 49 +++ crates/tui/src/utils.rs | 4 +- docs/CONFIGURATION.md | 2 +- docs/PROVIDERS.md | 8 +- web/lib/facts.generated.ts | 2 +- web/package-lock.json | 537 ----------------------------- 18 files changed, 140 insertions(+), 609 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3532192..50e759bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,8 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Recent large OpenRouter model presets.** Added completions, aliases, - routing metadata, and docs for Arcee Trinity Large Thinking, Qwen 3.7 - Max, Xiaomi MiMo v2.5, Qwen 3.6 open-weight models, Kimi K2.6, + routing metadata, and docs for Arcee Trinity Large Thinking, + MiniMax M3, Xiaomi MiMo v2.5, Qwen 3.6 open-weight models, Kimi K2.6, GLM 5.1, Tencent Hy3, Gemma 4, and Nemotron (#2461). - **Provider and web-search expansion.** Added Xiaomi MiMo provider support, SiliconFlow, AtlasCloud static models, Volcengine Ark search, Baidu AI @@ -34,6 +34,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 event envelopes, task migration/env isolation fixes, and state-message parent IDs for future forks (#2383, #2358, #2367, #2252, #2272, #2308). +### Removed + +- **Qwen 3.7 Max OpenRouter preset.** Removed from the model registry, docs, + and examples. Qwen 3.7 Max is a hosted model, not open-source; the preset + will return when an open-weight Qwen 3.7 release ships. + ### Changed - **Release hardening.** CI now runs clippy/docs checks, web frontend lint and diff --git a/README.md b/README.md index 2c4a6c7c..177187d2 100644 --- a/README.md +++ b/README.md @@ -316,7 +316,6 @@ codewhale --provider wanjie-ark --model deepseek-reasoner codewhale auth set --provider openrouter --api-key "YOUR_OPENROUTER_API_KEY" codewhale --provider openrouter --model deepseek/deepseek-v4-pro codewhale --provider openrouter --model arcee-ai/trinity-large-thinking -codewhale --provider openrouter --model qwen/qwen3.7-max codewhale --provider openrouter --model minimax/minimax-m3 # Xiaomi MiMo diff --git a/crates/agent/src/lib.rs b/crates/agent/src/lib.rs index 4d9adaba..7c3bbdf7 100644 --- a/crates/agent/src/lib.rs +++ b/crates/agent/src/lib.rs @@ -197,19 +197,6 @@ impl Default for ModelRegistry { supports_tools: true, supports_reasoning: true, }, - ModelInfo { - id: "qwen/qwen3.7-max".to_string(), - provider: ProviderKind::Openrouter, - aliases: vec![ - "qwen3.7".to_string(), - "qwen-3.7".to_string(), - "qwen3-7".to_string(), - "qwen3.7-max".to_string(), - "qwen-3.7-max".to_string(), - ], - supports_tools: true, - supports_reasoning: true, - }, ModelInfo { id: "xiaomi/mimo-v2.5-pro".to_string(), provider: ProviderKind::Openrouter, @@ -745,8 +732,6 @@ mod tests { for (alias, expected) in [ ("trinity-large-thinking", "arcee-ai/trinity-large-thinking"), - ("qwen3.7", "qwen/qwen3.7-max"), - ("qwen3.7-max", "qwen/qwen3.7-max"), ("qwen3.6-35b-a3b", "qwen/qwen3.6-35b-a3b"), ("gemma-4-31b-it", "google/gemma-4-31b-it"), ("glm-5.1", "z-ai/glm-5.1"), diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 29542ba6..a09569a3 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -36,7 +36,6 @@ const OPENROUTER_GLM_5_1_MODEL: &str = "z-ai/glm-5.1"; const OPENROUTER_KIMI_K2_6_MODEL: &str = "moonshotai/kimi-k2.6"; const OPENROUTER_NEMOTRON_3_NANO_OMNI_MODEL: &str = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free"; -const OPENROUTER_QWEN_3_7_MAX_MODEL: &str = "qwen/qwen3.7-max"; const OPENROUTER_QWEN_3_6_35B_A3B_MODEL: &str = "qwen/qwen3.6-35b-a3b"; const OPENROUTER_QWEN_3_6_27B_MODEL: &str = "qwen/qwen3.6-27b"; const OPENROUTER_TENCENT_HY3_PREVIEW_MODEL: &str = "tencent/hy3-preview"; @@ -1525,13 +1524,6 @@ fn canonical_openrouter_recent_model_id(model: &str) -> Option<&'static str> { OPENROUTER_NEMOTRON_3_NANO_OMNI_MODEL | "nemotron-3-nano-omni" | "nemotron-3-nano-omni-reasoning" => Some(OPENROUTER_NEMOTRON_3_NANO_OMNI_MODEL), - OPENROUTER_QWEN_3_7_MAX_MODEL - | "qwen3.7" - | "qwen-3.7" - | "qwen3-7" - | "qwen3.7-max" - | "qwen-3.7-max" - | "qwen3-7-max" => Some(OPENROUTER_QWEN_3_7_MAX_MODEL), OPENROUTER_QWEN_3_6_35B_A3B_MODEL | "qwen3.6-35b-a3b" | "qwen-3.6-35b-a3b" @@ -3845,8 +3837,6 @@ unix_socket_path = "/tmp/cw-hooks.sock" "trinity-large-thinking", OPENROUTER_ARCEE_TRINITY_LARGE_THINKING_MODEL, ), - ("qwen3.7", OPENROUTER_QWEN_3_7_MAX_MODEL), - ("qwen3.7-max", OPENROUTER_QWEN_3_7_MAX_MODEL), ("qwen3.6-35b-a3b", OPENROUTER_QWEN_3_6_35B_A3B_MODEL), ("mimo-v2.5-pro", OPENROUTER_XIAOMI_MIMO_V2_5_PRO_MODEL), ("kimi-k2.6", OPENROUTER_KIMI_K2_6_MODEL), diff --git a/crates/tui/CHANGELOG.md b/crates/tui/CHANGELOG.md index b3532192..50e759bd 100644 --- a/crates/tui/CHANGELOG.md +++ b/crates/tui/CHANGELOG.md @@ -12,8 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - **Recent large OpenRouter model presets.** Added completions, aliases, - routing metadata, and docs for Arcee Trinity Large Thinking, Qwen 3.7 - Max, Xiaomi MiMo v2.5, Qwen 3.6 open-weight models, Kimi K2.6, + routing metadata, and docs for Arcee Trinity Large Thinking, + MiniMax M3, Xiaomi MiMo v2.5, Qwen 3.6 open-weight models, Kimi K2.6, GLM 5.1, Tencent Hy3, Gemma 4, and Nemotron (#2461). - **Provider and web-search expansion.** Added Xiaomi MiMo provider support, SiliconFlow, AtlasCloud static models, Volcengine Ark search, Baidu AI @@ -34,6 +34,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 event envelopes, task migration/env isolation fixes, and state-message parent IDs for future forks (#2383, #2358, #2367, #2252, #2272, #2308). +### Removed + +- **Qwen 3.7 Max OpenRouter preset.** Removed from the model registry, docs, + and examples. Qwen 3.7 Max is a hosted model, not open-source; the preset + will return when an open-weight Qwen 3.7 release ships. + ### Changed - **Release hardening.** CI now runs clippy/docs checks, web frontend lint and diff --git a/crates/tui/src/client/chat.rs b/crates/tui/src/client/chat.rs index d6e39b6f..7d7fe244 100644 --- a/crates/tui/src/client/chat.rs +++ b/crates/tui/src/client/chat.rs @@ -3444,7 +3444,6 @@ mod alias_thinking_detection_tests { "mimo-v2.5-pro should stream reasoning as thinking on Xiaomi MiMo" ); for model in [ - "qwen/qwen3.7-max", "arcee-ai/trinity-large-thinking", "minimax/minimax-m3", "xiaomi/mimo-v2.5-pro", diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index da4f68c0..c5ba8645 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -56,7 +56,6 @@ pub const OPENROUTER_KIMI_K2_6_MODEL: &str = "moonshotai/kimi-k2.6"; pub const OPENROUTER_MINIMAX_M3_MODEL: &str = "minimax/minimax-m3"; pub const OPENROUTER_NEMOTRON_3_NANO_OMNI_MODEL: &str = "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free"; -pub const OPENROUTER_QWEN_3_7_MAX_MODEL: &str = "qwen/qwen3.7-max"; pub const OPENROUTER_QWEN_3_6_35B_A3B_MODEL: &str = "qwen/qwen3.6-35b-a3b"; pub const OPENROUTER_QWEN_3_6_27B_MODEL: &str = "qwen/qwen3.6-27b"; pub const OPENROUTER_TENCENT_HY3_PREVIEW_MODEL: &str = "tencent/hy3-preview"; @@ -64,7 +63,6 @@ pub const OPENROUTER_XIAOMI_MIMO_V2_5_PRO_MODEL: &str = "xiaomi/mimo-v2.5-pro"; pub const OPENROUTER_XIAOMI_MIMO_V2_5_MODEL: &str = "xiaomi/mimo-v2.5"; pub const RECENT_OPENROUTER_LARGE_MODELS: &[&str] = &[ OPENROUTER_ARCEE_TRINITY_LARGE_THINKING_MODEL, - OPENROUTER_QWEN_3_7_MAX_MODEL, OPENROUTER_MINIMAX_M3_MODEL, OPENROUTER_XIAOMI_MIMO_V2_5_PRO_MODEL, OPENROUTER_XIAOMI_MIMO_V2_5_MODEL, @@ -516,13 +514,6 @@ fn canonical_openrouter_recent_model_id(model: &str) -> Option<&'static str> { OPENROUTER_NEMOTRON_3_NANO_OMNI_MODEL | "nemotron-3-nano-omni" | "nemotron-3-nano-omni-reasoning" => Some(OPENROUTER_NEMOTRON_3_NANO_OMNI_MODEL), - OPENROUTER_QWEN_3_7_MAX_MODEL - | "qwen3.7" - | "qwen-3.7" - | "qwen3-7" - | "qwen3.7-max" - | "qwen-3.7-max" - | "qwen3-7-max" => Some(OPENROUTER_QWEN_3_7_MAX_MODEL), OPENROUTER_QWEN_3_6_35B_A3B_MODEL | "qwen3.6-35b-a3b" | "qwen-3.6-35b-a3b" @@ -6505,8 +6496,6 @@ api_key = "old-openrouter-key" "trinity-large-thinking", OPENROUTER_ARCEE_TRINITY_LARGE_THINKING_MODEL, ), - ("qwen3.7", OPENROUTER_QWEN_3_7_MAX_MODEL), - ("qwen3.7-max", OPENROUTER_QWEN_3_7_MAX_MODEL), ("qwen3.6-35b-a3b", OPENROUTER_QWEN_3_6_35B_A3B_MODEL), ("mimo-v2.5-pro", OPENROUTER_XIAOMI_MIMO_V2_5_PRO_MODEL), ("kimi-k2.6", OPENROUTER_KIMI_K2_6_MODEL), @@ -6537,7 +6526,6 @@ api_key = "old-openrouter-key" DEFAULT_OPENROUTER_MODEL, DEFAULT_OPENROUTER_FLASH_MODEL, OPENROUTER_ARCEE_TRINITY_LARGE_THINKING_MODEL, - OPENROUTER_QWEN_3_7_MAX_MODEL, OPENROUTER_XIAOMI_MIMO_V2_5_PRO_MODEL, OPENROUTER_MINIMAX_M3_MODEL, OPENROUTER_QWEN_3_6_35B_A3B_MODEL, @@ -8629,7 +8617,6 @@ model = "deepseek-ai/deepseek-v4-pro" 262_144, 262_144, ), - (OPENROUTER_QWEN_3_7_MAX_MODEL, 1_000_000, 65_536), (OPENROUTER_XIAOMI_MIMO_V2_5_PRO_MODEL, 1_000_000, 131_072), (OPENROUTER_MINIMAX_M3_MODEL, 1_000_000, 524_288), ] { diff --git a/crates/tui/src/main.rs b/crates/tui/src/main.rs index 884a16ed..9feaaac4 100644 --- a/crates/tui/src/main.rs +++ b/crates/tui/src/main.rs @@ -6076,7 +6076,7 @@ mod terminal_mode_tests { default_text_model: Some("deepseek/deepseek-v4-pro".to_string()), providers: Some(crate::config::ProvidersConfig { openrouter: crate::config::ProviderConfig { - model: Some("qwen/qwen3.7-max".to_string()), + model: Some("arcee-ai/trinity-large-thinking".to_string()), ..Default::default() }, ..Default::default() @@ -6084,7 +6084,10 @@ mod terminal_mode_tests { ..Default::default() }; - assert_eq!(resolve_exec_model(&config, None), "qwen/qwen3.7-max"); + assert_eq!( + resolve_exec_model(&config, None), + "arcee-ai/trinity-large-thinking" + ); assert_eq!( resolve_exec_model(&config, Some("arcee-ai/trinity-large-thinking")), "arcee-ai/trinity-large-thinking" diff --git a/crates/tui/src/models.rs b/crates/tui/src/models.rs index bee4fa12..301e9c09 100644 --- a/crates/tui/src/models.rs +++ b/crates/tui/src/models.rs @@ -253,8 +253,7 @@ fn known_context_window_for_model(model_lower: &str) -> Option { | "moonshotai/kimi-k2.6:free" => Some(262_144), "z-ai/glm-5.1" | "z-ai/glm-5v-turbo" => Some(202_752), "minimax/minimax-m3" => Some(1_000_000), - "qwen/qwen3.7-max" - | "xiaomi/mimo-v2.5-pro" + "xiaomi/mimo-v2.5-pro" | "xiaomi/mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5" @@ -276,7 +275,7 @@ pub fn max_output_tokens_for_model(model: &str) -> Option { "xiaomi/mimo-v2.5-pro" | "xiaomi/mimo-v2.5" | "mimo-v2.5-pro" | "mimo-v2.5" => { Some(131_072) } - "qwen/qwen3.7-max" | "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free" => Some(65_536), + "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free" => Some(65_536), "google/gemma-4-31b-it" => Some(16_384), "google/gemma-4-31b-it:free" | "google/gemma-4-26b-a4b-it:free" => Some(32_768), _ => None, @@ -300,7 +299,6 @@ pub fn model_supports_reasoning(model: &str) -> bool { | "moonshotai/kimi-k2.6:free" | "minimax/minimax-m3" | "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free" - | "qwen/qwen3.7-max" | "qwen/qwen3.6-35b-a3b" | "qwen/qwen3.6-27b" | "tencent/hy3-preview" @@ -500,7 +498,6 @@ mod tests { fn recent_openrouter_large_models_have_static_windows() { for (model, expected_window) in [ ("arcee-ai/trinity-large-thinking", 262_144), - (concat!("qwen/", "qwen3.7-max"), 1_000_000), (concat!("qwen/", "qwen3.6-35b-a3b"), 262_144), (concat!("xiaomi/", "mimo-v2.5-pro"), 1_000_000), ("mimo-v2.5-pro", 1_000_000), @@ -520,10 +517,6 @@ mod tests { max_output_tokens_for_model("arcee-ai/trinity-large-thinking"), Some(262_144) ); - assert_eq!( - max_output_tokens_for_model(concat!("qwen/", "qwen3.7-max")), - Some(65_536) - ); assert_eq!( max_output_tokens_for_model(concat!("xiaomi/", "mimo-v2.5-pro")), Some(131_072) diff --git a/crates/tui/src/tui/app.rs b/crates/tui/src/tui/app.rs index c2549fac..7a8e46bb 100644 --- a/crates/tui/src/tui/app.rs +++ b/crates/tui/src/tui/app.rs @@ -1445,6 +1445,10 @@ pub struct App { pub submit_pending_steers_after_interrupt: bool, /// Start time for current turn pub turn_started_at: Option, + /// Most recent engine event observed for the current turn. This is + /// separate from `turn_started_at` because the latter drives elapsed-time + /// UI and must not be reset during long but healthy turns. + pub turn_last_activity_at: Option, /// Sum of completed turn durations for this `App` instance (#448 /// follow-up). Drives the footer's `worked Nh Mm` chip so the /// label reflects actual model work, not wall-clock since launch. @@ -2055,6 +2059,7 @@ impl App { rejected_steers: VecDeque::new(), submit_pending_steers_after_interrupt: false, turn_started_at: None, + turn_last_activity_at: None, cumulative_turn_duration: std::time::Duration::ZERO, balance_cell: std::sync::Arc::new(std::sync::Mutex::new(None)), balance_initiated: false, diff --git a/crates/tui/src/tui/model_picker.rs b/crates/tui/src/tui/model_picker.rs index 6413c3f9..a6cc22f9 100644 --- a/crates/tui/src/tui/model_picker.rs +++ b/crates/tui/src/tui/model_picker.rs @@ -331,7 +331,6 @@ fn picker_model_hint(id: &str) -> &'static str { "faster model" } "arcee-ai/trinity-large-thinking" => "large thinking", - "qwen/qwen3.7-max" => "large Qwen", "xiaomi/mimo-v2.5-pro" | "mimo-v2.5-pro" => "long context", "minimax/minimax-m3" => "1M multimodal", _ => "provider model", @@ -640,14 +639,13 @@ mod tests { let (mut app, _lock) = create_test_app(); app.api_provider = crate::config::ApiProvider::Openrouter; app.model_ids_passthrough = true; - app.model = "qwen/qwen3.7-max".to_string(); + app.model = "minimax/minimax-m3".to_string(); app.auto_model = false; let view = ModelPickerView::new(&app); let model_ids = view.visible_model_ids(); assert!(model_ids.contains(&"arcee-ai/trinity-large-thinking")); - assert!(model_ids.contains(&"qwen/qwen3.7-max")); assert!(model_ids.contains(&"xiaomi/mimo-v2.5-pro")); assert!(model_ids.contains(&"minimax/minimax-m3")); assert!( @@ -658,7 +656,7 @@ mod tests { "MiniMax M3 should be visible in the first picker window on normal terminals" ); assert!(!view.show_custom_model_row); - assert_eq!(view.resolved_model(), "qwen/qwen3.7-max"); + assert_eq!(view.resolved_model(), "minimax/minimax-m3"); } #[test] diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 779f0dc0..e92a2a05 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -1158,6 +1158,7 @@ async fn run_event_loop( } else if !app.is_loading && ignore_stale_stream_event_while_idle(&event) { continue; } + record_turn_activity(app, &event, Instant::now()); match event { EngineEvent::MessageStarted { .. } => { // Assistant text starting after parallel tool work @@ -1474,7 +1475,9 @@ async fn run_event_loop( app.streaming_state.reset(); app.streaming_message_index = None; app.streaming_thinking_active_entry = None; - app.turn_started_at = Some(Instant::now()); + let now = Instant::now(); + app.turn_started_at = Some(now); + app.turn_last_activity_at = Some(now); // Discoverability hint for users who don't know how // to interrupt a long-running turn (#1367). Only // surface when the status_message slot is empty so @@ -1541,6 +1544,7 @@ async fn run_event_loop( let turn_elapsed = app.turn_started_at.map(|t| t.elapsed()).unwrap_or_default(); app.turn_started_at = None; + app.turn_last_activity_at = None; // Roll the just-finished turn's elapsed time into the // cumulative session work-time (#448 follow-up). The // footer's `worked Nh Mm` chip reads this so the @@ -4077,6 +4081,7 @@ fn reconcile_turn_liveness(app: &mut App, now: Instant, has_running_agents: bool app.is_loading = false; app.dispatch_started_at = None; app.turn_started_at = None; + app.turn_last_activity_at = None; app.push_status_toast( "Turn dispatch timed out; the engine may have stopped. Please try again.", StatusToastLevel::Error, @@ -4097,6 +4102,7 @@ fn reconcile_turn_liveness(app: &mut App, now: Instant, has_running_agents: bool app.is_loading = false; app.dispatch_started_at = None; app.turn_started_at = None; + app.turn_last_activity_at = None; app.push_status_toast( "Recovered from an inconsistent busy state.", StatusToastLevel::Warning, @@ -4111,9 +4117,13 @@ fn reconcile_turn_liveness(app: &mut App, now: Instant, has_running_agents: bool && matches!(app.runtime_turn_status.as_deref(), Some("in_progress")) && !has_running_agents && !app.is_compacting - && app.turn_started_at.is_some_and(|started| { - now.saturating_duration_since(started) > TURN_STALL_WATCHDOG_TIMEOUT - }) + && !active_turn_has_running_tool(app) + && app + .turn_last_activity_at + .or(app.turn_started_at) + .is_some_and(|last_activity| { + now.saturating_duration_since(last_activity) > TURN_STALL_WATCHDOG_TIMEOUT + }) { // Finalize in-flight thinking / assistant / tool cells so the // transcript doesn't show permanent spinners after recovery. @@ -4126,6 +4136,7 @@ fn reconcile_turn_liveness(app: &mut App, now: Instant, has_running_agents: bool app.is_loading = false; app.turn_started_at = None; + app.turn_last_activity_at = None; app.runtime_turn_status = None; app.runtime_turn_id = None; app.dispatch_started_at = None; @@ -4142,6 +4153,44 @@ fn reconcile_turn_liveness(app: &mut App, now: Instant, has_running_agents: bool false } +fn record_turn_activity(app: &mut App, event: &EngineEvent, now: Instant) { + if matches!(event, EngineEvent::TurnStarted { .. }) { + app.turn_last_activity_at = Some(now); + return; + } + + if app.is_loading || matches!(app.runtime_turn_status.as_deref(), Some("in_progress")) { + app.turn_last_activity_at = Some(now); + } +} + +fn active_turn_has_running_tool(app: &App) -> bool { + app.active_cell.as_ref().is_some_and(|active| { + active.entries().iter().any(|cell| match cell { + HistoryCell::Tool(tool) => tool_cell_is_running(tool), + _ => false, + }) + }) +} + +fn tool_cell_is_running(tool: &ToolCell) -> bool { + match tool { + ToolCell::Exec(cell) => cell.status == ToolStatus::Running, + ToolCell::Exploring(cell) => cell + .entries + .iter() + .any(|entry| entry.status == ToolStatus::Running), + ToolCell::PlanUpdate(cell) => cell.status == ToolStatus::Running, + ToolCell::PatchSummary(cell) => cell.status == ToolStatus::Running, + ToolCell::Review(cell) => cell.status == ToolStatus::Running, + ToolCell::DiffPreview(_) => false, + ToolCell::Mcp(cell) => cell.status == ToolStatus::Running, + ToolCell::ViewImage(_) => false, + ToolCell::WebSearch(cell) => cell.status == ToolStatus::Running, + ToolCell::Generic(cell) => cell.status == ToolStatus::Running, + } +} + /// Translate an `EngineEvent::Error` into UI state updates. /// /// The engine's `recoverable` flag (mirrored on `ErrorEnvelope`) decides @@ -6879,6 +6928,7 @@ fn mark_active_turn_cancelled_locally(app: &mut App) { app.is_loading = false; app.dispatch_started_at = None; app.turn_started_at = None; + app.turn_last_activity_at = None; app.streaming_state.reset(); app.runtime_turn_id = None; app.runtime_turn_status = None; diff --git a/crates/tui/src/tui/ui/tests.rs b/crates/tui/src/tui/ui/tests.rs index 489cc8f2..16603137 100644 --- a/crates/tui/src/tui/ui/tests.rs +++ b/crates/tui/src/tui/ui/tests.rs @@ -2509,6 +2509,55 @@ fn turn_liveness_leaves_active_turn_running() { assert!(app.status_toasts.is_empty()); } +#[test] +fn turn_liveness_uses_recent_turn_activity_not_turn_start() { + let mut app = create_test_app(); + let now = Instant::now(); + app.is_loading = true; + app.runtime_turn_status = Some("in_progress".to_string()); + app.turn_started_at = Some(now - TURN_STALL_WATCHDOG_TIMEOUT - Duration::from_secs(30)); + app.turn_last_activity_at = Some(now - Duration::from_secs(1)); + + let recovered = reconcile_turn_liveness(&mut app, now, false); + + assert!(!recovered); + assert!(app.is_loading); + assert!(app.runtime_turn_status.is_some()); + assert!(app.status_toasts.is_empty()); +} + +#[test] +fn turn_liveness_does_not_abort_running_tool() { + let mut app = create_test_app(); + let now = Instant::now(); + app.is_loading = true; + app.runtime_turn_status = Some("in_progress".to_string()); + app.turn_started_at = Some(now - TURN_STALL_WATCHDOG_TIMEOUT - Duration::from_secs(30)); + app.turn_last_activity_at = app.turn_started_at; + let mut active = ActiveCell::new(); + active.push_tool( + "tool-1", + HistoryCell::Tool(ToolCell::Generic(GenericToolCell { + name: "edit_file".to_string(), + status: ToolStatus::Running, + input_summary: Some("path: CHANGELOG.md".to_string()), + output: None, + prompts: None, + spillover_path: None, + output_summary: None, + is_diff: false, + })), + ); + app.active_cell = Some(active); + + let recovered = reconcile_turn_liveness(&mut app, now, false); + + assert!(!recovered); + assert!(app.is_loading); + assert!(app.active_cell.is_some()); + assert!(app.status_toasts.is_empty()); +} + #[test] fn turn_liveness_recovers_stalled_in_progress_turn() { let mut app = create_test_app(); diff --git a/crates/tui/src/utils.rs b/crates/tui/src/utils.rs index 1be800e6..756c483d 100644 --- a/crates/tui/src/utils.rs +++ b/crates/tui/src/utils.rs @@ -239,14 +239,14 @@ fn browser_open_command(url: &str) -> Result { { let mut command = Command::new("open"); command.arg(url); - return Ok(command); + Ok(command) } #[cfg(target_os = "linux")] { let mut command = Command::new("xdg-open"); command.arg(url); - return Ok(command); + Ok(command) } #[cfg(target_os = "windows")] diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index ab8e670f..de9a03a6 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -590,7 +590,7 @@ If you are upgrading from older releases: - `provider` (string, optional): `deepseek` (default), `nvidia-nim`, `openai`, `atlascloud`, `wanjie-ark`, `openrouter`, `xiaomi-mimo`, `novita`, `fireworks`, `siliconflow`, `moonshot`, `sglang`, `vllm`, or `ollama`. Legacy `deepseek-cn` configs are still accepted as an alias for `deepseek`; DeepSeek uses the same official host [`https://api.deepseek.com`](https://api-docs.deepseek.com/) worldwide. `nvidia-nim` targets NVIDIA's NIM-hosted DeepSeek endpoints through `https://integrate.api.nvidia.com/v1`; `openai` targets a generic OpenAI-compatible endpoint, defaulting to `https://api.openai.com/v1`; `atlascloud` targets AtlasCloud's OpenAI-compatible endpoint at `https://api.atlascloud.ai/v1`; `wanjie-ark` targets Wanjie Ark's OpenAI-compatible endpoint at `https://maas-openapi.wanjiedata.com/api/v1`; `openrouter` targets `https://openrouter.ai/api/v1`; `xiaomi-mimo` targets Xiaomi MiMo's OpenAI-compatible endpoint at `https://api.xiaomimimo.com/v1`; `novita` targets `https://api.novita.ai/v1`; `fireworks` targets `https://api.fireworks.ai/inference/v1`; `siliconflow` targets SiliconFlow, defaulting to `https://api.siliconflow.com/v1`; `moonshot` targets Moonshot/Kimi, defaulting to `https://api.moonshot.ai/v1`; `sglang` targets a self-hosted OpenAI-compatible endpoint, defaulting to `http://localhost:30000/v1`; `vllm` targets a self-hosted vLLM OpenAI-compatible endpoint, defaulting to `http://localhost:8000/v1`; `ollama` targets Ollama's OpenAI-compatible endpoint, defaulting to `http://localhost:11434/v1`. - `api_key` (string, required for hosted providers): must be non-empty for DeepSeek/hosted providers (or set the provider API key env var). Self-hosted SGLang, vLLM, and Ollama can omit it. - `base_url` (string, optional): defaults to `https://api.deepseek.com/beta` for DeepSeek's OpenAI-compatible Chat Completions API, including legacy `provider = "deepseek-cn"` configs. Other defaults are `https://integrate.api.nvidia.com/v1` for `nvidia-nim`, `https://api.openai.com/v1` for `openai`, `https://api.atlascloud.ai/v1` for `atlascloud`, `https://maas-openapi.wanjiedata.com/api/v1` for `wanjie-ark`, `https://openrouter.ai/api/v1` for `openrouter`, `https://api.xiaomimimo.com/v1` for `xiaomi-mimo`, `https://api.novita.ai/v1` for `novita`, `https://api.fireworks.ai/inference/v1` for `fireworks`, `https://api.siliconflow.com/v1` for `siliconflow`, `https://api.moonshot.ai/v1` for `moonshot`, `http://localhost:30000/v1` for `sglang`, `http://localhost:8000/v1` for `vllm`, and `http://localhost:11434/v1` for `ollama`. Set `https://api.deepseek.com` or `https://api.deepseek.com/v1` explicitly to opt out of DeepSeek beta features. -- `default_text_model` (string, optional): defaults to `deepseek-v4-pro` for DeepSeek and generic OpenAI-compatible endpoints, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `deepseek-ai/deepseek-v4-flash` for AtlasCloud, `deepseek-reasoner` for Wanjie Ark, `deepseek/deepseek-v4-pro` for OpenRouter and Novita, `mimo-v2.5-pro` for Xiaomi MiMo, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `deepseek-ai/DeepSeek-V4-Pro` for SiliconFlow, `kimi-k2.6` for Moonshot, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `deepseek-coder:1.3b` for Ollama. Current public DeepSeek IDs are `deepseek-v4-pro` and `deepseek-v4-flash`, both with 1M context windows, 384K max output, and thinking mode enabled by default. Legacy `deepseek-chat` and `deepseek-reasoner` remain compatibility aliases for `deepseek-v4-flash` until July 24, 2026, except SiliconFlow maps `deepseek-reasoner` and `deepseek-r1` to its Pro model while `deepseek-chat` and `deepseek-v3` map to Flash. Provider-specific mappings translate `deepseek-v4-pro` / `deepseek-v4-flash` to each provider's model ID where supported. OpenRouter also recognizes recent large IDs such as `arcee-ai/trinity-large-thinking`, `qwen/qwen3.7-max`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `qwen/qwen3.6-35b-a3b`, `google/gemma-4-31b-it`, and `moonshotai/kimi-k2.6`. Generic `openai`, `atlascloud`, `wanjie-ark`, `xiaomi-mimo`, and Ollama model IDs are passed through unchanged. OpenRouter and SiliconFlow provider configs with a custom `base_url` also preserve explicit model values, which lets OpenAI-compatible gateways accept bare model IDs. Use `/models` or `codewhale models` to discover live IDs from your configured endpoint. `CODEWHALE_MODEL` overrides this for a single process; `DEEPSEEK_MODEL` is the legacy alias. +- `default_text_model` (string, optional): defaults to `deepseek-v4-pro` for DeepSeek and generic OpenAI-compatible endpoints, `deepseek-ai/deepseek-v4-pro` for NVIDIA NIM, `deepseek-ai/deepseek-v4-flash` for AtlasCloud, `deepseek-reasoner` for Wanjie Ark, `deepseek/deepseek-v4-pro` for OpenRouter and Novita, `mimo-v2.5-pro` for Xiaomi MiMo, `accounts/fireworks/models/deepseek-v4-pro` for Fireworks, `deepseek-ai/DeepSeek-V4-Pro` for SiliconFlow, `kimi-k2.6` for Moonshot, `deepseek-ai/DeepSeek-V4-Pro` for SGLang/vLLM, and `deepseek-coder:1.3b` for Ollama. Current public DeepSeek IDs are `deepseek-v4-pro` and `deepseek-v4-flash`, both with 1M context windows, 384K max output, and thinking mode enabled by default. Legacy `deepseek-chat` and `deepseek-reasoner` remain compatibility aliases for `deepseek-v4-flash` until July 24, 2026, except SiliconFlow maps `deepseek-reasoner` and `deepseek-r1` to its Pro model while `deepseek-chat` and `deepseek-v3` map to Flash. Provider-specific mappings translate `deepseek-v4-pro` / `deepseek-v4-flash` to each provider's model ID where supported. OpenRouter also recognizes recent large IDs such as `arcee-ai/trinity-large-thinking`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `qwen/qwen3.6-35b-a3b`, `google/gemma-4-31b-it`, and `moonshotai/kimi-k2.6`. Generic `openai`, `atlascloud`, `wanjie-ark`, `xiaomi-mimo`, and Ollama model IDs are passed through unchanged. OpenRouter and SiliconFlow provider configs with a custom `base_url` also preserve explicit model values, which lets OpenAI-compatible gateways accept bare model IDs. Use `/models` or `codewhale models` to discover live IDs from your configured endpoint. `CODEWHALE_MODEL` overrides this for a single process; `DEEPSEEK_MODEL` is the legacy alias. - `reasoning_effort` (string, optional): `off`, `low`, `medium`, `high`, or `max`; defaults to the configured UI tier. DeepSeek Platform receives top-level `thinking` / `reasoning_effort` fields. NVIDIA NIM receives equivalent settings through `chat_template_kwargs`. - `allow_shell` (bool, optional): defaults to `true` (sandboxed). - `approval_policy` (string, optional): `on-request`, `untrusted`, or `never`. Runtime `approval_mode` editing in `/config` also accepts `on-request` and `untrusted` aliases. diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 75fc9cf3..aaadc14b 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -117,7 +117,7 @@ endpoint. | `atlascloud` | `[providers.atlascloud]` | `ATLASCLOUD_API_KEY` | `ATLASCLOUD_BASE_URL`; default `https://api.atlascloud.ai/v1` | `deepseek-ai/deepseek-v4-flash`, `deepseek-ai/deepseek-v4-pro` | OpenAI-compatible hosted route. `ATLASCLOUD_MODEL` is accepted by the TUI config path, and the static `ModelRegistry` includes AtlasCloud fallback rows for CLI model resolution. | | `wanjie-ark` | `[providers.wanjie_ark]` | `WANJIE_ARK_API_KEY`, `WANJIE_API_KEY`, `WANJIE_MAAS_API_KEY` | `WANJIE_ARK_BASE_URL`, `WANJIE_BASE_URL`, `WANJIE_MAAS_BASE_URL`; default `https://maas-openapi.wanjiedata.com/api/v1` | `deepseek-reasoner` | OpenAI-compatible hosted route. `WANJIE_ARK_MODEL`, `WANJIE_MODEL`, and `WANJIE_MAAS_MODEL` are accepted. | | `volcengine` | `[providers.volcengine]` | `VOLCENGINE_API_KEY`, `VOLCENGINE_ARK_API_KEY`, `ARK_API_KEY` | `VOLCENGINE_BASE_URL`, `VOLCENGINE_ARK_BASE_URL`, `ARK_BASE_URL`; default `https://ark.cn-beijing.volces.com/api/coding/v3` | `DeepSeek-V4-Pro`, `DeepSeek-V4-Flash` | Volcengine/Volcano Engine Ark OpenAI-compatible coding endpoint. `VOLCENGINE_MODEL` and `VOLCENGINE_ARK_MODEL` are accepted. | -| `openrouter` | `[providers.openrouter]` | `OPENROUTER_API_KEY` | `OPENROUTER_BASE_URL`; default `https://openrouter.ai/api/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`; recent large IDs include `arcee-ai/trinity-large-thinking`, `qwen/qwen3.7-max`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `qwen/qwen3.6-35b-a3b`, `google/gemma-4-31b-it`, `z-ai/glm-5.1`, `moonshotai/kimi-k2.6` | Additive open-model routing layer. It does not replace DeepSeek; it lets users route supported model IDs through OpenRouter when they choose it. | +| `openrouter` | `[providers.openrouter]` | `OPENROUTER_API_KEY` | `OPENROUTER_BASE_URL`; default `https://openrouter.ai/api/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`; recent large IDs include `arcee-ai/trinity-large-thinking`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `qwen/qwen3.6-35b-a3b`, `google/gemma-4-31b-it`, `z-ai/glm-5.1`, `moonshotai/kimi-k2.6` | Additive open-model routing layer. It does not replace DeepSeek; it lets users route supported model IDs through OpenRouter when they choose it. | | `xiaomi-mimo` | `[providers.xiaomi_mimo]` | `XIAOMI_MIMO_API_KEY`, `XIAOMI_API_KEY`, `MIMO_API_KEY` | `XIAOMI_MIMO_BASE_URL`, `MIMO_BASE_URL`; default `https://api.xiaomimimo.com/v1` | `mimo-v2.5-pro`, `mimo-v2.5` | Xiaomi MiMo OpenAI-compatible chat completions route. It sends `max_completion_tokens` and uses MiMo's `thinking` field for reasoning control. | | `novita` | `[providers.novita]` | `NOVITA_API_KEY` | `NOVITA_BASE_URL`; default `https://api.novita.ai/v1` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | OpenAI-compatible hosted route for DeepSeek model IDs. Use config or `CODEWHALE_MODEL` / `DEEPSEEK_MODEL` for model overrides. | | `fireworks` | `[providers.fireworks]` | `FIREWORKS_API_KEY` | `FIREWORKS_BASE_URL`; default `https://api.fireworks.ai/inference/v1` | `accounts/fireworks/models/deepseek-v4-pro` | OpenAI-compatible hosted route. Use config or `CODEWHALE_MODEL` / `DEEPSEEK_MODEL` for model overrides. | @@ -144,9 +144,7 @@ large models verified through OpenRouter's model metadata: `qwen/qwen3.6-27b`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `xiaomi/mimo-v2.5`, `moonshotai/kimi-k2.6`, `z-ai/glm-5.1`, `tencent/hy3-preview`, `google/gemma-4-31b-it`, `google/gemma-4-26b-a4b-it`, and -`nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free`. `qwen/qwen3.7-max` -is also included because it is a current user-requested large OpenRouter model, -but it is treated as a hosted Qwen model rather than documented as open-weight. +`nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free`. `minimax/minimax-m3` was added from OpenRouter's May 31, 2026 listing as a 1M context multimodal model for coding, tool use, and long-horizon agentic work. @@ -165,7 +163,7 @@ endpoint when the endpoint supports model listing. | `atlascloud` | `deepseek-ai/deepseek-v4-flash`, `deepseek-ai/deepseek-v4-pro` | yes | yes | | `wanjie-ark` | `deepseek-reasoner` | yes | yes | | `volcengine` | `DeepSeek-V4-Pro`, `DeepSeek-V4-Flash` | yes | yes | -| `openrouter` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`, `arcee-ai/trinity-large-thinking`, `qwen/qwen3.7-max`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `xiaomi/mimo-v2.5`, `qwen/qwen3.6-35b-a3b`, `qwen/qwen3.6-27b`, `moonshotai/kimi-k2.6`, `z-ai/glm-5.1`, `tencent/hy3-preview`, `google/gemma-4-31b-it`, `google/gemma-4-26b-a4b-it`, `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free` | yes | yes | +| `openrouter` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash`, `arcee-ai/trinity-large-thinking`, `minimax/minimax-m3`, `xiaomi/mimo-v2.5-pro`, `xiaomi/mimo-v2.5`, `qwen/qwen3.6-35b-a3b`, `qwen/qwen3.6-27b`, `moonshotai/kimi-k2.6`, `z-ai/glm-5.1`, `tencent/hy3-preview`, `google/gemma-4-31b-it`, `google/gemma-4-26b-a4b-it`, `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free` | yes | yes | | `xiaomi-mimo` | `mimo-v2.5-pro`, `mimo-v2.5` | yes | yes | | `novita` | `deepseek/deepseek-v4-pro`, `deepseek/deepseek-v4-flash` | yes | yes | | `fireworks` | `accounts/fireworks/models/deepseek-v4-pro` | yes | yes | diff --git a/web/lib/facts.generated.ts b/web/lib/facts.generated.ts index e995d063..84f3e41f 100644 --- a/web/lib/facts.generated.ts +++ b/web/lib/facts.generated.ts @@ -18,7 +18,7 @@ export interface RepoFacts { } export const FACTS: RepoFacts = { - "generatedAt": "2026-06-01T00:40:33.053Z", + "generatedAt": "2026-06-01T04:44:32.528Z", "version": "0.8.48", "crates": [ "agent", diff --git a/web/package-lock.json b/web/package-lock.json index 33806a68..1958d3fa 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1669,7 +1669,6 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", - "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -2435,7 +2434,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2458,7 +2456,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2481,7 +2478,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2498,7 +2494,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2515,7 +2510,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2532,7 +2526,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2549,7 +2542,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2566,7 +2558,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2583,7 +2574,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2600,7 +2590,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2617,7 +2606,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2634,7 +2622,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -2651,7 +2638,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2674,7 +2660,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2697,7 +2682,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2720,7 +2704,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2743,7 +2726,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2766,7 +2748,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2789,7 +2770,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2812,7 +2792,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -2835,7 +2814,6 @@ "cpu": [ "wasm32" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -2855,7 +2833,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -2875,7 +2852,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -2895,7 +2871,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ @@ -13107,474 +13082,6 @@ } } }, - "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/vitest/node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">=18" - } - }, "node_modules/vitest/node_modules/@vitest/mocker": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", @@ -13602,50 +13109,6 @@ } } }, - "node_modules/vitest/node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" - } - }, "node_modules/vitest/node_modules/vite": { "version": "8.0.14", "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz",