mirror of
https://github.com/block/buzz.git
synced 2026-11-13 04:41:12 +00:00
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:
@@ -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()
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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, [
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user