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
5 changes: 4 additions & 1 deletion desktop/src-tauri/src/managed_agents/discovery/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ pub(crate) 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,
// Claude Code reads ANTHROPIC_MODEL at process start (same key readiness
// already maps for anthropic). Without this, Edit Agent model picks never
// reach the spawned process (#2692).
model_env_var: Some(crate::managed_agents::ANTHROPIC_MODEL_ENV_KEY),
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 @@ -637,7 +637,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 @@ -26,7 +26,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 @@ -442,9 +442,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 @@ -463,6 +463,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 @@ -493,7 +493,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"),
Some("openrouter") => Some("OPENROUTER_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 @@ -524,13 +524,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
11 changes: 11 additions & 0 deletions desktop/src/features/agents/lib/agentCardAvatar.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ test("running agent card falls back to the definition avatar", () => {
);
});

test("running agent card falls back to the managed agent avatar", () => {
assert.equal(
resolveAgentCardAvatarUrl(
null,
null,
" https://relay.example/managed.png ",
),
"https://relay.example/managed.png",
);
});

test("running agent card ignores blank avatar values", () => {
assert.equal(resolveAgentCardAvatarUrl(" ", ""), null);
});
Expand Down
7 changes: 6 additions & 1 deletion desktop/src/features/agents/lib/agentCardAvatar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@
export function resolveAgentCardAvatarUrl(
profileAvatarUrl: string | null | undefined,
personaAvatarUrl: string | null | undefined,
managedAgentAvatarUrl?: string | null | undefined,
): string | null {
for (const candidate of [profileAvatarUrl, personaAvatarUrl]) {
for (const candidate of [
profileAvatarUrl,
managedAgentAvatarUrl,
personaAvatarUrl,
]) {
const trimmed = candidate?.trim();
if (trimmed) return trimmed;
}
Expand Down
19 changes: 16 additions & 3 deletions desktop/src/features/agents/ui/UnifiedAgentsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,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
? resolveAgentCardAvatarUrl(profileQuery.data?.avatarUrl, persona.avatarUrl)
? resolveAgentCardAvatarUrl(
profileQuery.data?.avatarUrl,
persona.avatarUrl,
agent.avatarUrl,
)
: persona.avatarUrl;
const friendlyError = agent
? friendlyAgentLastError(agent.lastError, agent.lastErrorCode)?.copy
Expand Down Expand Up @@ -394,6 +400,13 @@ function StandaloneAgentCard({
const availability = getAvailability(agent.pubkey);
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 = resolveAgentCardAvatarUrl(
profileQuery.data?.avatarUrl,
undefined,
agent.avatarUrl,
);
const friendlyError = friendlyAgentLastError(
agent.lastError,
agent.lastErrorCode,
Expand All @@ -407,7 +420,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 @@ -427,7 +440,7 @@ function StandaloneAgentCard({
}
/>
}
avatarUrl={profileQuery.data?.avatarUrl}
avatarUrl={avatarUrl}
dataTestId={`managed-agent-${agent.pubkey}`}
footerAccessory={
<ProtectedBestieCardBadge agent={agent} isBestie={isBestie} />
Expand Down