From 83e1e3a51bda251ad146bb66259629de72c2ae2c Mon Sep 17 00:00:00 2001 From: Hmbown <101357273+Hmbown@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:55:11 +0000 Subject: [PATCH 1/7] test(prompts): serialize override-notice test on lock_test_env config_override_requires_explicit_opt_in drains the process-global PROMPT_OVERRIDE_NOTICES queue but didn't hold test_support::lock_test_env(), while its sibling (tui::ui::tests::prompt_override_notice_surfaces_in_transcript_and_toast) does. With only one side locking, the two could interleave their take_prompt_override_notices() calls under the multi-threaded test binary and intermittently see an empty vec. Take the lock so both serialize. Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> --- crates/tui/src/prompts.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/tui/src/prompts.rs b/crates/tui/src/prompts.rs index 4907595c08..5cf1b74f85 100644 --- a/crates/tui/src/prompts.rs +++ b/crates/tui/src/prompts.rs @@ -1517,10 +1517,13 @@ mod tests { #[test] fn config_override_requires_explicit_opt_in() { // A present, non-empty override file must NOT replace the base prompt - // unless the explicit opt-in flag is set. When the flag is unset - // `load_config_dir_prompt_overrides` applies nothing (and never touches - // the global override cell), so this assertion is safe to run in the - // shared test binary. + // unless the explicit opt-in flag is set. This test drains the shared + // process-global PROMPT_OVERRIDE_NOTICES queue, so it must serialize + // against the sibling test that also touches it + // (`tui::ui::tests::prompt_override_notice_surfaces_in_transcript_and_toast`); + // both take `lock_test_env()` for mutual exclusion under the multi- + // threaded test binary. + let _env_guard = crate::test_support::lock_test_env(); let tmp = tempdir().expect("tempdir"); let prompts_dir = tmp.path().join("prompts"); std::fs::create_dir_all(&prompts_dir).expect("mkdir"); From 39a8218a2c302470bc755f32b8a399b7e519c648 Mon Sep 17 00:00:00 2001 From: Hmbown <101357273+Hmbown@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:55:11 +0000 Subject: [PATCH 2/7] fix(skills): warn on same-root skill-name collisions (#3919) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit discover_from_directories already warns when two roots yield the same normalized command name, but discover_recursive pushed every SKILL.md unconditionally. With aggressive slugging, two sibling dirs under one root (e.g. `My Skill/` and `my_skill/` → `my-skill`) could both land in the registry with identical names; get() only ever resolves the first, leaving the other silently unreachable. Mirror the cross-root behavior: keep the first and emit a "shadowed by" warning. Adds a same-root regression test. Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> --- crates/tui/src/skills/mod.rs | 55 +++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/crates/tui/src/skills/mod.rs b/crates/tui/src/skills/mod.rs index e567e4a64d..c7c0f8c40e 100644 --- a/crates/tui/src/skills/mod.rs +++ b/crates/tui/src/skills/mod.rs @@ -239,7 +239,27 @@ impl SkillRegistry { } skill.path = skill_path.clone(); registry.normalize_skill_name(&mut skill, &skill_path); - registry.skills.push(skill); + // Two sibling directories under the same root can + // normalize to the same command name (e.g. `My Skill/` + // and `my_skill/` both slugify to `my-skill`). Keep the + // first (matching the cross-root merge in + // `discover_from_directories`) and warn instead of + // silently pushing an unreachable duplicate (#3919). + let shadowed_by = registry + .skills + .iter() + .find(|s| s.name == skill.name) + .map(|s| s.path.clone()); + if let Some(existing_path) = shadowed_by { + registry.push_warning(format!( + "Skill `{}` at {} is shadowed by {}.", + skill.name, + skill.path.display(), + existing_path.display() + )); + } else { + registry.skills.push(skill); + } // This directory IS a skill. Don't descend further: // any nested `SKILL.md` would be a fixture or // example bundled with the parent skill, not a @@ -1412,6 +1432,39 @@ body"; ); } + #[test] + fn same_root_slug_collision_warns_and_keeps_one() { + let tmpdir = TempDir::new().unwrap(); + let root = tmpdir.path(); + // Two sibling directories under one root whose frontmatter names + // slugify to the same command name ("my-skill"). Only one can be + // reachable by name; the other must warn rather than silently coexist + // as an unreachable duplicate (#3919 same-root gap). + write_skill(root, "My Skill", "first", "body"); + write_skill(root, "my_skill", "second", "body"); + + let registry = super::SkillRegistry::discover(root); + let claimants = registry + .list() + .iter() + .filter(|s| s.name == "my-skill") + .count(); + assert_eq!( + claimants, + 1, + "exactly one skill should claim `my-skill`, got {:?}", + registry.list().iter().map(|s| &s.name).collect::>() + ); + assert!( + registry + .warnings() + .iter() + .any(|w| w.contains("my-skill") && w.contains("shadowed by")), + "same-root slug collision should warn, got {:?}", + registry.warnings() + ); + } + #[test] fn discover_in_workspace_pulls_skills_from_opencode_dir() { let tmpdir = TempDir::new().unwrap(); From 6446a3ea239d49c92deb93242f6db4d4aa8350bd Mon Sep 17 00:00:00 2001 From: Hmbown <101357273+Hmbown@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:55:11 +0000 Subject: [PATCH 3/7] fix(onboarding): don't let Enter silently trust the workspace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trusting a workspace is a security boundary. The Trust step bound Enter — the "advance" key on every other onboarding screen — to complete trust, so a reflexive Enter granted execution trust, and the behavior was inconsistent with the footer (which lists only 1/Y to trust, 2/N to exit). Enter now surfaces a guidance hint pointing at the explicit keys instead of trusting; it is neither an accidental-trust path nor a silent dead key. The explicit Y/1 and N/2 handlers are unchanged. Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> --- crates/tui/src/tui/ui.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 2d01154143..9bbf5d47fe 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -3925,10 +3925,16 @@ async fn run_event_loop( } } OnboardingState::TrustDirectory => { - if let Err(err) = complete_trust_directory_onboarding(app, config) { - app.status_message = - Some(format!("Failed to trust workspace: {err}")); - } + // Trusting a workspace is a security boundary, so it + // must be a deliberate choice. Enter — the "advance" + // key on every other onboarding screen — must NOT + // grant trust by reflex (accidental-trust risk). Nor + // is it a silent dead key: point the user at the + // explicit keys the footer advertises. + app.status_message = Some( + "Press 1 or Y to trust this workspace, or 2 or N to exit." + .to_string(), + ); } OnboardingState::Tips => { app.finish_onboarding_without_feature_intro(); From c4b4ad57a9990eeec9bea49b0a13d5473a19e03e Mon Sep 17 00:00:00 2001 From: Hmbown <101357273+Hmbown@users.noreply.github.com> Date: Sun, 5 Jul 2026 02:55:11 +0000 Subject: [PATCH 4/7] docs(plugins): clarify overrides_path invariant The field doc claimed None "in tests / when no home directory is available," but discovery always sets the store and default_user_plugins_dir falls back to /tmp. Reword to the real invariant: set by discovery; None only when a registry is built without a store (e.g. a direct PluginRegistry::new()). Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> --- crates/tui/src/plugins/registry.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/tui/src/plugins/registry.rs b/crates/tui/src/plugins/registry.rs index 41f338ba80..473af3dcd3 100644 --- a/crates/tui/src/plugins/registry.rs +++ b/crates/tui/src/plugins/registry.rs @@ -7,8 +7,11 @@ use super::manifest::LoadedPlugin; pub struct PluginRegistry { plugins: HashMap, user_overrides: HashMap, - /// Where `user_overrides` is persisted. `None` in tests / when no home - /// directory is available, in which case enable/disable stays in-memory. + /// Where `user_overrides` is persisted. Discovery always sets this via + /// [`set_overrides_store`](Self::set_overrides_store); it is `None` only + /// when a registry is built without a persistence store (e.g. a direct + /// `PluginRegistry::new()` in unit tests), in which case enable/disable + /// stays in-memory. overrides_path: Option, } From 1c46138827dd49e7d3cedd0e34070c60ae48c28e Mon Sep 17 00:00:00 2001 From: Hmbown <101357273+Hmbown@users.noreply.github.com> Date: Sun, 5 Jul 2026 04:26:17 +0000 Subject: [PATCH 5/7] feat(providers): add Meituan LongCat as a first-class provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds LongCat (Meituan's LongCat API platform) as a built-in OpenAI-compatible Chat Completions provider, mirroring the existing Sakana provider wiring: - ProviderKind::LongCat / ApiProvider::LongCat with aliases (long-cat, meituan-longcat, meituan) and LONGCAT_API_KEY / LONGCAT_BASE_URL / LONGCAT_MODEL env overrides. - Defaults: model LongCat-2.0, base https://api.longcat.chat/openai/v1, credential page https://longcat.chat/platform. - Registered in the provider registry + ModelRegistry (LongCat-2.0), and wired through every provider match (config accessors, defaults, reasoning-effort, base-url persistence, env overrides, merge, header badge). - API-key based like Sakana — deliberately NOT routed through the OpenAI Codex OAuth "missing credentials" message. - Docs (PROVIDERS.md) and config.example.toml updated. Verified: cargo check --workspace --all-features, the CI clippy gate (-D warnings), codewhale-config tests (347), and tui provider tests (366) all pass. Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> --- config.example.toml | 11 ++++++- crates/agent/src/lib.rs | 8 +++++ crates/cli/src/lib.rs | 3 ++ crates/config/src/lib.rs | 22 +++++++++++++ crates/config/src/provider.rs | 44 +++++++++++++++++--------- crates/config/src/provider_defaults.rs | 3 ++ crates/config/src/provider_kind.rs | 5 ++- crates/secrets/src/lib.rs | 2 ++ crates/tui/src/client.rs | 3 ++ crates/tui/src/config.rs | 35 ++++++++++++++++++-- crates/tui/src/config/models.rs | 2 ++ crates/tui/src/config_persistence.rs | 1 + crates/tui/src/tui/ui.rs | 3 ++ docs/PROVIDERS.md | 6 +++- 14 files changed, 128 insertions(+), 20 deletions(-) diff --git a/config.example.toml b/config.example.toml index a2444df182..71dcf81b42 100644 --- a/config.example.toml +++ b/config.example.toml @@ -20,7 +20,7 @@ # `api_key` / `base_url` are # still read as DeepSeek defaults when `[providers.deepseek]` is absent # (backward compatibility). -provider = "deepseek" # deepseek | deepseek-cn | deepseek-anthropic | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | xiaomi-mimo | novita | fireworks | siliconflow | siliconflow-CN | arcee | moonshot | zai | stepfun | minimax | sglang | vllm | ollama | huggingface | together | qianfan | openai-codex | anthropic | openmodel | deepinfra | sakana +provider = "deepseek" # deepseek | deepseek-cn | deepseek-anthropic | nvidia-nim | openai | atlascloud | wanjie-ark | volcengine | openrouter | xiaomi-mimo | novita | fireworks | siliconflow | siliconflow-CN | arcee | moonshot | zai | stepfun | minimax | sglang | vllm | ollama | huggingface | together | qianfan | openai-codex | anthropic | openmodel | deepinfra | sakana | longcat api_key = "YOUR_DEEPSEEK_API_KEY" # must be non-empty base_url = "https://api.deepseek.com/beta" # provider = "deepseek-cn" # legacy alias (official host is still https://api.deepseek.com) @@ -553,6 +553,15 @@ max_subagents = 10 # optional (1-20) # base_url = "https://api.sakana.ai/v1" # model = "fugu" # or fugu-ultra-20260615 +# Meituan LongCat Provider (https://longcat.chat/platform) +# OpenAI-compatible curated gateway for Meituan's LongCat models. +# Provider aliases: longcat, long-cat, meituan-longcat, meituan +# Env var aliases: LONGCAT_API_KEY +[providers.longcat] +# api_key = "YOUR_LONGCAT_API_KEY" +# base_url = "https://api.longcat.chat/openai/v1" +# model = "LongCat-2.0" + # ───────────────────────────────────────────────────────────────────────────────── # Together AI Provider (https://www.together.ai/) # Env var aliases: TOGETHER_API_KEY, TOGETHER_BASE_URL, TOGETHER_MODEL diff --git a/crates/agent/src/lib.rs b/crates/agent/src/lib.rs index 165306e4f3..73f1a60192 100644 --- a/crates/agent/src/lib.rs +++ b/crates/agent/src/lib.rs @@ -864,6 +864,14 @@ impl Default for ModelRegistry { supports_tools: true, supports_reasoning: true, }, + // Meituan LongCat (https://longcat.chat/platform) + ModelInfo { + id: "LongCat-2.0".to_string(), + provider: ProviderKind::LongCat, + aliases: vec!["longcat".to_string(), "longcat-2.0".to_string()], + supports_tools: true, + supports_reasoning: true, + }, ]; Self::new(models) } diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index bba16f5492..b31a058ae6 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -64,6 +64,8 @@ enum ProviderArg { Deepinfra, #[value(alias = "fugu", alias = "sakana-ai", alias = "sakana_ai")] Sakana, + #[value(alias = "long-cat", alias = "meituan-longcat", alias = "meituan")] + LongCat, } impl From for ProviderKind { @@ -96,6 +98,7 @@ impl From for ProviderKind { ProviderArg::Minimax => ProviderKind::Minimax, ProviderArg::Deepinfra => ProviderKind::Deepinfra, ProviderArg::Sakana => ProviderKind::Sakana, + ProviderArg::LongCat => ProviderKind::LongCat, } } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index d52d9bee8c..98d2b221d0 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -169,6 +169,13 @@ pub struct ProvidersToml { pub deepinfra: ProviderConfigToml, #[serde(default, alias = "sakana-ai", alias = "sakana_ai", alias = "fugu")] pub sakana: ProviderConfigToml, + #[serde( + default, + alias = "long-cat", + alias = "meituan-longcat", + alias = "meituan" + )] + pub longcat: ProviderConfigToml, /// Catch-all table for the dynamic OpenAI-compatible custom provider /// identity (#1519). Arbitrary `[providers.]` tables are handled by /// the tui-side flatten map; this named slot keeps the canonical @@ -267,6 +274,7 @@ impl ProvidersToml { ProviderKind::Minimax => &self.minimax, ProviderKind::Deepinfra => &self.deepinfra, ProviderKind::Sakana => &self.sakana, + ProviderKind::LongCat => &self.longcat, ProviderKind::Custom => &self.custom, } } @@ -302,6 +310,7 @@ impl ProvidersToml { ProviderKind::Minimax => &mut self.minimax, ProviderKind::Deepinfra => &mut self.deepinfra, ProviderKind::Sakana => &mut self.sakana, + ProviderKind::LongCat => &mut self.longcat, ProviderKind::Custom => &mut self.custom, } } @@ -2006,6 +2015,7 @@ impl ConfigToml { ProviderKind::Minimax => DEFAULT_MINIMAX_BASE_URL.to_string(), ProviderKind::Deepinfra => DEFAULT_DEEPINFRA_BASE_URL.to_string(), ProviderKind::Sakana => DEFAULT_SAKANA_BASE_URL.to_string(), + ProviderKind::LongCat => DEFAULT_LONGCAT_BASE_URL.to_string(), // The custom provider has no built-in endpoint; fall back to its // descriptor placeholder so the lookup is total. Real custom // routes always supply a configured base_url before this point. @@ -2582,6 +2592,7 @@ fn default_model_for_provider(provider: ProviderKind) -> &'static str { ProviderKind::Minimax => DEFAULT_MINIMAX_MODEL, ProviderKind::Deepinfra => DEFAULT_DEEPINFRA_MODEL, ProviderKind::Sakana => DEFAULT_SAKANA_MODEL, + ProviderKind::LongCat => DEFAULT_LONGCAT_MODEL, // No built-in default model; the registry placeholder keeps this total. ProviderKind::Custom => provider.provider().default_model(), } @@ -2618,6 +2629,7 @@ fn default_base_url_for_provider(provider: ProviderKind) -> &'static str { ProviderKind::Minimax => DEFAULT_MINIMAX_BASE_URL, ProviderKind::Deepinfra => DEFAULT_DEEPINFRA_BASE_URL, ProviderKind::Sakana => DEFAULT_SAKANA_BASE_URL, + ProviderKind::LongCat => DEFAULT_LONGCAT_BASE_URL, // No built-in default base URL; the registry placeholder keeps this total. ProviderKind::Custom => provider.provider().default_base_url(), } @@ -4180,6 +4192,8 @@ struct EnvRuntimeOverrides { deepinfra_model: Option, sakana_base_url: Option, sakana_model: Option, + longcat_base_url: Option, + longcat_model: Option, } impl EnvRuntimeOverrides { @@ -4418,6 +4432,12 @@ impl EnvRuntimeOverrides { sakana_model: std::env::var("SAKANA_MODEL") .ok() .filter(|v| !v.trim().is_empty()), + longcat_base_url: std::env::var("LONGCAT_BASE_URL") + .ok() + .filter(|v| !v.trim().is_empty()), + longcat_model: std::env::var("LONGCAT_MODEL") + .ok() + .filter(|v| !v.trim().is_empty()), } } @@ -4469,6 +4489,7 @@ impl EnvRuntimeOverrides { ProviderKind::Minimax => self.minimax_base_url.clone(), ProviderKind::Deepinfra => self.deepinfra_base_url.clone(), ProviderKind::Sakana => self.sakana_base_url.clone(), + ProviderKind::LongCat => self.longcat_base_url.clone(), // No dedicated CODEWHALE_CUSTOM_BASE_URL env override: a custom // provider's base URL comes from its `[providers.]` table. ProviderKind::Custom => None, @@ -4499,6 +4520,7 @@ impl EnvRuntimeOverrides { ProviderKind::Minimax => self.minimax_model.clone(), ProviderKind::Deepinfra => self.deepinfra_model.clone(), ProviderKind::Sakana => self.sakana_model.clone(), + ProviderKind::LongCat => self.longcat_model.clone(), _ => None, }?; diff --git a/crates/config/src/provider.rs b/crates/config/src/provider.rs index c5964a9d17..c461d4cfb6 100644 --- a/crates/config/src/provider.rs +++ b/crates/config/src/provider.rs @@ -10,20 +10,20 @@ use super::{ DEFAULT_DEEPSEEK_ANTHROPIC_BASE_URL, DEFAULT_DEEPSEEK_ANTHROPIC_MODEL, DEFAULT_DEEPSEEK_BASE_URL, DEFAULT_DEEPSEEK_MODEL, DEFAULT_FIREWORKS_BASE_URL, DEFAULT_FIREWORKS_MODEL, DEFAULT_HUGGINGFACE_BASE_URL, DEFAULT_HUGGINGFACE_MODEL, - DEFAULT_MINIMAX_BASE_URL, DEFAULT_MINIMAX_MODEL, DEFAULT_MOONSHOT_BASE_URL, - DEFAULT_MOONSHOT_MODEL, DEFAULT_NOVITA_BASE_URL, DEFAULT_NOVITA_MODEL, - DEFAULT_NVIDIA_NIM_BASE_URL, DEFAULT_NVIDIA_NIM_MODEL, DEFAULT_OLLAMA_BASE_URL, - DEFAULT_OLLAMA_MODEL, DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_CODEX_BASE_URL, - DEFAULT_OPENAI_CODEX_MODEL, DEFAULT_OPENAI_MODEL, DEFAULT_OPENMODEL_BASE_URL, - DEFAULT_OPENMODEL_MODEL, DEFAULT_OPENROUTER_BASE_URL, DEFAULT_OPENROUTER_MODEL, - DEFAULT_QIANFAN_BASE_URL, DEFAULT_QIANFAN_MODEL, DEFAULT_SAKANA_BASE_URL, DEFAULT_SAKANA_MODEL, - DEFAULT_SGLANG_BASE_URL, DEFAULT_SGLANG_MODEL, DEFAULT_SILICONFLOW_BASE_URL, - DEFAULT_SILICONFLOW_CN_BASE_URL, DEFAULT_SILICONFLOW_MODEL, DEFAULT_STEPFUN_BASE_URL, - DEFAULT_STEPFUN_MODEL, DEFAULT_TOGETHER_BASE_URL, DEFAULT_TOGETHER_MODEL, - DEFAULT_VLLM_BASE_URL, DEFAULT_VLLM_MODEL, DEFAULT_VOLCENGINE_BASE_URL, - DEFAULT_VOLCENGINE_MODEL, DEFAULT_WANJIE_ARK_BASE_URL, DEFAULT_WANJIE_ARK_MODEL, - DEFAULT_XIAOMI_MIMO_BASE_URL, DEFAULT_XIAOMI_MIMO_MODEL, DEFAULT_ZAI_BASE_URL, - DEFAULT_ZAI_MODEL, ProviderKind, + DEFAULT_LONGCAT_BASE_URL, DEFAULT_LONGCAT_MODEL, DEFAULT_MINIMAX_BASE_URL, + DEFAULT_MINIMAX_MODEL, DEFAULT_MOONSHOT_BASE_URL, DEFAULT_MOONSHOT_MODEL, + DEFAULT_NOVITA_BASE_URL, DEFAULT_NOVITA_MODEL, DEFAULT_NVIDIA_NIM_BASE_URL, + DEFAULT_NVIDIA_NIM_MODEL, DEFAULT_OLLAMA_BASE_URL, DEFAULT_OLLAMA_MODEL, + DEFAULT_OPENAI_BASE_URL, DEFAULT_OPENAI_CODEX_BASE_URL, DEFAULT_OPENAI_CODEX_MODEL, + DEFAULT_OPENAI_MODEL, DEFAULT_OPENMODEL_BASE_URL, DEFAULT_OPENMODEL_MODEL, + DEFAULT_OPENROUTER_BASE_URL, DEFAULT_OPENROUTER_MODEL, DEFAULT_QIANFAN_BASE_URL, + DEFAULT_QIANFAN_MODEL, DEFAULT_SAKANA_BASE_URL, DEFAULT_SAKANA_MODEL, DEFAULT_SGLANG_BASE_URL, + DEFAULT_SGLANG_MODEL, DEFAULT_SILICONFLOW_BASE_URL, DEFAULT_SILICONFLOW_CN_BASE_URL, + DEFAULT_SILICONFLOW_MODEL, DEFAULT_STEPFUN_BASE_URL, DEFAULT_STEPFUN_MODEL, + DEFAULT_TOGETHER_BASE_URL, DEFAULT_TOGETHER_MODEL, DEFAULT_VLLM_BASE_URL, DEFAULT_VLLM_MODEL, + DEFAULT_VOLCENGINE_BASE_URL, DEFAULT_VOLCENGINE_MODEL, DEFAULT_WANJIE_ARK_BASE_URL, + DEFAULT_WANJIE_ARK_MODEL, DEFAULT_XIAOMI_MIMO_BASE_URL, DEFAULT_XIAOMI_MIMO_MODEL, + DEFAULT_ZAI_BASE_URL, DEFAULT_ZAI_MODEL, ProviderKind, }; /// Wire protocol spoken by a provider. @@ -594,6 +594,18 @@ provider!( aliases: ["sakana-ai", "sakana_ai", "fugu"] ); +provider!( + LongCat, + LongCat, + "longcat", + "LongCat", + DEFAULT_LONGCAT_BASE_URL, + DEFAULT_LONGCAT_MODEL, + ["LONGCAT_API_KEY"], + "longcat", + aliases: ["long-cat", "meituan-longcat", "meituan"] +); + /// User-defined OpenAI-compatible endpoint (#1519). /// /// A single dynamic provider identity for arbitrary `[providers.] @@ -676,9 +688,10 @@ static STEPFUN: Stepfun = Stepfun; static MINIMAX: Minimax = Minimax; static DEEPINFRA: Deepinfra = Deepinfra; static SAKANA: Sakana = Sakana; +static LONGCAT: LongCat = LongCat; static CUSTOM: Custom = Custom; -static PROVIDER_REGISTRY: [&dyn Provider; 30] = [ +static PROVIDER_REGISTRY: [&dyn Provider; 31] = [ &DEEPSEEK, &DEEPSEEK_ANTHROPIC, &NVIDIA_NIM, @@ -708,6 +721,7 @@ static PROVIDER_REGISTRY: [&dyn Provider; 30] = [ &MINIMAX, &DEEPINFRA, &SAKANA, + &LONGCAT, &CUSTOM, ]; diff --git a/crates/config/src/provider_defaults.rs b/crates/config/src/provider_defaults.rs index 795b023e6c..869e6b571d 100644 --- a/crates/config/src/provider_defaults.rs +++ b/crates/config/src/provider_defaults.rs @@ -130,3 +130,6 @@ pub(crate) const DEFAULT_DEEPINFRA_BASE_URL: &str = "https://api.deepinfra.com/v // Sakana AI Fugu defaults pub(crate) const DEFAULT_SAKANA_MODEL: &str = "fugu"; pub(crate) const DEFAULT_SAKANA_BASE_URL: &str = "https://api.sakana.ai/v1"; +// Meituan LongCat defaults +pub(crate) const DEFAULT_LONGCAT_MODEL: &str = "LongCat-2.0"; +pub(crate) const DEFAULT_LONGCAT_BASE_URL: &str = "https://api.longcat.chat/openai/v1"; diff --git a/crates/config/src/provider_kind.rs b/crates/config/src/provider_kind.rs index 8c84d66116..1dabb1011a 100644 --- a/crates/config/src/provider_kind.rs +++ b/crates/config/src/provider_kind.rs @@ -100,6 +100,8 @@ pub enum ProviderKind { Deepinfra, #[serde(alias = "sakana-ai", alias = "sakana_ai", alias = "fugu")] Sakana, + #[serde(alias = "long-cat", alias = "meituan-longcat", alias = "meituan")] + LongCat, /// User-defined OpenAI-compatible endpoint (#1519). /// /// A single dynamic identity for arbitrary `[providers.] @@ -111,7 +113,7 @@ pub enum ProviderKind { } impl ProviderKind { - pub const ALL: [Self; 30] = [ + pub const ALL: [Self; 31] = [ Self::Deepseek, Self::DeepseekAnthropic, Self::NvidiaNim, @@ -141,6 +143,7 @@ impl ProviderKind { Self::Minimax, Self::Deepinfra, Self::Sakana, + Self::LongCat, Self::Custom, ]; diff --git a/crates/secrets/src/lib.rs b/crates/secrets/src/lib.rs index a69e742b47..38ac2692a0 100644 --- a/crates/secrets/src/lib.rs +++ b/crates/secrets/src/lib.rs @@ -865,6 +865,7 @@ pub fn env_for(name: &str) -> Option { "WANJIE_MAAS_API_KEY", ], "sakana" | "sakana-ai" | "sakana_ai" | "fugu" => &["FUGU_API_KEY", "SAKANA_API_KEY"], + "longcat" | "long-cat" | "meituan-longcat" | "meituan" => &["LONGCAT_API_KEY"], _ => return None, }; for var in candidates { @@ -915,6 +916,7 @@ mod tests { "MIMO_API_KEY", "FUGU_API_KEY", "SAKANA_API_KEY", + "LONGCAT_API_KEY", SECRET_BACKEND_ENV, LEGACY_SECRET_BACKEND_ENV, ] { diff --git a/crates/tui/src/client.rs b/crates/tui/src/client.rs index 1d9fa0cd61..0b35f7c270 100644 --- a/crates/tui/src/client.rs +++ b/crates/tui/src/client.rs @@ -1721,6 +1721,7 @@ pub(super) fn apply_reasoning_effort( } ApiProvider::Stepfun => {} ApiProvider::Sakana => {} + ApiProvider::LongCat => {} }, "low" | "minimal" | "medium" | "mid" | "high" | "" => match provider { // DeepSeek compatibility: low/medium both map to high @@ -1810,6 +1811,7 @@ pub(super) fn apply_reasoning_effort( } ApiProvider::Stepfun => {} ApiProvider::Sakana => {} + ApiProvider::LongCat => {} }, "xhigh" | "max" | "highest" | "ultracode" => match provider { ApiProvider::Deepseek @@ -1879,6 +1881,7 @@ pub(super) fn apply_reasoning_effort( } ApiProvider::Stepfun => {} ApiProvider::Sakana => {} + ApiProvider::LongCat => {} }, _ => {} } diff --git a/crates/tui/src/config.rs b/crates/tui/src/config.rs index 5465664e79..56341e96b1 100644 --- a/crates/tui/src/config.rs +++ b/crates/tui/src/config.rs @@ -71,6 +71,7 @@ pub enum ApiProvider { Minimax, Deepinfra, Sakana, + LongCat, /// User-defined OpenAI-compatible endpoint (#1519). /// /// Selected when `provider = ""` names a `[providers.] @@ -204,6 +205,7 @@ impl ApiProvider { Self::Minimax => "https://platform.minimax.io/docs/guides/quickstart-preparation", Self::Deepinfra => "https://deepinfra.com/dash/api_keys", Self::Sakana => "https://api.sakana.ai/", + Self::LongCat => "https://longcat.chat/platform", Self::OpenaiCodex | Self::Sglang | Self::Vllm | Self::Ollama => return None, // Custom endpoints have no canonical credential page; the user // supplies the key via their own `api_key_env`. @@ -219,7 +221,7 @@ impl ApiProvider { /// `ApiProvider` discriminant → `ProviderKind` lookup. /// Index 1 is `None` for the legacy `DeepseekCN` variant. - const KIND_LOOKUP: [Option; 31] = [ + const KIND_LOOKUP: [Option; 32] = [ Some(codewhale_config::ProviderKind::Deepseek), None, // DeepseekCN Some(codewhale_config::ProviderKind::DeepseekAnthropic), @@ -250,11 +252,12 @@ impl ApiProvider { Some(codewhale_config::ProviderKind::Minimax), Some(codewhale_config::ProviderKind::Deepinfra), Some(codewhale_config::ProviderKind::Sakana), + Some(codewhale_config::ProviderKind::LongCat), Some(codewhale_config::ProviderKind::Custom), ]; /// `ProviderKind` discriminant → `ApiProvider` lookup. - const FROM_KIND_LOOKUP: [Self; 30] = [ + const FROM_KIND_LOOKUP: [Self; 31] = [ Self::Deepseek, Self::DeepseekAnthropic, Self::NvidiaNim, @@ -284,6 +287,7 @@ impl ApiProvider { Self::Minimax, Self::Deepinfra, Self::Sakana, + Self::LongCat, Self::Custom, ]; @@ -373,6 +377,10 @@ fn subagent_provider_key_matches(key: &str, provider: ApiProvider) -> bool { | "big_model" | "zhipu_glm" ), + ApiProvider::LongCat => matches!( + normalized.as_str(), + "longcat" | "long_cat" | "meituan_longcat" | "meituan" + ), _ => false, } } @@ -1157,6 +1165,7 @@ pub fn model_completion_names_for_provider(provider: ApiProvider) -> Vec<&'stati MINIMAX_M2_MODEL, ], ApiProvider::Sakana => vec![DEFAULT_SAKANA_MODEL, SAKANA_FUGU_ULTRA_MODEL], + ApiProvider::LongCat => vec![DEFAULT_LONGCAT_MODEL], // Custom endpoints expose no built-in completion names; the user // supplies their own model id (#1519). ApiProvider::Custom => Vec::new(), @@ -2572,6 +2581,13 @@ pub struct ProvidersConfig { pub minimax: ProviderConfig, #[serde(default, alias = "sakana-ai", alias = "sakana_ai", alias = "fugu")] pub sakana: ProviderConfig, + #[serde( + default, + alias = "long-cat", + alias = "meituan-longcat", + alias = "meituan" + )] + pub longcat: ProviderConfig, /// Arbitrary user-named custom providers (#1519). /// /// Captures every `[providers.]` table whose key is not one of the @@ -2961,6 +2977,7 @@ impl Config { ApiProvider::Stepfun => &providers.stepfun, ApiProvider::Minimax => &providers.minimax, ApiProvider::Sakana => &providers.sakana, + ApiProvider::LongCat => &providers.longcat, // Handled by the name-keyed early return above (#1519). ApiProvider::Custom => unreachable!("custom provider resolved by name above"), }) @@ -3021,6 +3038,7 @@ impl Config { ApiProvider::Stepfun => &mut providers.stepfun, ApiProvider::Minimax => &mut providers.minimax, ApiProvider::Sakana => &mut providers.sakana, + ApiProvider::LongCat => &mut providers.longcat, // Handled by the name-keyed early return above (#1519). ApiProvider::Custom => unreachable!("custom provider resolved by name above"), } @@ -3213,6 +3231,7 @@ impl Config { ApiProvider::Anthropic => DEFAULT_ANTHROPIC_MODEL, ApiProvider::Minimax => DEFAULT_MINIMAX_MODEL, ApiProvider::Sakana => DEFAULT_SAKANA_MODEL, + ApiProvider::LongCat => DEFAULT_LONGCAT_MODEL, // Custom endpoints have no built-in default model; pass through the // descriptor placeholder when nothing is configured (#1519). ApiProvider::Custom => codewhale_config::ProviderKind::Custom @@ -3266,6 +3285,7 @@ impl Config { | ApiProvider::Stepfun | ApiProvider::Minimax | ApiProvider::Sakana + | ApiProvider::LongCat // Custom reads its base_url from the named `[providers.]` // table (via provider_base), never from the legacy root field. | ApiProvider::Custom => None, @@ -3326,6 +3346,7 @@ impl Config { ApiProvider::Anthropic => DEFAULT_ANTHROPIC_BASE_URL, ApiProvider::Minimax => DEFAULT_MINIMAX_BASE_URL, ApiProvider::Sakana => DEFAULT_SAKANA_BASE_URL, + ApiProvider::LongCat => DEFAULT_LONGCAT_BASE_URL, // No built-in endpoint; descriptor placeholder keeps the // fallback total. A real custom route configures // `[providers.] base_url` which wins above (#1519). @@ -4470,6 +4491,13 @@ fn apply_env_overrides(config: &mut Config) { .sakana .base_url = Some(value); } + ApiProvider::LongCat => { + config + .providers + .get_or_insert_with(ProvidersConfig::default) + .longcat + .base_url = Some(value); + } // Custom resolves to the named `[providers.]` table; route the // override through the name-keyed mutable accessor (#1519). ApiProvider::Custom => { @@ -4698,6 +4726,7 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::Stepfun => &mut providers.stepfun, ApiProvider::Minimax => &mut providers.minimax, ApiProvider::Sakana => &mut providers.sakana, + ApiProvider::LongCat => &mut providers.longcat, ApiProvider::Custom => providers .custom .entry(custom_key.expect("custom key captured for custom provider")) @@ -4920,6 +4949,7 @@ fn apply_env_overrides(config: &mut Config) { ApiProvider::Stepfun => &mut providers.stepfun, ApiProvider::Minimax => &mut providers.minimax, ApiProvider::Sakana => &mut providers.sakana, + ApiProvider::LongCat => &mut providers.longcat, }; entry.model = Some(value); } @@ -5691,6 +5721,7 @@ fn merge_providers( stepfun: merge_provider_config(base.stepfun, override_cfg.stepfun), minimax: merge_provider_config(base.minimax, override_cfg.minimax), sakana: merge_provider_config(base.sakana, override_cfg.sakana), + longcat: merge_provider_config(base.longcat, override_cfg.longcat), custom: merge_custom_providers(base.custom, override_cfg.custom), }), } diff --git a/crates/tui/src/config/models.rs b/crates/tui/src/config/models.rs index a8bf8c6fb4..2b04d54bf5 100644 --- a/crates/tui/src/config/models.rs +++ b/crates/tui/src/config/models.rs @@ -163,3 +163,5 @@ pub const DEFAULT_MINIMAX_BASE_URL: &str = "https://api.minimax.io/v1"; pub const DEFAULT_SAKANA_MODEL: &str = "fugu"; pub const SAKANA_FUGU_ULTRA_MODEL: &str = "fugu-ultra-20260615"; pub const DEFAULT_SAKANA_BASE_URL: &str = "https://api.sakana.ai/v1"; +pub const DEFAULT_LONGCAT_MODEL: &str = "LongCat-2.0"; +pub const DEFAULT_LONGCAT_BASE_URL: &str = "https://api.longcat.chat/openai/v1"; diff --git a/crates/tui/src/config_persistence.rs b/crates/tui/src/config_persistence.rs index c8b27bf133..0c9120a26c 100644 --- a/crates/tui/src/config_persistence.rs +++ b/crates/tui/src/config_persistence.rs @@ -385,6 +385,7 @@ fn provider_base_url_table_key(provider: ApiProvider) -> anyhow::Result<&'static ApiProvider::Stepfun => Ok("stepfun"), ApiProvider::Minimax => Ok("minimax"), ApiProvider::Sakana => Ok("sakana"), + ApiProvider::LongCat => Ok("longcat"), // Custom providers live under a user-chosen `[providers.]` table, // not a fixed key. Persisting base_url through this static-key path is // out of scope for the #1519 constrained slice; users edit the named diff --git a/crates/tui/src/tui/ui.rs b/crates/tui/src/tui/ui.rs index 9bbf5d47fe..58bff458e3 100644 --- a/crates/tui/src/tui/ui.rs +++ b/crates/tui/src/tui/ui.rs @@ -9161,6 +9161,7 @@ fn render(f: &mut Frame, app: &mut App, config: &Config) { crate::config::ApiProvider::Stepfun => Some("StepFun"), crate::config::ApiProvider::Minimax => Some("MiniMax"), crate::config::ApiProvider::Sakana => Some("Sakana"), + crate::config::ApiProvider::LongCat => Some("LongCat"), crate::config::ApiProvider::Custom => Some("Custom"), }; let status_indicator_started_at = if app.low_motion { @@ -10824,6 +10825,7 @@ async fn apply_provider_picker_api_key( ApiProvider::Stepfun => &mut providers.stepfun, ApiProvider::Minimax => &mut providers.minimax, ApiProvider::Sakana => &mut providers.sakana, + ApiProvider::LongCat => &mut providers.longcat, }; entry.api_key = Some(api_key); } @@ -10904,6 +10906,7 @@ fn set_provider_auth_mode_in_memory(config: &mut Config, provider: ApiProvider, ApiProvider::Stepfun => &mut providers.stepfun, ApiProvider::Minimax => &mut providers.minimax, ApiProvider::Sakana => &mut providers.sakana, + ApiProvider::LongCat => &mut providers.longcat, }; entry.auth_mode = Some(auth_mode); } diff --git a/docs/PROVIDERS.md b/docs/PROVIDERS.md index 4011eea457..4c4eedebc2 100644 --- a/docs/PROVIDERS.md +++ b/docs/PROVIDERS.md @@ -32,7 +32,7 @@ The canonical provider IDs are: `wanjie-ark`, `volcengine`, `openrouter`, `xiaomi-mimo`, `novita`, `fireworks`, `siliconflow`, `arcee`, `siliconflow-CN`, `moonshot`, `sglang`, `vllm`, `ollama`, `huggingface`, `together`, `qianfan`, `openai-codex`, `anthropic`, -`openmodel`, `zai`, `stepfun`, `minimax`, `deepinfra`, and `sakana`. +`openmodel`, `zai`, `stepfun`, `minimax`, `deepinfra`, `sakana`, and `longcat`. Use any of these surfaces to select a provider: @@ -106,6 +106,7 @@ the listed provider env vars. | `minimax` | `[providers.minimax]` | OpenAI Chat Completions | `MINIMAX_API_KEY` | | `deepinfra` | `[providers.deepinfra]` | OpenAI Chat Completions | `DEEPINFRA_API_KEY`, `DEEPINFRA_TOKEN` | | `sakana` | `[providers.sakana]` | OpenAI Chat Completions | `FUGU_API_KEY`, `SAKANA_API_KEY` | +| `longcat` | `[providers.longcat]` | OpenAI Chat Completions | `LONGCAT_API_KEY` | Default base URLs and models for each route are listed in the shipped provider table below. The wire protocol values above are derived from @@ -235,6 +236,7 @@ the same links where possible. | `openai-codex` | Reuses `codex login`; no CodeWhale API key is stored. | | `sglang`, `vllm`, `ollama` | Local OpenAI-compatible endpoints can run without an API key on localhost. | | `sakana` | [Sakana AI API](https://api.sakana.ai/) | +| `longcat` | [Meituan LongCat platform](https://longcat.chat/platform) | ## Shipped Providers @@ -269,6 +271,7 @@ the same links where possible. | `anthropic` | `[providers.anthropic]` | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL`; default `https://api.anthropic.com` | `claude-opus-4-8`, `claude-sonnet-4-6` (default), `claude-haiku-4-5` | Native Anthropic Messages API route (`/v1/messages`, `x-api-key` + `anthropic-version: 2023-06-01`) — not OpenAI-compatible. Prompt caching via `cache_control` breakpoints, adaptive thinking + `output_config.effort`, signed thinking blocks replayed verbatim, cache telemetry normalized per #2961. `ANTHROPIC_MODEL` is accepted. | | `openmodel` | `[providers.openmodel]` | `OPENMODEL_API_KEY` | `OPENMODEL_BASE_URL`; default `https://api.openmodel.ai` | `deepseek-v4-flash`; provider-scoped custom model IDs pass through | OpenModel Anthropic-compatible Messages route. Uses `/v1/messages`, Bearer auth, and `anthropic-version: 2023-06-01`; OpenModel selects DeepSeek, DashScope, Xiaomi, Claude, and other routes by model id. `OPENMODEL_MODEL` is accepted. | | `sakana` | `[providers.sakana]` | `FUGU_API_KEY`, `SAKANA_API_KEY` | `SAKANA_BASE_URL`; default `https://api.sakana.ai/v1` | `fugu` (default), `fugu-ultra-20260615` | Sakana AI Fugu OpenAI-compatible route. Standard Chat Completions wire protocol; streaming supported. `fugu-ultra-20260615` is the heavy/reasoning variant. Env var aliases: `FUGU_API_KEY` (primary), `SAKANA_API_KEY`; provider aliases: `sakana-ai`, `sakana_ai`, `fugu`. | +| `longcat` | `[providers.longcat]` | `LONGCAT_API_KEY` | `LONGCAT_BASE_URL`; default `https://api.longcat.chat/openai/v1` | `LongCat-2.0` (default) | Meituan LongCat curated model gateway. OpenAI-compatible Chat Completions wire protocol. Sign up at https://longcat.chat/platform for an API key. Provider aliases: `long-cat`, `meituan-longcat`, `meituan`. | ### Hugging Face Provider vs MCP vs Hub @@ -394,6 +397,7 @@ endpoint when the endpoint supports model listing. | `anthropic` | `claude-opus-4-8`, `claude-sonnet-4-6`, `claude-haiku-4-5` | yes | yes for `claude-opus-4-8` and `claude-sonnet-4-6`; no for `claude-haiku-4-5` | | `openmodel` | `deepseek-v4-flash`; provider-scoped custom model IDs pass through | yes | model-dependent | | `sakana` | `fugu`, `fugu-ultra-20260615` | yes | yes for `fugu-ultra-20260615` | +| `longcat` | `LongCat-2.0` | yes | yes | AtlasCloud keeps the same default model as the config layer and adds provider-scoped aliases for the Pro and Flash rows. Other AtlasCloud model IDs From f02c3abd12db4d11a86fffb835392c3dcc5025a0 Mon Sep 17 00:00:00 2001 From: Hmbown <101357273+Hmbown@users.noreply.github.com> Date: Sun, 5 Jul 2026 04:57:48 +0000 Subject: [PATCH 6/7] chore(release): bump workspace to 0.8.67 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Advance the workspace version 0.8.66 -> 0.8.67 (root Cargo.toml, internal path-dep pins, npm wrapper, Cargo.lock) and open the 0.8.67 CHANGELOG section, rolling the accumulated Unreleased notes into it plus the LongCat provider and the onboarding/plugins/skills follow-up fixes. README and INSTALL version/tag pointers advanced to match. `check-versions.sh` passes (workspace=0.8.67, npm=0.8.67, lockfile in sync). No tag, publish, GitHub Release, or main merge — those remain gated on explicit maintainer approval. Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> --- CHANGELOG.md | 30 ++++++++++++- Cargo.lock | 30 ++++++------- Cargo.toml | 2 +- README.ja-JP.md | 6 +-- README.ko-KR.md | 6 +-- README.md | 6 +-- README.vi.md | 6 +-- README.zh-CN.md | 6 +-- crates/agent/Cargo.toml | 2 +- crates/app-server/Cargo.toml | 18 ++++---- crates/cli/Cargo.toml | 16 +++---- crates/config/Cargo.toml | 4 +- crates/core/Cargo.toml | 16 +++---- crates/execpolicy/Cargo.toml | 2 +- crates/hooks/Cargo.toml | 2 +- crates/tools/Cargo.toml | 2 +- crates/tui/CHANGELOG.md | 84 ++++++++++++------------------------ crates/tui/Cargo.toml | 12 +++--- docs/INSTALL.md | 6 +-- npm/codewhale/package.json | 2 +- web/lib/facts.generated.ts | 4 +- 21 files changed, 130 insertions(+), 132 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c922112b5a..5852ed78b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.8.67] - 2026-07-05 + ### Added +- Added Meituan LongCat as a first-class OpenAI-compatible provider (`longcat` + with `long-cat`/`meituan-longcat`/`meituan` aliases), `LONGCAT_API_KEY` + discovery, the `LongCat-2.0` default model and the + `https://api.longcat.chat/openai/v1` endpoint, with provider-picker wiring, + model completions, and provider docs. +- Surfaced every shipped UI locale in the first-run language picker: `es-419` + and `vi` are now offered (options 7–8) and the footer range is corrected to + "1-8", with guard tests that keep the picker in sync with `Locale::shipped()` + (#3929). - Added a website localization matrix with a locale registry and drift checks. Harvested from #3763 by @idling11. @@ -19,6 +30,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Persisted `/plugin enable|disable` overrides across restarts so a disabled + (or enabled) plugin no longer resets on the next launch (#3918). +- Unified the terminal display-width contract on a single helper, fixing the + pending-input preview's word-wrap on tab/control characters (#3924). +- Made skill-name lookup reachable and warned on shadowing: discovered skill + names are normalized/slugified for dispatch, and duplicate normalized names — + across roots and within a single root — now warn instead of silently + shadowing (#3919). +- Removed the unenforced skills trust / `allowed-tools` copy that implied + sandboxing that is not applied (#3920). +- Repaired the onboarding Trust step so `Enter` no longer silently grants + workspace trust (it guides to the explicit keys), and fixed the API-key + step's `Esc` navigation (#3926, #3927). +- Surfaced a visible notice when a gated custom Constitution override is + present but the opt-in flag is unset, instead of only a `tracing::warn` + (#3928). - Raised the streamed model-response idle timeout and matched the TUI stall watchdog to the configured stream budget so long reasoning pauses are not recovered as stalled turns (#2487). @@ -2626,7 +2653,8 @@ overflow report and `/theme` picker edge-wrapping patch in #1814. Older releases (v0.8.39 and earlier) are archived in [docs/CHANGELOG_ARCHIVE.md](docs/CHANGELOG_ARCHIVE.md). -[Unreleased]: https://github.com/Hmbown/CodeWhale/compare/v0.8.66...HEAD +[Unreleased]: https://github.com/Hmbown/CodeWhale/compare/v0.8.67...HEAD +[0.8.67]: https://github.com/Hmbown/CodeWhale/compare/v0.8.66...v0.8.67 [0.8.66]: https://github.com/Hmbown/CodeWhale/compare/v0.8.65...v0.8.66 [0.8.65]: https://github.com/Hmbown/CodeWhale/compare/v0.8.64...v0.8.65 [0.8.64]: https://github.com/Hmbown/CodeWhale/compare/v0.8.63...v0.8.64 diff --git a/Cargo.lock b/Cargo.lock index 6ffc4a475a..084b5e759a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -792,7 +792,7 @@ checksum = "e9b18233253483ce2f65329a24072ec414db782531bdbb7d0bbc4bd2ce6b7e21" [[package]] name = "codewhale-agent" -version = "0.8.66" +version = "0.8.67" dependencies = [ "codewhale-config", "serde", @@ -800,7 +800,7 @@ dependencies = [ [[package]] name = "codewhale-app-server" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "axum", @@ -828,7 +828,7 @@ dependencies = [ [[package]] name = "codewhale-cli" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "chrono", @@ -858,7 +858,7 @@ dependencies = [ [[package]] name = "codewhale-config" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "codewhale-execpolicy", @@ -875,7 +875,7 @@ dependencies = [ [[package]] name = "codewhale-core" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "chrono", @@ -894,7 +894,7 @@ dependencies = [ [[package]] name = "codewhale-execpolicy" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "codewhale-protocol", @@ -903,7 +903,7 @@ dependencies = [ [[package]] name = "codewhale-hooks" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "async-trait", @@ -917,7 +917,7 @@ dependencies = [ [[package]] name = "codewhale-mcp" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "serde", @@ -926,7 +926,7 @@ dependencies = [ [[package]] name = "codewhale-protocol" -version = "0.8.66" +version = "0.8.67" dependencies = [ "chrono", "serde", @@ -936,7 +936,7 @@ dependencies = [ [[package]] name = "codewhale-release" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "reqwest 0.13.4", @@ -947,7 +947,7 @@ dependencies = [ [[package]] name = "codewhale-secrets" -version = "0.8.66" +version = "0.8.67" dependencies = [ "dirs", "keyring", @@ -960,7 +960,7 @@ dependencies = [ [[package]] name = "codewhale-state" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "chrono", @@ -972,7 +972,7 @@ dependencies = [ [[package]] name = "codewhale-tools" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "async-trait", @@ -986,7 +986,7 @@ dependencies = [ [[package]] name = "codewhale-tui" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "arboard", @@ -1063,7 +1063,7 @@ dependencies = [ [[package]] name = "codewhale-whaleflow" -version = "0.8.66" +version = "0.8.67" dependencies = [ "anyhow", "serde", diff --git a/Cargo.toml b/Cargo.toml index a58012c260..a306ef5588 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ default-members = ["crates/cli", "crates/app-server", "crates/tui"] resolver = "2" [workspace.package] -version = "0.8.66" +version = "0.8.67" edition = "2024" # Rust 1.88 stabilized `let_chains` in `if`/`while` conditions, which the # codebase relies on extensively. Cargo enforces this so users on older diff --git a/README.ja-JP.md b/README.ja-JP.md index 66bbb57108..19ebd5f13c 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -17,7 +17,7 @@ Rust 製の TUI と CLI、25 のプロバイダ。DeepSeek、OpenRouter、Huggin ```bash npm install -g codewhale -codewhale --version # 0.8.66 +codewhale --version # 0.8.67 ``` npm wrapper(Node 18+)は GitHub Releases から SHA-256 検証済みのバイナリをダウンロードし、`codewhale`、`codew`、`codewhale-tui` をインストールします。ソースからビルドしたい場合は cargo(Rust 1.88+)で: @@ -44,8 +44,8 @@ nix run github:Hmbown/CodeWhale scoop install codewhale # または GitHub Releases の NSIS インストーラ # GitHub に安定して到達できない場合の CNB ミラー -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-cli --locked --force -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-tui --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-cli --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-tui --locked --force # 旧 Homebrew 互換。formula の改名が完了するまで deepseek-tui 名のままです brew tap Hmbown/deepseek-tui diff --git a/README.ko-KR.md b/README.ko-KR.md index 5c524e5819..ab00a04cd6 100644 --- a/README.ko-KR.md +++ b/README.ko-KR.md @@ -35,7 +35,7 @@ CodeWhale이 실제 라우트를 해석해 실행합니다. ```bash npm install -g codewhale -codewhale --version # 0.8.66 +codewhale --version # 0.8.67 ``` npm 래퍼(Node 18+)는 GitHub Releases에서 SHA-256으로 검증된 바이너리를 @@ -64,8 +64,8 @@ nix run github:Hmbown/CodeWhale scoop install codewhale # or the NSIS installer from GitHub Releases # CNB mirror for users who cannot reliably reach GitHub -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-cli --locked --force -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-tui --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-cli --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-tui --locked --force # Legacy Homebrew compatibility while the formula is renamed brew tap Hmbown/deepseek-tui diff --git a/README.md b/README.md index e67b31497f..be6d6376a1 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ open an issue — that's how the project grows. ```bash npm install -g codewhale -codewhale --version # 0.8.66 +codewhale --version # 0.8.67 ``` The npm wrapper (Node 18+) downloads SHA-256-verified binaries from GitHub @@ -62,8 +62,8 @@ nix run github:Hmbown/CodeWhale scoop install codewhale # or the NSIS installer from GitHub Releases # CNB mirror for users who cannot reliably reach GitHub -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-cli --locked --force -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-tui --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-cli --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-tui --locked --force # Legacy Homebrew compatibility while the formula is renamed brew tap Hmbown/deepseek-tui diff --git a/README.vi.md b/README.vi.md index 28c2785e2b..6b1f40b512 100644 --- a/README.vi.md +++ b/README.vi.md @@ -21,7 +21,7 @@ bằng `/restore` cho mọi lượt. ```bash npm install -g codewhale -codewhale --version # 0.8.66 +codewhale --version # 0.8.67 ``` Wrapper npm (Node 18+) tải binary đã xác minh SHA-256 từ GitHub Releases và @@ -50,8 +50,8 @@ nix run github:Hmbown/CodeWhale scoop install codewhale # hoặc trình cài NSIS từ GitHub Releases # CNB mirror cho người dùng khó truy cập GitHub ổn định -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-cli --locked --force -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-tui --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-cli --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-tui --locked --force # Homebrew legacy trong lúc formula đang được đổi tên brew tap Hmbown/deepseek-tui diff --git a/README.zh-CN.md b/README.zh-CN.md index 608845f7ac..2e7f268fd2 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -20,7 +20,7 @@ DeepInfra 以及本地 vLLM/SGLang/Ollama 都是一等路由;当你手里是 A ```bash npm install -g codewhale -codewhale --version # 0.8.66 +codewhale --version # 0.8.67 ``` npm wrapper(Node 18+)会从 GitHub Releases 下载经 SHA-256 校验的二进制,并安装 @@ -49,8 +49,8 @@ nix run github:Hmbown/CodeWhale scoop install codewhale # 或使用 GitHub Releases 中的 NSIS 安装包 # CNB 镜像:适合无法稳定访问 GitHub 的用户 -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-cli --locked --force -cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.66 codewhale-tui --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-cli --locked --force +cargo install --git https://cnb.cool/codewhale.net/codewhale --tag v0.8.67 codewhale-tui --locked --force # 旧 Homebrew 兼容路径:formula 改名期间仍沿用 deepseek-tui brew tap Hmbown/deepseek-tui diff --git a/crates/agent/Cargo.toml b/crates/agent/Cargo.toml index f24e58b7ee..a2a2550f92 100644 --- a/crates/agent/Cargo.toml +++ b/crates/agent/Cargo.toml @@ -7,5 +7,5 @@ repository.workspace = true description = "Model/provider registry and fallback strategy for CodeWhale" [dependencies] -codewhale-config = { path = "../config", version = "0.8.66" } +codewhale-config = { path = "../config", version = "0.8.67" } serde.workspace = true diff --git a/crates/app-server/Cargo.toml b/crates/app-server/Cargo.toml index 38521524d2..113752b6d9 100644 --- a/crates/app-server/Cargo.toml +++ b/crates/app-server/Cargo.toml @@ -12,15 +12,15 @@ autobins = false anyhow.workspace = true axum.workspace = true clap.workspace = true -codewhale-agent = { path = "../agent", version = "0.8.66" } -codewhale-config = { path = "../config", version = "0.8.66" } -codewhale-core = { path = "../core", version = "0.8.66" } -codewhale-execpolicy = { path = "../execpolicy", version = "0.8.66" } -codewhale-hooks = { path = "../hooks", version = "0.8.66" } -codewhale-mcp = { path = "../mcp", version = "0.8.66" } -codewhale-protocol = { path = "../protocol", version = "0.8.66" } -codewhale-state = { path = "../state", version = "0.8.66" } -codewhale-tools = { path = "../tools", version = "0.8.66" } +codewhale-agent = { path = "../agent", version = "0.8.67" } +codewhale-config = { path = "../config", version = "0.8.67" } +codewhale-core = { path = "../core", version = "0.8.67" } +codewhale-execpolicy = { path = "../execpolicy", version = "0.8.67" } +codewhale-hooks = { path = "../hooks", version = "0.8.67" } +codewhale-mcp = { path = "../mcp", version = "0.8.67" } +codewhale-protocol = { path = "../protocol", version = "0.8.67" } +codewhale-state = { path = "../state", version = "0.8.67" } +codewhale-tools = { path = "../tools", version = "0.8.67" } serde.workspace = true serde_json.workspace = true rustls.workspace = true diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 22d12ed932..3f50aa0fad 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -19,14 +19,14 @@ path = "src/bin/codew_legacy_shim.rs" anyhow.workspace = true clap.workspace = true clap_complete.workspace = true -codewhale-agent = { path = "../agent", version = "0.8.66" } -codewhale-app-server = { path = "../app-server", version = "0.8.66" } -codewhale-config = { path = "../config", version = "0.8.66" } -codewhale-execpolicy = { path = "../execpolicy", version = "0.8.66" } -codewhale-mcp = { path = "../mcp", version = "0.8.66" } -codewhale-release = { path = "../release", version = "0.8.66" } -codewhale-secrets = { path = "../secrets", version = "0.8.66" } -codewhale-state = { path = "../state", version = "0.8.66" } +codewhale-agent = { path = "../agent", version = "0.8.67" } +codewhale-app-server = { path = "../app-server", version = "0.8.67" } +codewhale-config = { path = "../config", version = "0.8.67" } +codewhale-execpolicy = { path = "../execpolicy", version = "0.8.67" } +codewhale-mcp = { path = "../mcp", version = "0.8.67" } +codewhale-release = { path = "../release", version = "0.8.67" } +codewhale-secrets = { path = "../secrets", version = "0.8.67" } +codewhale-state = { path = "../state", version = "0.8.67" } chrono.workspace = true dirs.workspace = true serde.workspace = true diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index e011d00f01..c113ed7cff 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -8,8 +8,8 @@ description = "Config schema and precedence model for CodeWhale" [dependencies] anyhow.workspace = true -codewhale-execpolicy = { path = "../execpolicy", version = "0.8.66" } -codewhale-secrets = { path = "../secrets", version = "0.8.66" } +codewhale-execpolicy = { path = "../execpolicy", version = "0.8.67" } +codewhale-secrets = { path = "../secrets", version = "0.8.67" } dirs.workspace = true libc = "0.2" serde.workspace = true diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 51cd31dd6a..0e03317398 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -9,14 +9,14 @@ description = "Core runtime boundaries for CodeWhale" [dependencies] anyhow.workspace = true chrono.workspace = true -codewhale-agent = { path = "../agent", version = "0.8.66" } -codewhale-config = { path = "../config", version = "0.8.66" } -codewhale-execpolicy = { path = "../execpolicy", version = "0.8.66" } -codewhale-hooks = { path = "../hooks", version = "0.8.66" } -codewhale-mcp = { path = "../mcp", version = "0.8.66" } -codewhale-protocol = { path = "../protocol", version = "0.8.66" } -codewhale-state = { path = "../state", version = "0.8.66" } -codewhale-tools = { path = "../tools", version = "0.8.66" } +codewhale-agent = { path = "../agent", version = "0.8.67" } +codewhale-config = { path = "../config", version = "0.8.67" } +codewhale-execpolicy = { path = "../execpolicy", version = "0.8.67" } +codewhale-hooks = { path = "../hooks", version = "0.8.67" } +codewhale-mcp = { path = "../mcp", version = "0.8.67" } +codewhale-protocol = { path = "../protocol", version = "0.8.67" } +codewhale-state = { path = "../state", version = "0.8.67" } +codewhale-tools = { path = "../tools", version = "0.8.67" } serde_json.workspace = true tracing.workspace = true uuid.workspace = true diff --git a/crates/execpolicy/Cargo.toml b/crates/execpolicy/Cargo.toml index 15a80dbf8e..6be88944a9 100644 --- a/crates/execpolicy/Cargo.toml +++ b/crates/execpolicy/Cargo.toml @@ -8,5 +8,5 @@ description = "Execution policy and approval model for CodeWhale" [dependencies] anyhow.workspace = true -codewhale-protocol = { path = "../protocol", version = "0.8.66" } +codewhale-protocol = { path = "../protocol", version = "0.8.67" } serde.workspace = true diff --git a/crates/hooks/Cargo.toml b/crates/hooks/Cargo.toml index b544f7f0ad..5570019dd4 100644 --- a/crates/hooks/Cargo.toml +++ b/crates/hooks/Cargo.toml @@ -10,7 +10,7 @@ description = "Hook dispatch and notifications support for CodeWhale" anyhow.workspace = true async-trait.workspace = true chrono.workspace = true -codewhale-protocol = { path = "../protocol", version = "0.8.66" } +codewhale-protocol = { path = "../protocol", version = "0.8.67" } reqwest.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/tools/Cargo.toml b/crates/tools/Cargo.toml index cd3db6f07c..810c38a1f0 100644 --- a/crates/tools/Cargo.toml +++ b/crates/tools/Cargo.toml @@ -9,7 +9,7 @@ description = "Tool invocation lifecycle, schema validation, and scheduler paral [dependencies] anyhow.workspace = true async-trait.workspace = true -codewhale-protocol = { path = "../protocol", version = "0.8.66" } +codewhale-protocol = { path = "../protocol", version = "0.8.67" } serde.workspace = true serde_json.workspace = true thiserror.workspace = true diff --git a/crates/tui/CHANGELOG.md b/crates/tui/CHANGELOG.md index 9494d45dc7..95db27633e 100644 --- a/crates/tui/CHANGELOG.md +++ b/crates/tui/CHANGELOG.md @@ -7,8 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.8.67] - 2026-07-05 + ### Added +- Added Meituan LongCat as a first-class OpenAI-compatible provider (`longcat` + with `long-cat`/`meituan-longcat`/`meituan` aliases), `LONGCAT_API_KEY` + discovery, the `LongCat-2.0` default model and the + `https://api.longcat.chat/openai/v1` endpoint, with provider-picker wiring, + model completions, and provider docs. +- Surfaced every shipped UI locale in the first-run language picker: `es-419` + and `vi` are now offered (options 7–8) and the footer range is corrected to + "1-8", with guard tests that keep the picker in sync with `Locale::shipped()` + (#3929). - Added a website localization matrix with a locale registry and drift checks. Harvested from #3763 by @idling11. @@ -19,6 +30,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Persisted `/plugin enable|disable` overrides across restarts so a disabled + (or enabled) plugin no longer resets on the next launch (#3918). +- Unified the terminal display-width contract on a single helper, fixing the + pending-input preview's word-wrap on tab/control characters (#3924). +- Made skill-name lookup reachable and warned on shadowing: discovered skill + names are normalized/slugified for dispatch, and duplicate normalized names — + across roots and within a single root — now warn instead of silently + shadowing (#3919). +- Removed the unenforced skills trust / `allowed-tools` copy that implied + sandboxing that is not applied (#3920). +- Repaired the onboarding Trust step so `Enter` no longer silently grants + workspace trust (it guides to the explicit keys), and fixed the API-key + step's `Esc` navigation (#3926, #3927). +- Surfaced a visible notice when a gated custom Constitution override is + present but the opt-in flag is unset, instead of only a `tracing::warn` + (#3928). - Raised the streamed model-response idle timeout and matched the TUI stall watchdog to the configured stream budget so long reasoning pauses are not recovered as stalled turns (#2487). @@ -1602,63 +1629,6 @@ provider picker key replacement, and MiMo auth cleanup work (#2714, #2715, #2717, #2718), and **@RefuseOdd** for configurable `path_suffix` support on OpenAI-compatible endpoints (#2558). -## [0.8.52] - 2026-06-03 - -### Added - -- **SiliconFlow China region provider.** Added the `siliconflow-CN` provider - variant for the China regional endpoint, sharing the existing - `[providers.siliconflow]` credentials and `SILICONFLOW_API_KEY` slot - instead of creating a second credential namespace; the provider picker and - registry docs now expose the regional route explicitly (#2588, #2615). -- **Multimodal `/attach` image forwarding.** Attached images are now sent as - OpenAI-compatible `image_url` content blocks so multimodal providers can - actually see image attachments (#2584, #2587, #2607). -- **Sub-agent lifecycle hooks and runtime metadata.** Sub-agent spawn/complete - hook events, mode-change runtime messages, mode metadata on turns, localized - context-inspector strings, and drag-to-resize sidebar width are included in - this release slice. - -### Fixed - -- **Sub-agents now auto-cancel after stale heartbeats.** Running sub-agents - track manager-visible progress and are auto-cancelled after the configurable - `[subagents] heartbeat_timeout_secs` window (default 300s), releasing their - concurrency slot and unblocking parent turns that would otherwise wait - forever (#2603, #2614, #2620). -- **Work panel state survives transient lock misses.** The sidebar caches the - last successful Work summary so checklist and strategy progress no longer - disappear into "Work state updating..." while the engine briefly owns the - shared todo/plan locks (#2606, #2616). -- **SiliconFlow-CN no longer breaks main.** Filled the missing CLI provider - exhaustiveness arms and removed the duplicate/unreachable TUI config arms - left by the #2615 landing; direct auth now stores the China-region variant in - the shared SiliconFlow provider table (#2616, #2618, #2619). -- **v0.8.51 image-attach closure corrected.** The `/attach` multimodal fix - landed after the v0.8.51 tag, so this release is the first version that - actually contains it for users installing from the published release line - (#2584, #2607). -- **Legacy SSE MCP reconnects are retryable again.** Closed or reset - `POST /messages` requests on stale legacy SSE sessions now trigger the same - reconnect-and-retry path as closed SSE streams, removing a release-gate flake - and matching the intended recovery behavior (#2597). -- **Cache-hit cost accounting uses one telemetry source.** Mixed DeepSeek - `prompt_cache_hit_tokens` and OpenAI-style `cached_tokens` usage payloads no - longer infer cache misses from the wrong hit count, avoiding inflated TUI cost - estimates on cached DeepSeek turns (#2567, #2609). -- **Cygwin/MSYS2 config paths honor exported `$HOME`.** CodeWhale and legacy - DeepSeek config roots now prefer a non-empty `$HOME` before falling back to the - platform home resolver, while `CODEWHALE_HOME` remains the strongest explicit - override (#2369, #2610). - -### Community - -Thanks to **@xyuai** (#2587), **@IcedOranges** (#2584), **@BH8GCJ** (#2588), -**@shenjackyuanjie** (#2618, #2619), **@idling11** (#2606, #2616), -**@AresNing** (#2578), **@caiyilian** (#2567), **@buko** (#2369), -**@gordonlu**, **@encyc**, and **@simuusang** (#2603, #2620) for reports, -patches, retesting, and release-stabilization signals that shaped this pass. - --- Older releases: [CHANGELOG.md](https://github.com/Hmbown/CodeWhale/blob/main/CHANGELOG.md) and [docs/CHANGELOG_ARCHIVE.md](https://github.com/Hmbown/CodeWhale/blob/main/docs/CHANGELOG_ARCHIVE.md). diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index f6514be418..d762ac63a6 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -21,12 +21,12 @@ path = "src/main.rs" [dependencies] anyhow = "1.0.100" -codewhale-config = { path = "../config", version = "0.8.66" } -codewhale-execpolicy = { path = "../execpolicy", version = "0.8.66" } -codewhale-protocol = { path = "../protocol", version = "0.8.66" } -codewhale-release = { path = "../release", version = "0.8.66" } -codewhale-secrets = { path = "../secrets", version = "0.8.66" } -codewhale-tools = { path = "../tools", version = "0.8.66" } +codewhale-config = { path = "../config", version = "0.8.67" } +codewhale-execpolicy = { path = "../execpolicy", version = "0.8.67" } +codewhale-protocol = { path = "../protocol", version = "0.8.67" } +codewhale-release = { path = "../release", version = "0.8.67" } +codewhale-secrets = { path = "../secrets", version = "0.8.67" } +codewhale-tools = { path = "../tools", version = "0.8.67" } schemaui = { version = "0.12.0", default-features = false, optional = true } async-stream = "0.3.6" async-trait = "0.1" diff --git a/docs/INSTALL.md b/docs/INSTALL.md index fee50d9708..7a2bb47a0b 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -56,7 +56,7 @@ builds. They dynamically link normal Linux runtime libraries such as This floor applies only to the **GNU libc** assets (arm64, riscv64). The static x64 (musl) asset has no `GLIBC_*` symbols, so it passes the install preflight -and runs on older systems without error. In the current v0.8.66 release lane, +and runs on older systems without error. In the current v0.8.67 release lane, the GNU assets are built on Ubuntu 24.04 and can require `GLIBC_2.39`. Ubuntu 22.04 ships glibc 2.35, so those arm64/riscv64 binaries fail with errors such as: @@ -127,11 +127,11 @@ a download sourced from an impersonating repository or mirror. ## 3. Install via npm npm is the recommended install path. The `codewhale` wrapper is published at -v0.8.66 (Node 18+; wrapper available for v0.8.56 and later). +v0.8.67 (Node 18+; wrapper available for v0.8.56 and later). ```bash npm install -g codewhale -codewhale --version # 0.8.66 +codewhale --version # 0.8.67 ``` `postinstall` downloads the right pair of binaries from the matching GitHub diff --git a/npm/codewhale/package.json b/npm/codewhale/package.json index 2fcad85f87..51158f5b0b 100644 --- a/npm/codewhale/package.json +++ b/npm/codewhale/package.json @@ -1,6 +1,6 @@ { "name": "codewhale", - "version": "0.8.66", + "version": "0.8.67", "codewhaleBinaryVersion": "0.8.66", "description": "Install and run CodeWhale, the agentic terminal for open-source and open-weight coding models, from GitHub release artifacts.", "author": "Hmbown", diff --git a/web/lib/facts.generated.ts b/web/lib/facts.generated.ts index 9a956a6763..ca18f1aba3 100644 --- a/web/lib/facts.generated.ts +++ b/web/lib/facts.generated.ts @@ -18,8 +18,8 @@ export interface RepoFacts { } export const FACTS: RepoFacts = { - "generatedAt": "2026-06-30T05:41:26.332Z", - "version": "0.8.66", + "generatedAt": "2026-07-05T04:55:28.855Z", + "version": "0.8.67", "crates": [ "agent", "app-server", From e883ac832525dfc28a55fd05b93374868eeed107 Mon Sep 17 00:00:00 2001 From: Hmbown <101357273+Hmbown@users.noreply.github.com> Date: Sun, 5 Jul 2026 05:09:02 +0000 Subject: [PATCH 7/7] fix(web): map LongCat in provider facts drift check Adding the LongCat ApiProvider variant tripped the web `check:facts` gate ("unmapped ApiProvider variant(s): LongCat"). Add LongCat to PROVIDER_LABEL_MAP (web/scripts/facts-lib.mjs) and labelMap (web/lib/facts-drift.ts), mirroring the Sakana entry, and regenerate web/lib/facts.generated.ts. `node scripts/check-facts.mjs` now passes (providers=30, version=0.8.67). Signed-off-by: Hmbown <101357273+Hmbown@users.noreply.github.com> --- web/lib/facts-drift.ts | 1 + web/lib/facts.generated.ts | 7 ++++++- web/scripts/facts-lib.mjs | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/web/lib/facts-drift.ts b/web/lib/facts-drift.ts index 2773915d93..7e5968eef0 100644 --- a/web/lib/facts-drift.ts +++ b/web/lib/facts-drift.ts @@ -105,6 +105,7 @@ function deriveProvidersFromConfig(cfg: string): ProviderFact[] { Minimax: { id: "minimax", label: "MiniMax", env: "MINIMAX_API_KEY" }, Openmodel: { id: "openmodel", label: "OpenModel", env: "OPENMODEL_API_KEY" }, Sakana: { id: "sakana", label: "Sakana AI", env: "FUGU_API_KEY / SAKANA_API_KEY" }, + LongCat: { id: "longcat", label: "LongCat", env: "LONGCAT_API_KEY" }, }; // Log loudly on unmapped variants so a new provider can never be silently // dropped from the drift-derived facts again. DeepseekCN (#1104) and the diff --git a/web/lib/facts.generated.ts b/web/lib/facts.generated.ts index ca18f1aba3..69d7a5d287 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-07-05T04:55:28.855Z", + "generatedAt": "2026-07-05T05:08:38.020Z", "version": "0.8.67", "crates": [ "agent", @@ -189,6 +189,11 @@ export const FACTS: RepoFacts = { "id": "sakana", "label": "Sakana AI", "env": "FUGU_API_KEY / SAKANA_API_KEY" + }, + { + "id": "longcat", + "label": "LongCat", + "env": "LONGCAT_API_KEY" } ], "defaultModel": "deepseek-v4-pro", diff --git a/web/scripts/facts-lib.mjs b/web/scripts/facts-lib.mjs index cc4b9c63d7..e0444d1dcb 100644 --- a/web/scripts/facts-lib.mjs +++ b/web/scripts/facts-lib.mjs @@ -89,6 +89,7 @@ const PROVIDER_LABEL_MAP = { Minimax: { id: "minimax", label: "MiniMax", env: "MINIMAX_API_KEY" }, Openmodel: { id: "openmodel", label: "OpenModel", env: "OPENMODEL_API_KEY" }, Sakana: { id: "sakana", label: "Sakana AI", env: "FUGU_API_KEY / SAKANA_API_KEY" }, + LongCat: { id: "longcat", label: "LongCat", env: "LONGCAT_API_KEY" }, }; // DeepseekCN: not wired through shared ProviderKind (#1104).