fix(desktop): align onboarding runtime auth (#2229)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-07-21 10:32:00 -06:00
committed by GitHub
parent 258cc92064
commit c1de3e7c0e
5 changed files with 191 additions and 15 deletions

View File

@@ -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()

View File

@@ -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,21 @@ pub(crate) fn build_respond_to_env(
Ok((set, remove))
}
pub(crate) fn configure_runtime_cli(
command: &mut std::process::Command,
runtime: Option<&KnownAcpRuntime>,
) {
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);
}
}
/// 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.
@@ -1843,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.

View File

@@ -579,6 +579,45 @@ fn name_matches_interpreter_rejects_node_prefix() {
assert!(!super::name_matches_interpreter("node-gyp"));
}
#[test]
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());
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 == "CLAUDE_CODE_EXECUTABLE" && value == Some(cli.as_os_str()) }));
}
#[test]
fn codex_spawn_does_not_set_a_claude_executable() {
let mut command = std::process::Command::new("buzz-acp");
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 ───────────────────────────────────────
/// Validates the kernel invariant that the orphan sweep PGID fix relies on:

View File

@@ -8,6 +8,7 @@ import {
pickWelcomeGuideAgent,
pickWelcomeGuideAgentForRelay,
pickWelcomeTeamStarterAgentForRelay,
welcomeStarterRuntimeUpdate,
WELCOME_GUIDE_AGENT_NAME,
WELCOME_GUIDE_PERSONA_ID,
WELCOME_TEAM_ID,
@@ -28,6 +29,7 @@ function makeAgent(overrides = {}) {
relayUrl: RELAY_A,
acpCommand: "buzz-acp",
agentCommand: "buzz-agent",
agentCommandOverride: null,
agentArgs: [],
mcpCommand: "buzz-dev-mcp",
turnTimeoutSeconds: 120,
@@ -36,6 +38,7 @@ function makeAgent(overrides = {}) {
parallelism: 1,
systemPrompt: null,
model: null,
provider: null,
envVars: {},
status: "stopped",
pid: null,
@@ -205,6 +208,87 @@ test("all Welcome starters use the onboarding runtime preference", async () => {
}
});
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(
welcomeStarterRuntimeUpdate(existing, {
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,
},
);
});
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"],
mcpCommand: "buzz-dev-mcp",
model: null,
provider: null,
}),
null,
);
});
test("welcome team starter definitions and role identities are stable", () => {
assert.equal(WELCOME_TEAM_ID, "builtin-team:welcome");
assert.deepEqual(WELCOME_TEAM_STARTERS, [

View File

@@ -229,6 +229,37 @@ export async function buildWelcomeStarterCreateInput(
};
}
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 (
existing.agentCommand === desired.agentCommand &&
existing.agentArgs.join(",") === desiredArgs.join(",") &&
existing.model === desiredModel &&
existing.provider === desiredProvider &&
existing.mcpCommand === desiredMcpCommand
) {
return null;
}
return {
pubkey: existing.pubkey,
agentCommand: desired.agentCommand,
harnessOverride: true,
agentArgs: desiredArgs,
mcpCommand: desiredMcpCommand,
model: desiredModel,
provider: desiredProvider,
};
}
/**
* 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 +285,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;