Files
buzz/desktop/src-tauri/src/commands/agent_model_process.rs
Logan Johnson 4a9de1a3a1 feat(desktop): add isolated named demo builds (#6407)
🤖 I’m Larry, updating this description on Logan’s behalf.

## Summary

Build named macOS demo apps without Finder automation or collisions with
installed Buzz. `just desktop-demo-build "PR 6407 Demo"` produces a
matching app and DMG, with a fresh build identity even when the same
display name is reused.

- The headless DMG packager uses `hdiutil`; optional Finder styling is
bounded. The existing production release recipe is unchanged.
- Each demo has independent app data, keychain, nest, CLI name,
voice-model storage, repository discovery, and agent OAuth/config
storage. Reset preserves production and sibling-demo state, and retains
retry intent when credential removal or root resolution fails.
- Native links accept only the active build’s registered scheme, then
translate validated entity links into the frontend’s canonical `buzz:`
format.
- The recipe builds all six executable sidecars. Display names are
capped at 31 ASCII characters so the generated identity fits Rust’s
build-time limit.

**Open delivery requirement:** downloaded demos must run without a
Gatekeeper security override. The current recipe is ad-hoc signed and
unnotarized; it does **not** satisfy this requirement. Trusted
branch-demo signing/distribution remains blocked on establishing an
approved signing path. This PR is not being presented as complete
download-and-run delivery.

### Related issue

N/A — reported in the Buzz DMG-packaging workstream.

### Testing

At `11ce21ff97cb387ad676e7caa65b00964097d0bb`, macOS Blox passed the
Tauri workspace suite and compiled-flags gate (including the full
named-demo state; each library pass: 2,992 passed, 19 ignored), Tauri
all-target clippy, the full `buzz-agent` package suite, and frontend
lint/typecheck plus 5,733 tests. Regression coverage includes
cold-start/running entity-link handling, wrong-build rejection, OAuth
deletion failure and retry, unresolved credential roots, and
production/sibling preservation.

At the same head, an extra full named-demo/mesh-enabled run had 3,092
passing tests and one failure: a pre-existing shared-compute `auto`
versus `mesh` expectation, also reproduced on the old published head
`a77b25eca`. The ordinary and demo-state matrix above passes; this is
not an all-features-green claim. Live macOS Launch Services delivery
remains unverified.

GitHub CI completed with 30 successful checks and 9 skipped. The
exact-range security review has not run; its authorization notice
remains open. CI success does not establish trusted signing or
downloaded-app launch.

Earlier demo artifacts established matching app/DMG names, side-by-side
launch, and six non-empty executable arm64 sidecars. These screenshots
show an earlier artifact, not a new build of the final repair commit.
Signature-integrity checks are not Gatekeeper/notarization evidence.

<img width="1032" height="548" alt="Buzz PR 6407 Demo disk image
containing the matching app"
src="https://github.com/user-attachments/assets/bca0277e-db03-4308-b280-fcad55e6d601"
/>

<img width="1186" height="821" alt="Buzz PR 6407 Demo running alongside
other Buzz installations"
src="https://github.com/user-attachments/assets/b4bf4ae5-c341-4e15-8090-9d2ea7c623b6"
/>

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Signed-off-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
Co-authored-by: Other Brother Darryl <cee32d92756729ee0c097c5661b879c6199931cd25315c8cf398dcbf0f155cf1@buzz.block.builderlab.xyz>
Co-authored-by: Larry <loganj+sandbox-larry@squareup.com>
Co-authored-by: Larry <8cf5a83f590ec0955b11647d1c88f796a98e088c30a492c58e0e46c3026ae7a4@buzz.block.builderlab.xyz>
2026-08-31 21:14:19 -04:00

87 lines
3.8 KiB
Rust

use std::{collections::BTreeMap, path::PathBuf};
use crate::managed_agents::{
build_buzz_agent_provider_defaults, default_agent_workdir, known_acp_runtime,
redact_env_values_in, AgentModelsResponse,
};
use super::agent_models::normalize_agent_models;
pub(super) async fn run_agent_models_command(
resolved_acp: PathBuf,
agent_command: String,
agent_args: Vec<String>,
persisted_model: Option<String>,
merged_env: BTreeMap<String, String>,
) -> Result<AgentModelsResponse, String> {
// Clone the env map for redaction below — `merged_env` is moved
// into the spawn_blocking closure and we still need the values to
// scrub any user-supplied secrets that the child surfaces in stderr.
let env_for_redaction = merged_env.clone();
// Use spawn_blocking because the desktop Tauri crate doesn't enable
// tokio's `process` feature. std::process::Command is synchronous
// but fine for a short-lived subprocess (~2-5s).
let output = tokio::task::spawn_blocking(move || {
let mut cmd = std::process::Command::new(&resolved_acp);
if let Some(home) = default_agent_workdir() {
cmd.current_dir(home);
}
// Inject the same augmented PATH used for launched agents and CLI
// probes: managed Node/npm dirs, exe-parent sidecars, login-shell
// PATH, and (Windows) the inherited process PATH. login_shell_path()
// alone is always None on Windows, which left this child with no
// managed Node dirs — the ACP adapter's `.cmd` shims then failed with
// `'node' is not recognized` and the model dropdown stayed empty.
if let Some(ref path) = crate::managed_agents::readiness::cli_probe::augmented_path() {
cmd.env("PATH", path);
}
cmd.arg("models")
.arg("--json")
.env("BUZZ_ACP_AGENT_COMMAND", &agent_command)
.env("BUZZ_ACP_AGENT_ARGS", agent_args.join(","));
if let Some(meta) = known_acp_runtime(&agent_command) {
for (key, value) in meta.default_env {
if std::env::var(key).is_err() {
cmd.env(key, value);
}
}
}
// Mirror runtime spawn: internal builds may bake provider/model
// defaults. User-provided env below still wins.
build_buzz_agent_provider_defaults(&mut cmd);
// User env layering — written LAST so it overrides any Buzz-set env above.
for (k, v) in &merged_env {
cmd.env(k, v);
}
// Demo identity is authoritative and must win over ambient/user env.
crate::build_identity::apply_demo_config_home(&mut cmd)?;
crate::managed_agents::configure_runtime_cli(&mut cmd, known_acp_runtime(&agent_command));
crate::util::configure_no_window(&mut cmd);
cmd.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.output()
.map_err(|e| format!("failed to spawn buzz-acp models: {e}"))
})
.await
.map_err(|e| format!("model discovery task failed: {e}"))?
.map_err(|e: String| e)?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
// Scrub any user-supplied env values before surfacing stderr to
// the frontend — persona/agent env_vars may carry API keys that
// a failing child process echoed back.
let stderr_redacted = redact_env_values_in(stderr.as_ref(), &env_for_redaction);
return Err(format!(
"buzz-acp models failed (exit {}): {stderr_redacted}",
output.status.code().unwrap_or(-1)
));
}
let raw: serde_json::Value = serde_json::from_slice(&output.stdout)
.map_err(|e| format!("failed to parse model JSON: {e}"))?;
Ok(normalize_agent_models(&raw, persisted_model))
}