Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[
adapter_install_hint: "Buzz talks to the Claude Code CLI through an ACP adapter. Install it with: npm install -g @agentclientprotocol/claude-agent-acp.",
skill_dir: Some(".claude/skills"),
supports_acp_model_switching: false,
model_env_var: None,
model_env_var: Some(crate::managed_agents::ANTHROPIC_MODEL_ENV_KEY), // #2692
provider_env_var: None,
provider_locked: true,
default_env: &[],
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/discovery/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ fn probe_codex_acp_version_parses_full_semver_output() {
"adapter output must parse to its full semantic version"
);
}

mod claude_model_env;
mod codex_version;

#[cfg(unix)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/// The claude runtime must declare a model env channel — without one, the
/// model picked in the UI is persisted but never applied at spawn (#2692).
#[test]
fn claude_runtime_declares_anthropic_model_env_var() {
let claude = super::super::known_acp_runtime_exact("claude").expect("claude runtime registered");
assert_eq!(claude.model_env_var, Some("ANTHROPIC_MODEL"));
assert!(claude.provider_locked);
assert_eq!(claude.provider_env_var, None);
}
7 changes: 7 additions & 0 deletions desktop/src-tauri/src/managed_agents/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,14 @@ use std::collections::BTreeMap;
///
/// Non-structured knobs (`GOOSE_TEMPERATURE`, `GOOSE_CONTEXT_LIMIT`) are NOT
/// in this list — they have no structured counterpart and must be preserved.
/// Env var the Claude CLI honors as a session model override. Single source
/// of truth for the key — referenced by the claude runtime entry
/// (`discovery.rs`), the derived-key list below, and the readiness
/// provider-model mapping, so the three cannot drift apart.
pub(crate) const ANTHROPIC_MODEL_ENV_KEY: &str = "ANTHROPIC_MODEL";

pub(crate) const DERIVED_PROVIDER_MODEL_ENV_KEYS: &[&str] = &[
ANTHROPIC_MODEL_ENV_KEY,
"GOOSE_MODEL",
"GOOSE_PROVIDER",
"BUZZ_AGENT_MODEL",
Expand Down
7 changes: 4 additions & 3 deletions desktop/src-tauri/src/managed_agents/env_vars/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -418,9 +418,9 @@ fn merged_env_drops_oversize_value() {

// ── derived provider/model key filter ──────────────────────────────
//
// Pack import must strip derived env keys (GOOSE_MODEL, GOOSE_PROVIDER,
// BUZZ_AGENT_MODEL, BUZZ_AGENT_PROVIDER) so they don't shadow the
// structured AgentDefinition.model / AgentDefinition.provider fields after
// Pack import must strip derived env keys (ANTHROPIC_MODEL, GOOSE_MODEL,
// GOOSE_PROVIDER, BUZZ_AGENT_MODEL, BUZZ_AGENT_PROVIDER) so they don't shadow
// the structured AgentDefinition.model / AgentDefinition.provider fields after
// the user edits them in the UI.

#[test]
Expand All @@ -439,6 +439,7 @@ fn is_derived_key_is_case_insensitive() {
assert!(is_derived_provider_model_key("Goose_Provider"));
assert!(is_derived_provider_model_key("buzz_agent_model"));
assert!(is_derived_provider_model_key("BUZZ_AGENT_PROVIDER"));
assert!(is_derived_provider_model_key("anthropic_model"));
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ fn buzz_agent_requirements(effective: &EffectiveAgentEnv) -> Vec<Requirement> {
Some("databricks") | Some("databricks_v2") | Some("databricks-v2") => {
Some("DATABRICKS_MODEL")
}
Some("anthropic") => Some("ANTHROPIC_MODEL"),
Some("anthropic") => Some(super::ANTHROPIC_MODEL_ENV_KEY),
Some("openai") | Some("openai-compat") => Some("OPENAI_COMPAT_MODEL"),
_ => None,
};
Expand Down
6 changes: 3 additions & 3 deletions desktop/src-tauri/src/managed_agents/runtime/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -535,13 +535,13 @@ fn runtime_metadata_env_vars_injects_model_and_provider() {
#[test]
fn runtime_metadata_env_vars_skips_provider_when_locked() {
let vars = runtime_metadata_env_vars(
None, // claude has no model_env_var
None, // claude has no provider_env_var
Some("ANTHROPIC_MODEL"),
None,
true, // provider_locked = true
Some("claude-opus-4-7"),
Some("anthropic"),
);
assert!(vars.is_empty());
assert_eq!(vars, vec![("ANTHROPIC_MODEL", "claude-opus-4-7")]);
}

#[test]
Expand Down
18 changes: 15 additions & 3 deletions desktop/src/features/agents/ui/UnifiedAgentsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,14 @@ function AgentPersonaCard({
});
const isActive = agent ? isManagedAgentActive(agent) : false;
const profileQuery = useUserProfileQuery(agent?.pubkey);
// Prefer the managed agent record — it survives restart even when the
// profile query cache is still empty (#2576).
const avatarUrl = agent
? firstAvatarUrl(persona.avatarUrl, profileQuery.data?.avatarUrl)
? firstAvatarUrl(
agent.avatarUrl,
persona.avatarUrl,
profileQuery.data?.avatarUrl,
)
: persona.avatarUrl;
const friendlyError = agent
? friendlyAgentLastError(agent.lastError, agent.lastErrorCode)?.copy
Expand Down Expand Up @@ -361,6 +367,12 @@ function StandaloneAgentCard({
}) {
const title = agent.name;
const profileQuery = useUserProfileQuery(agent.pubkey);
// Prefer the managed agent record — it survives restart even when the
// profile query cache is still empty (#2576).
const avatarUrl = firstAvatarUrl(
agent.avatarUrl,
profileQuery.data?.avatarUrl,
);
const friendlyError = friendlyAgentLastError(
agent.lastError,
agent.lastErrorCode,
Expand All @@ -374,7 +386,7 @@ function StandaloneAgentCard({
avatar={
<AgentRuntimeAvatarControl
activeTestId={`agent-runtime-active-${agent.pubkey}`}
avatarUrl={profileQuery.data?.avatarUrl}
avatarUrl={avatarUrl}
errorLabel={friendlyError}
errorTestId={`agent-runtime-error-${agent.pubkey}`}
isActive={isActive}
Expand All @@ -387,7 +399,7 @@ function StandaloneAgentCard({
onStart={() => onStartAgent(agent.pubkey)}
/>
}
avatarUrl={profileQuery.data?.avatarUrl}
avatarUrl={avatarUrl}
dataTestId={`managed-agent-${agent.pubkey}`}
label={title}
modelLabel={resolveAgentCardModelLabel({
Expand Down