From 2ee01891b3f7dceff13c2ae168ae99b32a163a31 Mon Sep 17 00:00:00 2001 From: Wes Date: Tue, 21 Jul 2026 08:15:34 -0700 Subject: [PATCH 1/2] fix(desktop): align onboarding runtime auth Repin existing Welcome agents to the selected onboarding runtime and keep Claude readiness and launch auth isolated from stale ambient API keys. Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Wes --- .../src-tauri/src/managed_agents/discovery.rs | 1 + .../src/managed_agents/readiness/cli_probe.rs | 32 ++++++++++++ .../src-tauri/src/managed_agents/runtime.rs | 17 +++++- .../src/managed_agents/runtime/tests.rs | 27 ++++++++++ .../features/onboarding/welcomeGuide.test.mjs | 41 +++++++++++++++ .../src/features/onboarding/welcomeGuide.ts | 52 ++++++++++++++----- 6 files changed, 155 insertions(+), 15 deletions(-) diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs index 50206567ad5..6643f92d32d 100644 --- a/desktop/src-tauri/src/managed_agents/discovery.rs +++ b/desktop/src-tauri/src/managed_agents/discovery.rs @@ -909,6 +909,7 @@ fn probe_auth_status(binary_path: &Path, probe_args: &[&str]) -> AuthStatus { let mut command = std::process::Command::new(binary_path); command.args(&probe_args[1..]); + cli_probe::configure_auth_probe_command(&mut command, probe_args); if let Some(ref path) = augmented_path { command.env("PATH", path); } diff --git a/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs b/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs index 8e2f866b7e4..1c47602892d 100644 --- a/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs +++ b/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs @@ -47,6 +47,15 @@ pub(crate) enum ProbeOutcome { /// one term. const CONFIG_PARSE_SIGNALS: &[&str] = &["error loading configuration", "unknown variant"]; +pub(crate) fn configure_auth_probe_command( + command: &mut std::process::Command, + probe_args: &[&str], +) { + if probe_args.first() == Some(&"claude") { + command.env_remove("ANTHROPIC_API_KEY"); + } +} + /// Run the probe at the resolved absolute path so the GUI-PATH gap is /// bypassed. Injects the same augmented PATH used for launched agents so /// script shims with `/usr/bin/env ` shebangs can find runtimes @@ -58,6 +67,7 @@ pub(crate) fn login_probe( ) -> ProbeOutcome { let mut command = std::process::Command::new(binary_path); command.args(&probe_args[1..]); + configure_auth_probe_command(&mut command, probe_args); if let Some(path) = augmented_path { command.env("PATH", path); } @@ -97,6 +107,28 @@ pub(crate) fn classify_probe_output(stderr_bytes: &[u8], exit_success: bool) -> mod tests { use super::{ProbeOutcome, CONFIG_PARSE_SIGNALS}; + #[test] + fn claude_auth_probe_removes_ambient_api_key() { + let mut command = std::process::Command::new("claude"); + command.env("ANTHROPIC_API_KEY", "invalid-but-present"); + super::configure_auth_probe_command(&mut command, &["claude", "auth", "status"]); + + assert!(command + .get_envs() + .any(|(key, value)| { key == "ANTHROPIC_API_KEY" && value.is_none() })); + } + + #[test] + fn codex_auth_probe_leaves_unrelated_api_key_alone() { + let mut command = std::process::Command::new("codex"); + command.env("ANTHROPIC_API_KEY", "unrelated"); + super::configure_auth_probe_command(&mut command, &["codex", "login", "status"]); + + assert!(command.get_envs().any(|(key, value)| { + key == "ANTHROPIC_API_KEY" && value == Some(std::ffi::OsStr::new("unrelated")) + })); + } + #[cfg(unix)] #[test] fn login_probe_uses_augmented_path_for_env_shebang_interpreter() { diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index 7429c4ffa10..c12c23770f2 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -8,7 +8,8 @@ use crate::{ managed_agents::{ append_log_marker, known_acp_runtime, login_shell_path, managed_agent_log_path, missing_command_message, normalize_agent_args, open_log_file, resolve_command, - spawn_key_refusal, ManagedAgentProcess, ManagedAgentRecord, ManagedAgentSummary, + spawn_key_refusal, KnownAcpRuntime, ManagedAgentProcess, ManagedAgentRecord, + ManagedAgentSummary, }, util::now_iso, }; @@ -1466,6 +1467,15 @@ pub(crate) fn build_respond_to_env( Ok((set, remove)) } +fn scrub_ambient_runtime_credentials( + command: &mut std::process::Command, + runtime: Option<&KnownAcpRuntime>, +) { + if runtime.is_some_and(|runtime| runtime.id == "claude") { + command.env_remove("ANTHROPIC_API_KEY"); + } +} + /// Spawn an agent process without holding any locks on records or runtimes. /// Returns the child process and log path on success. The caller is responsible /// for updating `ManagedAgentRecord` fields and inserting into the runtimes map. @@ -1819,6 +1829,11 @@ pub fn spawn_agent_child( ); } + // Claude subscription auth must come from the CLI credential store unless + // the user explicitly configured an API key in Buzz. A stale key inherited + // from the Desktop process otherwise overrides a valid subscription login. + scrub_ambient_runtime_credentials(&mut command, runtime_meta); + // ── User env vars: live persona env under agent overrides ────────── // // The record's `env_vars` holds agent-level overrides only. The linked diff --git a/desktop/src-tauri/src/managed_agents/runtime/tests.rs b/desktop/src-tauri/src/managed_agents/runtime/tests.rs index 06d3ac6db31..447c58e1da8 100644 --- a/desktop/src-tauri/src/managed_agents/runtime/tests.rs +++ b/desktop/src-tauri/src/managed_agents/runtime/tests.rs @@ -579,6 +579,33 @@ fn name_matches_interpreter_rejects_node_prefix() { assert!(!super::name_matches_interpreter("node-gyp")); } +#[test] +fn claude_spawn_scrubs_ambient_api_key() { + let mut command = std::process::Command::new("buzz-acp"); + command.env("ANTHROPIC_API_KEY", "stale"); + + super::scrub_ambient_runtime_credentials( + &mut command, + super::known_acp_runtime("claude-agent-acp"), + ); + + assert!(command + .get_envs() + .any(|(key, value)| key == "ANTHROPIC_API_KEY" && value.is_none())); +} + +#[test] +fn non_claude_spawn_preserves_ambient_api_key() { + let mut command = std::process::Command::new("buzz-acp"); + command.env("ANTHROPIC_API_KEY", "explicit"); + + super::scrub_ambient_runtime_credentials(&mut command, super::known_acp_runtime("codex-acp")); + + assert!(command.get_envs().any(|(key, value)| { + key == "ANTHROPIC_API_KEY" && value == Some(std::ffi::OsStr::new("explicit")) + })); +} + // ── PGID-based orphan sweep tests ─────────────────────────────────────── /// Validates the kernel invariant that the orphan sweep PGID fix relies on: diff --git a/desktop/src/features/onboarding/welcomeGuide.test.mjs b/desktop/src/features/onboarding/welcomeGuide.test.mjs index 7e00c3b5536..7002ed7f4fd 100644 --- a/desktop/src/features/onboarding/welcomeGuide.test.mjs +++ b/desktop/src/features/onboarding/welcomeGuide.test.mjs @@ -8,6 +8,7 @@ import { pickWelcomeGuideAgent, pickWelcomeGuideAgentForRelay, pickWelcomeTeamStarterAgentForRelay, + welcomeStarterRuntimeUpdate, WELCOME_GUIDE_AGENT_NAME, WELCOME_GUIDE_PERSONA_ID, WELCOME_TEAM_ID, @@ -205,6 +206,46 @@ test("all Welcome starters use the onboarding runtime preference", async () => { } }); +test("existing Welcome starter is repinned when onboarding runtime changes", () => { + const existing = makeAgent({ + pubkey: PUB_A, + personaId: WELCOME_GUIDE_PERSONA_ID, + agentCommand: "claude-agent-acp", + agentArgs: ["--old"], + }); + + assert.deepEqual( + welcomeStarterRuntimeUpdate(existing, { + name: "Fizz", + agentCommand: "codex-acp", + agentArgs: ["--new"], + }), + { + pubkey: PUB_A, + agentCommand: "codex-acp", + harnessOverride: true, + agentArgs: ["--new"], + }, + ); +}); + +test("existing Welcome starter needs no update when runtime already matches", () => { + const existing = makeAgent({ + personaId: WELCOME_GUIDE_PERSONA_ID, + agentCommand: "codex-acp", + agentArgs: ["--same"], + }); + + assert.equal( + welcomeStarterRuntimeUpdate(existing, { + name: "Fizz", + agentCommand: "codex-acp", + agentArgs: ["--same"], + }), + null, + ); +}); + test("welcome team starter definitions and role identities are stable", () => { assert.equal(WELCOME_TEAM_ID, "builtin-team:welcome"); assert.deepEqual(WELCOME_TEAM_STARTERS, [ diff --git a/desktop/src/features/onboarding/welcomeGuide.ts b/desktop/src/features/onboarding/welcomeGuide.ts index a3dd6a43703..fe392c7412b 100644 --- a/desktop/src/features/onboarding/welcomeGuide.ts +++ b/desktop/src/features/onboarding/welcomeGuide.ts @@ -229,6 +229,26 @@ export async function buildWelcomeStarterCreateInput( }; } +export function welcomeStarterRuntimeUpdate( + existing: ManagedAgent, + desired: CreateManagedAgentInput, +) { + if ( + !desired.agentCommand || + (existing.agentCommand === desired.agentCommand && + existing.agentArgs.join(",") === (desired.agentArgs ?? []).join(",")) + ) { + return null; + } + + return { + pubkey: existing.pubkey, + agentCommand: desired.agentCommand, + harnessOverride: true, + agentArgs: desired.agentArgs ?? [], + }; +} + /** * Ensure the complete built-in Welcome Team is ready for kickoff. * The team itself is Rust-seeded; this only activates personas, creates any @@ -254,29 +274,33 @@ async function provisionWelcomeTeam( const agents: ManagedAgent[] = []; for (const starter of WELCOME_TEAM_STARTERS) { + const persona = personasById.get(starter.personaId); + if (!persona) { + throw new Error(`${starter.name} agent not found.`); + } + const desired = await buildWelcomeStarterCreateInput( + starter, + persona, + runtimes, + globalConfig.preferred_runtime, + relayUrl, + ); const existing = pickWelcomeTeamStarterAgentForRelay( existingAgents, starter, relayUrl, ); if (existing) { - agents.push(existing); + const runtimeUpdate = welcomeStarterRuntimeUpdate(existing, desired); + agents.push( + runtimeUpdate + ? (await updateManagedAgent(runtimeUpdate)).agent + : existing, + ); continue; } - const persona = personasById.get(starter.personaId); - if (!persona) { - throw new Error(`${starter.name} agent not found.`); - } - const created = await createManagedAgent( - await buildWelcomeStarterCreateInput( - starter, - persona, - runtimes, - globalConfig.preferred_runtime, - relayUrl, - ), - ); + const created = await createManagedAgent(desired); agents.push(created.agent); } const [lead, honey, bumble] = agents; From fdf1c926551b0a942994c321364a408ace70d33b Mon Sep 17 00:00:00 2001 From: Wes Date: Tue, 21 Jul 2026 09:00:38 -0700 Subject: [PATCH 2/2] fix(onboarding): rematerialize starter runtime config Keep the Welcome starter's command, arguments, MCP command, model, and provider aligned when onboarding switches harnesses. Route Claude launches and model discovery through the same resolved CLI used by readiness checks. Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co> Signed-off-by: Wes --- .../src/commands/agent_model_process.rs | 1 + .../src-tauri/src/managed_agents/discovery.rs | 1 - .../src/managed_agents/readiness/cli_probe.rs | 32 ------------- .../src-tauri/src/managed_agents/runtime.rs | 18 ++++---- .../src/managed_agents/runtime/tests.rs | 44 +++++++++++------- .../features/onboarding/welcomeGuide.test.mjs | 45 ++++++++++++++++++- .../src/features/onboarding/welcomeGuide.ts | 19 ++++++-- 7 files changed, 98 insertions(+), 62 deletions(-) diff --git a/desktop/src-tauri/src/commands/agent_model_process.rs b/desktop/src-tauri/src/commands/agent_model_process.rs index c61170a4a1f..2093d4fe293 100644 --- a/desktop/src-tauri/src/commands/agent_model_process.rs +++ b/desktop/src-tauri/src/commands/agent_model_process.rs @@ -48,6 +48,7 @@ pub(super) async fn run_agent_models_command( for (k, v) in &merged_env { cmd.env(k, v); } + crate::managed_agents::configure_runtime_cli(&mut cmd, known_acp_runtime(&agent_command)); cmd.stdout(std::process::Stdio::piped()) .stderr(std::process::Stdio::piped()) .output() diff --git a/desktop/src-tauri/src/managed_agents/discovery.rs b/desktop/src-tauri/src/managed_agents/discovery.rs index 6643f92d32d..50206567ad5 100644 --- a/desktop/src-tauri/src/managed_agents/discovery.rs +++ b/desktop/src-tauri/src/managed_agents/discovery.rs @@ -909,7 +909,6 @@ fn probe_auth_status(binary_path: &Path, probe_args: &[&str]) -> AuthStatus { let mut command = std::process::Command::new(binary_path); command.args(&probe_args[1..]); - cli_probe::configure_auth_probe_command(&mut command, probe_args); if let Some(ref path) = augmented_path { command.env("PATH", path); } diff --git a/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs b/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs index 1c47602892d..8e2f866b7e4 100644 --- a/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs +++ b/desktop/src-tauri/src/managed_agents/readiness/cli_probe.rs @@ -47,15 +47,6 @@ pub(crate) enum ProbeOutcome { /// one term. const CONFIG_PARSE_SIGNALS: &[&str] = &["error loading configuration", "unknown variant"]; -pub(crate) fn configure_auth_probe_command( - command: &mut std::process::Command, - probe_args: &[&str], -) { - if probe_args.first() == Some(&"claude") { - command.env_remove("ANTHROPIC_API_KEY"); - } -} - /// Run the probe at the resolved absolute path so the GUI-PATH gap is /// bypassed. Injects the same augmented PATH used for launched agents so /// script shims with `/usr/bin/env ` shebangs can find runtimes @@ -67,7 +58,6 @@ pub(crate) fn login_probe( ) -> ProbeOutcome { let mut command = std::process::Command::new(binary_path); command.args(&probe_args[1..]); - configure_auth_probe_command(&mut command, probe_args); if let Some(path) = augmented_path { command.env("PATH", path); } @@ -107,28 +97,6 @@ pub(crate) fn classify_probe_output(stderr_bytes: &[u8], exit_success: bool) -> mod tests { use super::{ProbeOutcome, CONFIG_PARSE_SIGNALS}; - #[test] - fn claude_auth_probe_removes_ambient_api_key() { - let mut command = std::process::Command::new("claude"); - command.env("ANTHROPIC_API_KEY", "invalid-but-present"); - super::configure_auth_probe_command(&mut command, &["claude", "auth", "status"]); - - assert!(command - .get_envs() - .any(|(key, value)| { key == "ANTHROPIC_API_KEY" && value.is_none() })); - } - - #[test] - fn codex_auth_probe_leaves_unrelated_api_key_alone() { - let mut command = std::process::Command::new("codex"); - command.env("ANTHROPIC_API_KEY", "unrelated"); - super::configure_auth_probe_command(&mut command, &["codex", "login", "status"]); - - assert!(command.get_envs().any(|(key, value)| { - key == "ANTHROPIC_API_KEY" && value == Some(std::ffi::OsStr::new("unrelated")) - })); - } - #[cfg(unix)] #[test] fn login_probe_uses_augmented_path_for_env_shebang_interpreter() { diff --git a/desktop/src-tauri/src/managed_agents/runtime.rs b/desktop/src-tauri/src/managed_agents/runtime.rs index c12c23770f2..77b3a241e0d 100644 --- a/desktop/src-tauri/src/managed_agents/runtime.rs +++ b/desktop/src-tauri/src/managed_agents/runtime.rs @@ -1467,12 +1467,18 @@ pub(crate) fn build_respond_to_env( Ok((set, remove)) } -fn scrub_ambient_runtime_credentials( +pub(crate) fn configure_runtime_cli( command: &mut std::process::Command, runtime: Option<&KnownAcpRuntime>, ) { - if runtime.is_some_and(|runtime| runtime.id == "claude") { - command.env_remove("ANTHROPIC_API_KEY"); + let Some(runtime) = runtime else { + return; + }; + if runtime.id != "claude" { + return; + } + if let Some(cli_path) = runtime.underlying_cli.and_then(resolve_command) { + command.env("CLAUDE_CODE_EXECUTABLE", cli_path); } } @@ -1829,11 +1835,6 @@ pub fn spawn_agent_child( ); } - // Claude subscription auth must come from the CLI credential store unless - // the user explicitly configured an API key in Buzz. A stale key inherited - // from the Desktop process otherwise overrides a valid subscription login. - scrub_ambient_runtime_credentials(&mut command, runtime_meta); - // ── User env vars: live persona env under agent overrides ────────── // // The record's `env_vars` holds agent-level overrides only. The linked @@ -1858,6 +1859,7 @@ pub fn spawn_agent_child( for (key, value) in super::env_vars::merged_user_env(&persona_over_global, &record.env_vars) { command.env(key, value); } + configure_runtime_cli(&mut command, runtime_meta); // Buzz shared compute is stored as a native provider; derive the OpenAI-compatible // transport at spawn time and scrub any unrelated ambient OpenAI key. diff --git a/desktop/src-tauri/src/managed_agents/runtime/tests.rs b/desktop/src-tauri/src/managed_agents/runtime/tests.rs index 447c58e1da8..6a758675064 100644 --- a/desktop/src-tauri/src/managed_agents/runtime/tests.rs +++ b/desktop/src-tauri/src/managed_agents/runtime/tests.rs @@ -580,30 +580,42 @@ fn name_matches_interpreter_rejects_node_prefix() { } #[test] -fn claude_spawn_scrubs_ambient_api_key() { - let mut command = std::process::Command::new("buzz-acp"); - command.env("ANTHROPIC_API_KEY", "stale"); +fn claude_spawn_uses_the_probed_cli_executable() { + let _guard = crate::managed_agents::lock_path_mutex(); + let temp = tempfile::tempdir().expect("temp dir"); + let cli = temp + .path() + .join(format!("claude{}", std::env::consts::EXE_SUFFIX)); + std::fs::write(&cli, "").expect("write fake cli"); + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + std::fs::set_permissions(&cli, std::fs::Permissions::from_mode(0o755)) + .expect("make fake cli executable"); + } + let original_path = std::env::var_os("PATH"); + std::env::set_var("PATH", temp.path()); - super::scrub_ambient_runtime_credentials( - &mut command, - super::known_acp_runtime("claude-agent-acp"), - ); + let mut command = std::process::Command::new("buzz-acp"); + super::configure_runtime_cli(&mut command, super::known_acp_runtime("claude-agent-acp")); + if let Some(path) = original_path { + std::env::set_var("PATH", path); + } else { + std::env::remove_var("PATH"); + } assert!(command .get_envs() - .any(|(key, value)| key == "ANTHROPIC_API_KEY" && value.is_none())); + .any(|(key, value)| { key == "CLAUDE_CODE_EXECUTABLE" && value == Some(cli.as_os_str()) })); } #[test] -fn non_claude_spawn_preserves_ambient_api_key() { +fn codex_spawn_does_not_set_a_claude_executable() { let mut command = std::process::Command::new("buzz-acp"); - command.env("ANTHROPIC_API_KEY", "explicit"); - - super::scrub_ambient_runtime_credentials(&mut command, super::known_acp_runtime("codex-acp")); - - assert!(command.get_envs().any(|(key, value)| { - key == "ANTHROPIC_API_KEY" && value == Some(std::ffi::OsStr::new("explicit")) - })); + super::configure_runtime_cli(&mut command, super::known_acp_runtime("codex-acp")); + assert!(!command + .get_envs() + .any(|(key, _)| key == "CLAUDE_CODE_EXECUTABLE")); } // ── PGID-based orphan sweep tests ─────────────────────────────────────── diff --git a/desktop/src/features/onboarding/welcomeGuide.test.mjs b/desktop/src/features/onboarding/welcomeGuide.test.mjs index 7002ed7f4fd..b3def930f11 100644 --- a/desktop/src/features/onboarding/welcomeGuide.test.mjs +++ b/desktop/src/features/onboarding/welcomeGuide.test.mjs @@ -29,6 +29,7 @@ function makeAgent(overrides = {}) { relayUrl: RELAY_A, acpCommand: "buzz-acp", agentCommand: "buzz-agent", + agentCommandOverride: null, agentArgs: [], mcpCommand: "buzz-dev-mcp", turnTimeoutSeconds: 120, @@ -37,6 +38,7 @@ function makeAgent(overrides = {}) { parallelism: 1, systemPrompt: null, model: null, + provider: null, envVars: {}, status: "stopped", pid: null, @@ -206,12 +208,16 @@ test("all Welcome starters use the onboarding runtime preference", async () => { } }); -test("existing Welcome starter is repinned when onboarding runtime changes", () => { +test("existing Welcome starter rematerializes runtime-specific fields atomically", () => { const existing = makeAgent({ pubkey: PUB_A, personaId: WELCOME_GUIDE_PERSONA_ID, agentCommand: "claude-agent-acp", + agentCommandOverride: "claude-agent-acp", agentArgs: ["--old"], + mcpCommand: "", + model: "claude-sonnet", + provider: "anthropic", }); assert.deepEqual( @@ -219,12 +225,46 @@ test("existing Welcome starter is repinned when onboarding runtime changes", () name: "Fizz", agentCommand: "codex-acp", agentArgs: ["--new"], + mcpCommand: "buzz-dev-mcp", + model: "gpt-5.6-sol", + provider: null, }), { pubkey: PUB_A, agentCommand: "codex-acp", harnessOverride: true, agentArgs: ["--new"], + mcpCommand: "buzz-dev-mcp", + model: "gpt-5.6-sol", + provider: null, + }, + ); +}); + +test("existing Welcome starter clears stale model and provider for Claude", () => { + const existing = makeAgent({ + personaId: WELCOME_GUIDE_PERSONA_ID, + agentCommand: "codex-acp", + agentArgs: [], + model: "gpt-5.6-sol", + provider: "openai", + }); + + assert.deepEqual( + welcomeStarterRuntimeUpdate(existing, { + name: "Fizz", + agentCommand: "claude-agent-acp", + agentArgs: [], + mcpCommand: "", + }), + { + pubkey: PUB_A, + agentCommand: "claude-agent-acp", + harnessOverride: true, + agentArgs: [], + mcpCommand: "", + model: null, + provider: null, }, ); }); @@ -241,6 +281,9 @@ test("existing Welcome starter needs no update when runtime already matches", () name: "Fizz", agentCommand: "codex-acp", agentArgs: ["--same"], + mcpCommand: "buzz-dev-mcp", + model: null, + provider: null, }), null, ); diff --git a/desktop/src/features/onboarding/welcomeGuide.ts b/desktop/src/features/onboarding/welcomeGuide.ts index fe392c7412b..aa8deb7c19b 100644 --- a/desktop/src/features/onboarding/welcomeGuide.ts +++ b/desktop/src/features/onboarding/welcomeGuide.ts @@ -233,10 +233,18 @@ export function welcomeStarterRuntimeUpdate( existing: ManagedAgent, desired: CreateManagedAgentInput, ) { + if (!desired.agentCommand) return null; + + const desiredArgs = desired.agentArgs ?? []; + const desiredModel = desired.model ?? null; + const desiredProvider = desired.provider ?? null; + const desiredMcpCommand = desired.mcpCommand ?? ""; if ( - !desired.agentCommand || - (existing.agentCommand === desired.agentCommand && - existing.agentArgs.join(",") === (desired.agentArgs ?? []).join(",")) + existing.agentCommand === desired.agentCommand && + existing.agentArgs.join(",") === desiredArgs.join(",") && + existing.model === desiredModel && + existing.provider === desiredProvider && + existing.mcpCommand === desiredMcpCommand ) { return null; } @@ -245,7 +253,10 @@ export function welcomeStarterRuntimeUpdate( pubkey: existing.pubkey, agentCommand: desired.agentCommand, harnessOverride: true, - agentArgs: desired.agentArgs ?? [], + agentArgs: desiredArgs, + mcpCommand: desiredMcpCommand, + model: desiredModel, + provider: desiredProvider, }; }