mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
fix(desktop): rename SPROUT_ env vars to BUZZ_ for child agent processes (#971)
This commit is contained in:
@@ -83,8 +83,8 @@ pub async fn get_agent_models(
|
||||
}
|
||||
cmd.arg("models")
|
||||
.arg("--json")
|
||||
.env("SPROUT_ACP_AGENT_COMMAND", &agent_command)
|
||||
.env("SPROUT_ACP_AGENT_ARGS", agent_args.join(","));
|
||||
.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() {
|
||||
|
||||
@@ -152,8 +152,8 @@ const KNOWN_ACP_RUNTIMES: &[KnownAcpRuntime] = &[
|
||||
adapter_install_hint: "",
|
||||
skill_dir: None,
|
||||
supports_acp_model_switching: true,
|
||||
model_env_var: Some("SPROUT_AGENT_MODEL"),
|
||||
provider_env_var: Some("SPROUT_AGENT_PROVIDER"),
|
||||
model_env_var: Some("BUZZ_AGENT_MODEL"),
|
||||
provider_env_var: Some("BUZZ_AGENT_PROVIDER"),
|
||||
provider_locked: false,
|
||||
default_env: &[],
|
||||
},
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
//! Precedence: desktop parent env < persona env < agent env (last wins on
|
||||
//! key collision). See `runtime::spawn_agent_child`.
|
||||
//!
|
||||
//! A small set of *reserved* keys — Sprout's identity and secrets — are
|
||||
//! A small set of *reserved* keys — Buzz's identity and secrets — are
|
||||
//! rejected at save time and stripped at runtime so a typo or malicious
|
||||
//! value can't swap the agent's nsec. Behavior knobs (GOOSE_MODE,
|
||||
//! SPROUT_TOOLSETS, SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT, …) remain
|
||||
//! BUZZ_TOOLSETS, BUZZ_ACP_MODEL, BUZZ_ACP_SYSTEM_PROMPT, …) remain
|
||||
//! freely overridable — those have dedicated UI fields, but power users
|
||||
//! may want to bypass them.
|
||||
|
||||
@@ -28,8 +28,8 @@ use std::collections::BTreeMap;
|
||||
pub(crate) const DERIVED_PROVIDER_MODEL_ENV_KEYS: &[&str] = &[
|
||||
"GOOSE_MODEL",
|
||||
"GOOSE_PROVIDER",
|
||||
"SPROUT_AGENT_MODEL",
|
||||
"SPROUT_AGENT_PROVIDER",
|
||||
"BUZZ_AGENT_MODEL",
|
||||
"BUZZ_AGENT_PROVIDER",
|
||||
];
|
||||
|
||||
/// Returns `true` if `key` is a derived provider/model env key that should be
|
||||
@@ -68,32 +68,32 @@ pub(crate) fn filter_derived_provider_model_env_vars(
|
||||
/// example), or redirect the agent to an attacker-controlled relay.
|
||||
///
|
||||
/// This list is deliberately narrow — it only covers keys with security
|
||||
/// implications. Behavior knobs (GOOSE_MODE, SPROUT_TOOLSETS,
|
||||
/// SPROUT_ACP_MODEL, SPROUT_ACP_SYSTEM_PROMPT, …) remain freely
|
||||
/// implications. Behavior knobs (GOOSE_MODE, BUZZ_TOOLSETS,
|
||||
/// BUZZ_ACP_MODEL, BUZZ_ACP_SYSTEM_PROMPT, …) remain freely
|
||||
/// overridable; those have dedicated UI fields but power users may want
|
||||
/// to bypass them.
|
||||
pub(crate) const RESERVED_ENV_KEYS: &[&str] = &[
|
||||
// Identity / secrets.
|
||||
"SPROUT_PRIVATE_KEY",
|
||||
"BUZZ_PRIVATE_KEY",
|
||||
"NOSTR_PRIVATE_KEY",
|
||||
"SPROUT_AUTH_TAG",
|
||||
"SPROUT_API_TOKEN",
|
||||
"SPROUT_ACP_PRIVATE_KEY",
|
||||
"SPROUT_ACP_API_TOKEN",
|
||||
"BUZZ_AUTH_TAG",
|
||||
"BUZZ_API_TOKEN",
|
||||
"BUZZ_ACP_PRIVATE_KEY",
|
||||
"BUZZ_ACP_API_TOKEN",
|
||||
// Relay URL: overriding would let a malicious config redirect the
|
||||
// agent to an attacker-controlled relay.
|
||||
"SPROUT_RELAY_URL",
|
||||
"BUZZ_RELAY_URL",
|
||||
// Code-execution surface: overriding would let the user run arbitrary
|
||||
// binaries/args as the agent process.
|
||||
"SPROUT_ACP_AGENT_COMMAND",
|
||||
"SPROUT_ACP_AGENT_ARGS",
|
||||
"SPROUT_ACP_MCP_COMMAND",
|
||||
"BUZZ_ACP_AGENT_COMMAND",
|
||||
"BUZZ_ACP_AGENT_ARGS",
|
||||
"BUZZ_ACP_MCP_COMMAND",
|
||||
// Security gates: respond-to mode + allowlist + legacy owner-only
|
||||
// fallback. Overriding would make the running agent's gate diverge
|
||||
// from the saved/UI-visible settings.
|
||||
"SPROUT_ACP_RESPOND_TO",
|
||||
"SPROUT_ACP_RESPOND_TO_ALLOWLIST",
|
||||
"SPROUT_ACP_AGENT_OWNER",
|
||||
"BUZZ_ACP_RESPOND_TO",
|
||||
"BUZZ_ACP_RESPOND_TO_ALLOWLIST",
|
||||
"BUZZ_ACP_AGENT_OWNER",
|
||||
];
|
||||
|
||||
pub(crate) fn is_reserved_env_key(key: &str) -> bool {
|
||||
@@ -109,7 +109,7 @@ pub(crate) fn is_reserved_env_key(key: &str) -> bool {
|
||||
/// where `getenv("FOO")` then matches whatever comes after the first
|
||||
/// `=`. That means a key like `SPROUT_AUTH_TAG=x` with value `forged`
|
||||
/// lands as `SPROUT_AUTH_TAG=x=forged` in the child env and
|
||||
/// `getenv("SPROUT_AUTH_TAG")` returns `"x=forged"` — a full reserved-
|
||||
/// `getenv("BUZZ_AUTH_TAG")` returns `"x=forged"` — a full reserved-
|
||||
/// key bypass. Rejecting non-POSIX keys closes this hole at the
|
||||
/// boundary where the input enters the system.
|
||||
pub(crate) fn is_well_formed_env_key(key: &str) -> bool {
|
||||
|
||||
@@ -90,11 +90,11 @@ fn merged_env_strips_reserved_keys_from_persona() {
|
||||
// persona data (e.g. older record from before validation existed),
|
||||
// it must be stripped before reaching the child process.
|
||||
let persona = map(&[
|
||||
("SPROUT_PRIVATE_KEY", "nsec1evil"),
|
||||
("BUZZ_PRIVATE_KEY", "nsec1evil"),
|
||||
("ANTHROPIC_API_KEY", "ok"),
|
||||
]);
|
||||
let merged = merged_user_env(&persona, &BTreeMap::new());
|
||||
assert!(!merged.contains_key("SPROUT_PRIVATE_KEY"));
|
||||
assert!(!merged.contains_key("BUZZ_PRIVATE_KEY"));
|
||||
assert_eq!(
|
||||
merged.get("ANTHROPIC_API_KEY").map(String::as_str),
|
||||
Some("ok")
|
||||
@@ -105,12 +105,12 @@ fn merged_env_strips_reserved_keys_from_persona() {
|
||||
fn merged_env_strips_reserved_keys_from_agent() {
|
||||
let agent = map(&[
|
||||
("NOSTR_PRIVATE_KEY", "nsec1evil"),
|
||||
("SPROUT_AUTH_TAG", "{}"),
|
||||
("BUZZ_AUTH_TAG", "{}"),
|
||||
("FOO", "1"),
|
||||
]);
|
||||
let merged = merged_user_env(&BTreeMap::new(), &agent);
|
||||
assert!(!merged.contains_key("NOSTR_PRIVATE_KEY"));
|
||||
assert!(!merged.contains_key("SPROUT_AUTH_TAG"));
|
||||
assert!(!merged.contains_key("BUZZ_AUTH_TAG"));
|
||||
assert_eq!(merged.get("FOO").map(String::as_str), Some("1"));
|
||||
assert_eq!(merged.len(), 1);
|
||||
}
|
||||
@@ -118,9 +118,9 @@ fn merged_env_strips_reserved_keys_from_agent() {
|
||||
#[test]
|
||||
fn merged_env_strips_reserved_case_insensitive() {
|
||||
// Unix env vars are case-sensitive at the syscall level, but we
|
||||
// refuse close-typo variants too — a lowercase `sprout_private_key`
|
||||
// refuse close-typo variants too — a lowercase `buzz_private_key`
|
||||
// is almost certainly a footgun, not a legitimate use.
|
||||
let agent = map(&[("sprout_private_key", "x"), ("Sprout_Auth_Tag", "y")]);
|
||||
let agent = map(&[("buzz_private_key", "x"), ("Buzz_Auth_Tag", "y")]);
|
||||
let merged = merged_user_env(&BTreeMap::new(), &agent);
|
||||
assert!(merged.is_empty());
|
||||
}
|
||||
@@ -132,16 +132,16 @@ fn is_reserved_recognises_full_list() {
|
||||
}
|
||||
assert!(!is_reserved_env_key("GOOSE_MODE"));
|
||||
assert!(!is_reserved_env_key("ANTHROPIC_API_KEY"));
|
||||
assert!(!is_reserved_env_key("SPROUT_ACP_MODEL")); // behavior knob
|
||||
assert!(!is_reserved_env_key("SPROUT_TOOLSETS"));
|
||||
assert!(!is_reserved_env_key("BUZZ_ACP_MODEL")); // behavior knob
|
||||
assert!(!is_reserved_env_key("BUZZ_TOOLSETS"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reserved_keys_include_agent_owner_for_legacy_records() {
|
||||
// Legacy records without auth_tag fall back to SPROUT_ACP_AGENT_OWNER
|
||||
// Legacy records without auth_tag fall back to BUZZ_ACP_AGENT_OWNER
|
||||
// to enforce the respond-to gate. Must not be user-overridable.
|
||||
assert!(is_reserved_env_key("SPROUT_ACP_AGENT_OWNER"));
|
||||
let agent = map(&[("SPROUT_ACP_AGENT_OWNER", "imposter")]);
|
||||
assert!(is_reserved_env_key("BUZZ_ACP_AGENT_OWNER"));
|
||||
let agent = map(&[("BUZZ_ACP_AGENT_OWNER", "imposter")]);
|
||||
let merged = merged_user_env(&BTreeMap::new(), &agent);
|
||||
assert!(merged.is_empty());
|
||||
}
|
||||
@@ -151,7 +151,7 @@ fn reserved_keys_include_respond_to_gate() {
|
||||
// Respond-to mode + allowlist control who the agent answers.
|
||||
// Overriding via env_vars would let the running agent answer
|
||||
// anyone even when the UI/record says owner-only.
|
||||
for key in ["SPROUT_ACP_RESPOND_TO", "SPROUT_ACP_RESPOND_TO_ALLOWLIST"] {
|
||||
for key in ["BUZZ_ACP_RESPOND_TO", "BUZZ_ACP_RESPOND_TO_ALLOWLIST"] {
|
||||
assert!(is_reserved_env_key(key), "{key} should be reserved");
|
||||
let agent = map(&[(key, "anyone")]);
|
||||
let merged = merged_user_env(&BTreeMap::new(), &agent);
|
||||
@@ -164,9 +164,9 @@ fn reserved_keys_include_code_execution_surface() {
|
||||
// The agent/MCP command + args are what Sprout actually exec's.
|
||||
// Overriding lets the user run arbitrary code as the agent.
|
||||
for key in [
|
||||
"SPROUT_ACP_AGENT_COMMAND",
|
||||
"SPROUT_ACP_AGENT_ARGS",
|
||||
"SPROUT_ACP_MCP_COMMAND",
|
||||
"BUZZ_ACP_AGENT_COMMAND",
|
||||
"BUZZ_ACP_AGENT_ARGS",
|
||||
"BUZZ_ACP_MCP_COMMAND",
|
||||
] {
|
||||
assert!(is_reserved_env_key(key), "{key} should be reserved");
|
||||
}
|
||||
@@ -176,8 +176,8 @@ fn reserved_keys_include_code_execution_surface() {
|
||||
fn reserved_keys_include_relay_url() {
|
||||
// Overriding the relay URL could redirect the agent to an
|
||||
// attacker-controlled relay.
|
||||
assert!(is_reserved_env_key("SPROUT_RELAY_URL"));
|
||||
let agent = map(&[("SPROUT_RELAY_URL", "ws://attacker.example")]);
|
||||
assert!(is_reserved_env_key("BUZZ_RELAY_URL"));
|
||||
let agent = map(&[("BUZZ_RELAY_URL", "ws://attacker.example")]);
|
||||
let merged = merged_user_env(&BTreeMap::new(), &agent);
|
||||
assert!(merged.is_empty());
|
||||
}
|
||||
@@ -192,21 +192,21 @@ fn validate_keys_accepts_normal_env() {
|
||||
|
||||
#[test]
|
||||
fn validate_keys_rejects_reserved() {
|
||||
let env = map(&[("SPROUT_PRIVATE_KEY", "nsec1evil")]);
|
||||
let env = map(&[("BUZZ_PRIVATE_KEY", "nsec1evil")]);
|
||||
let err = validate_user_env_keys(&env).unwrap_err();
|
||||
assert!(err.contains("SPROUT_PRIVATE_KEY"), "got: {err}");
|
||||
assert!(err.contains("BUZZ_PRIVATE_KEY"), "got: {err}");
|
||||
assert!(err.contains("reserved"), "got: {err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_keys_lists_all_reserved_keys_found() {
|
||||
let env = map(&[
|
||||
("SPROUT_PRIVATE_KEY", "x"),
|
||||
("BUZZ_PRIVATE_KEY", "x"),
|
||||
("NOSTR_PRIVATE_KEY", "y"),
|
||||
("ANTHROPIC_API_KEY", "ok"),
|
||||
]);
|
||||
let err = validate_user_env_keys(&env).unwrap_err();
|
||||
assert!(err.contains("SPROUT_PRIVATE_KEY"));
|
||||
assert!(err.contains("BUZZ_PRIVATE_KEY"));
|
||||
assert!(err.contains("NOSTR_PRIVATE_KEY"));
|
||||
}
|
||||
|
||||
@@ -228,8 +228,8 @@ fn validate_keys_accepts_empty_map() {
|
||||
// Rust's `Command::env(k, v)` will accept a key containing `=` and
|
||||
// pass it straight into the child's environ block, where
|
||||
// `getenv("PREFIX")` matches anything after the first `=`. Concretely:
|
||||
// `c.env("SPROUT_AUTH_TAG=x", "forged")` results in the child seeing
|
||||
// `SPROUT_AUTH_TAG=x=forged` and `getenv("SPROUT_AUTH_TAG") == "x=forged"`.
|
||||
// `c.env("BUZZ_AUTH_TAG=x", "forged")` results in the child seeing
|
||||
// `BUZZ_AUTH_TAG=x=forged` and `getenv("BUZZ_AUTH_TAG") == "x=forged"`.
|
||||
// That bypasses our reserved-key check, which compares strings.
|
||||
// These tests pin the fix at the validator boundary.
|
||||
|
||||
@@ -250,19 +250,19 @@ fn is_well_formed_accepts_posix_keys() {
|
||||
#[test]
|
||||
fn is_well_formed_rejects_malformed_keys() {
|
||||
for key in [
|
||||
"", // empty
|
||||
"=", // bare equals
|
||||
"SPROUT_AUTH_TAG=x", // =-in-key bypass
|
||||
"SPROUT_PRIVATE_KEY=", // trailing equals
|
||||
"FOO BAR", // space
|
||||
" FOO", // leading whitespace
|
||||
"FOO\nBAR", // newline
|
||||
"FOO\0BAR", // NUL
|
||||
"123_LEADING_DIGIT", // POSIX forbids leading digit
|
||||
"FOO-BAR", // hyphen
|
||||
"FOO.BAR", // dot
|
||||
"FOO/BAR", // slash
|
||||
"ünicode_key", // non-ASCII
|
||||
"", // empty
|
||||
"=", // bare equals
|
||||
"BUZZ_AUTH_TAG=x", // =-in-key bypass
|
||||
"BUZZ_PRIVATE_KEY=", // trailing equals
|
||||
"FOO BAR", // space
|
||||
" FOO", // leading whitespace
|
||||
"FOO\nBAR", // newline
|
||||
"FOO\0BAR", // NUL
|
||||
"123_LEADING_DIGIT", // POSIX forbids leading digit
|
||||
"FOO-BAR", // hyphen
|
||||
"FOO.BAR", // dot
|
||||
"FOO/BAR", // slash
|
||||
"ünicode_key", // non-ASCII
|
||||
] {
|
||||
assert!(!is_well_formed_env_key(key), "{key:?} should be malformed");
|
||||
}
|
||||
@@ -270,15 +270,15 @@ fn is_well_formed_rejects_malformed_keys() {
|
||||
|
||||
#[test]
|
||||
fn validate_keys_rejects_equals_in_key_bypass() {
|
||||
// The actual exploit: `SPROUT_AUTH_TAG=x` smuggles a value past
|
||||
// The actual exploit: `BUZZ_AUTH_TAG=x` smuggles a value past
|
||||
// the reserved-key string compare and into the child's environ.
|
||||
let env = map(&[("SPROUT_AUTH_TAG=x", "forged")]);
|
||||
let env = map(&[("BUZZ_AUTH_TAG=x", "forged")]);
|
||||
let err = validate_user_env_keys(&env).unwrap_err();
|
||||
assert!(err.contains("[A-Za-z_]"), "got: {err}");
|
||||
// After P2 fix the key is truncated at `=` in the error to avoid
|
||||
// surfacing pasted secrets — only the prefix should appear, with an
|
||||
// ellipsis marking that we elided trailing content.
|
||||
assert!(err.contains("SPROUT_AUTH_TAG"), "got: {err}");
|
||||
assert!(err.contains("BUZZ_AUTH_TAG"), "got: {err}");
|
||||
assert!(err.contains('…'), "expected ellipsis marker: {err}");
|
||||
assert!(!err.contains("=x"), "leak of value past `=`: {err}");
|
||||
}
|
||||
@@ -296,7 +296,7 @@ fn validate_keys_reports_malformed_before_reserved() {
|
||||
// the way that other key is reserved" — they've got a typo to fix
|
||||
// first. Ordering is a UX detail but pinning it stops the message
|
||||
// from churning.
|
||||
let env = map(&[("SPROUT_AUTH_TAG=x", "v"), ("SPROUT_PRIVATE_KEY", "v")]);
|
||||
let env = map(&[("BUZZ_AUTH_TAG=x", "v"), ("BUZZ_PRIVATE_KEY", "v")]);
|
||||
let err = validate_user_env_keys(&env).unwrap_err();
|
||||
assert!(err.contains("[A-Za-z_]"), "got: {err}");
|
||||
assert!(!err.contains("reserved"), "got: {err}");
|
||||
@@ -307,12 +307,12 @@ fn merged_env_drops_malformed_keys() {
|
||||
// Defense in depth: on-disk records written before the validator
|
||||
// tightened must not be able to smuggle reserved keys through.
|
||||
let agent = map(&[
|
||||
("SPROUT_AUTH_TAG=x", "forged"),
|
||||
("BUZZ_AUTH_TAG=x", "forged"),
|
||||
("FOO=bar", "v"),
|
||||
("LEGIT", "ok"),
|
||||
]);
|
||||
let merged = merged_user_env(&BTreeMap::new(), &agent);
|
||||
assert!(!merged.contains_key("SPROUT_AUTH_TAG=x"));
|
||||
assert!(!merged.contains_key("BUZZ_AUTH_TAG=x"));
|
||||
assert!(!merged.contains_key("FOO=bar"));
|
||||
assert_eq!(merged.get("LEGIT").map(String::as_str), Some("ok"));
|
||||
assert_eq!(merged.len(), 1);
|
||||
@@ -420,7 +420,7 @@ fn merged_env_drops_oversize_value() {
|
||||
// ── derived provider/model key filter ──────────────────────────────
|
||||
//
|
||||
// Pack import must strip derived env keys (GOOSE_MODEL, GOOSE_PROVIDER,
|
||||
// SPROUT_AGENT_MODEL, SPROUT_AGENT_PROVIDER) so they don't shadow the
|
||||
// BUZZ_AGENT_MODEL, BUZZ_AGENT_PROVIDER) so they don't shadow the
|
||||
// structured PersonaRecord.model / PersonaRecord.provider fields after
|
||||
// the user edits them in the UI.
|
||||
|
||||
@@ -438,8 +438,8 @@ fn is_derived_key_matches_all_known_keys() {
|
||||
fn is_derived_key_is_case_insensitive() {
|
||||
assert!(is_derived_provider_model_key("goose_model"));
|
||||
assert!(is_derived_provider_model_key("Goose_Provider"));
|
||||
assert!(is_derived_provider_model_key("sprout_agent_model"));
|
||||
assert!(is_derived_provider_model_key("SPROUT_AGENT_PROVIDER"));
|
||||
assert!(is_derived_provider_model_key("buzz_agent_model"));
|
||||
assert!(is_derived_provider_model_key("BUZZ_AGENT_PROVIDER"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -447,7 +447,7 @@ fn is_derived_key_does_not_match_unrelated_keys() {
|
||||
assert!(!is_derived_provider_model_key("GOOSE_TEMPERATURE"));
|
||||
assert!(!is_derived_provider_model_key("GOOSE_CONTEXT_LIMIT"));
|
||||
assert!(!is_derived_provider_model_key("ANTHROPIC_API_KEY"));
|
||||
assert!(!is_derived_provider_model_key("SPROUT_PRIVATE_KEY"));
|
||||
assert!(!is_derived_provider_model_key("BUZZ_PRIVATE_KEY"));
|
||||
assert!(!is_derived_provider_model_key("MODEL"));
|
||||
assert!(!is_derived_provider_model_key("PROVIDER"));
|
||||
}
|
||||
@@ -460,8 +460,8 @@ fn filter_derived_strips_provider_model_keys_preserves_rest() {
|
||||
"claude-sonnet-4-20250514".to_string(),
|
||||
),
|
||||
("GOOSE_PROVIDER".to_string(), "anthropic".to_string()),
|
||||
("SPROUT_AGENT_MODEL".to_string(), "gpt-4o".to_string()),
|
||||
("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("BUZZ_AGENT_MODEL".to_string(), "gpt-4o".to_string()),
|
||||
("BUZZ_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("GOOSE_TEMPERATURE".to_string(), "0.7".to_string()),
|
||||
("ANTHROPIC_API_KEY".to_string(), "sk-test".to_string()),
|
||||
];
|
||||
|
||||
@@ -37,7 +37,7 @@ fn relay_mesh_model_id_from_env(record: &ManagedAgentRecord) -> Option<String> {
|
||||
if base_url.trim_end_matches('/') != RELAY_MESH_API_BASE_URL {
|
||||
return None;
|
||||
}
|
||||
let provider = record.env_vars.get("SPROUT_AGENT_PROVIDER")?.trim();
|
||||
let provider = record.env_vars.get("BUZZ_AGENT_PROVIDER")?.trim();
|
||||
if provider != "openai" {
|
||||
return None;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ mod tests {
|
||||
fn relay_mesh_model_id_detects_mesh_preset_env() {
|
||||
let mut rec = fixture();
|
||||
rec.env_vars = BTreeMap::from([
|
||||
("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("BUZZ_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
(
|
||||
"OPENAI_COMPAT_BASE_URL".to_string(),
|
||||
"http://127.0.0.1:9337/v1/".to_string(),
|
||||
@@ -125,7 +125,7 @@ mod tests {
|
||||
fn relay_mesh_model_id_ignores_non_mesh_openai_env() {
|
||||
let mut rec = fixture();
|
||||
rec.env_vars = BTreeMap::from([
|
||||
("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("BUZZ_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
(
|
||||
"OPENAI_COMPAT_BASE_URL".to_string(),
|
||||
"https://api.openai.com/v1".to_string(),
|
||||
@@ -141,7 +141,7 @@ mod tests {
|
||||
fn relay_mesh_model_id_ignores_user_openai_on_same_local_port() {
|
||||
let mut rec = fixture();
|
||||
rec.env_vars = BTreeMap::from([
|
||||
("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("BUZZ_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
(
|
||||
"OPENAI_COMPAT_BASE_URL".to_string(),
|
||||
"http://127.0.0.1:9337/v1".to_string(),
|
||||
@@ -181,7 +181,7 @@ mod tests {
|
||||
model_ref: "typed-model".to_string(),
|
||||
});
|
||||
rec.env_vars = BTreeMap::from([
|
||||
("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("BUZZ_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
(
|
||||
"OPENAI_COMPAT_BASE_URL".to_string(),
|
||||
"http://127.0.0.1:9337/v1".to_string(),
|
||||
@@ -202,7 +202,7 @@ mod tests {
|
||||
let mut rec = fixture();
|
||||
rec.relay_mesh = None;
|
||||
rec.env_vars = BTreeMap::from([
|
||||
("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("BUZZ_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
(
|
||||
"OPENAI_COMPAT_BASE_URL".to_string(),
|
||||
"http://127.0.0.1:9337/v1".to_string(),
|
||||
|
||||
@@ -116,7 +116,7 @@ pub(crate) fn process_belongs_to_us(_pid: u32) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// The value stamped into the `SPROUT_MANAGED_AGENT` env var of every agent we
|
||||
/// The value stamped into the `BUZZ_MANAGED_AGENT` env var of every agent we
|
||||
/// spawn, identifying *which* desktop instance owns it. We use the app's bundle
|
||||
/// identifier (`xyz.block.sprout.app` for release, `xyz.block.sprout.app.dev`
|
||||
/// for `just dev`) because it is stable across restarts — a relaunched dev
|
||||
@@ -128,15 +128,15 @@ pub(crate) fn current_instance_id(app: &AppHandle) -> String {
|
||||
app.config().identifier.clone()
|
||||
}
|
||||
|
||||
/// Build the full `SPROUT_MANAGED_AGENT=<instance-id>` env entry we match
|
||||
/// Build the full `BUZZ_MANAGED_AGENT=<instance-id>` env entry we match
|
||||
/// against when scanning processes. Kept here so the spawn stamp and the sweep
|
||||
/// matcher can never drift apart.
|
||||
fn sprout_marker_entry(instance_id: &str) -> Vec<u8> {
|
||||
format!("SPROUT_MANAGED_AGENT={instance_id}").into_bytes()
|
||||
format!("BUZZ_MANAGED_AGENT={instance_id}").into_bytes()
|
||||
}
|
||||
|
||||
/// Check if a running process is one of *our* managed agents: it must carry
|
||||
/// `SPROUT_MANAGED_AGENT=<instance_id>` in its environment, where `instance_id`
|
||||
/// `BUZZ_MANAGED_AGENT=<instance_id>` in its environment, where `instance_id`
|
||||
/// is this desktop instance's id. A process stamped with a *different* instance
|
||||
/// id belongs to another live Sprout app and must never be reaped here.
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -398,7 +398,7 @@ const _: () = assert!(std::mem::size_of::<BSDInfo>() == 136);
|
||||
const PROC_PIDTBSDINFO: libc::c_int = 3;
|
||||
|
||||
/// Enumerate all processes on the system owned by the current user and kill any
|
||||
/// agent binary stamped with *this* instance's `SPROUT_MANAGED_AGENT` marker
|
||||
/// agent binary stamped with *this* instance's `BUZZ_MANAGED_AGENT` marker
|
||||
/// (`instance_id`) that isn't in `skip_pids`. This catches orphans that escaped
|
||||
/// PID-file-based cleanup (e.g. agent workers spawned with their own process
|
||||
/// group whose parent harness already exited and had its PID file removed),
|
||||
@@ -762,11 +762,11 @@ fn buffer_contains_identifier(buf: &[u8], id: &[u8]) -> bool {
|
||||
})
|
||||
}
|
||||
|
||||
/// Extract the `SPROUT_MANAGED_AGENT` value from a process's environment.
|
||||
/// Extract the `BUZZ_MANAGED_AGENT` value from a process's environment.
|
||||
/// Returns `None` if the process doesn't have the marker or can't be read.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn extract_sprout_marker_value(pid: u32) -> Option<String> {
|
||||
let prefix = b"SPROUT_MANAGED_AGENT=";
|
||||
let prefix = b"BUZZ_MANAGED_AGENT=";
|
||||
|
||||
let mut mib: [libc::c_int; 3] = [libc::CTL_KERN, libc::KERN_PROCARGS2, pid as libc::c_int];
|
||||
let mut buf_size: libc::size_t = 0;
|
||||
@@ -843,7 +843,7 @@ fn extract_sprout_marker_value(pid: u32) -> Option<String> {
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
fn extract_sprout_marker_value(pid: u32) -> Option<String> {
|
||||
let prefix = b"SPROUT_MANAGED_AGENT=";
|
||||
let prefix = b"BUZZ_MANAGED_AGENT=";
|
||||
let data = std::fs::read(format!("/proc/{pid}/environ")).ok()?;
|
||||
for entry in data.split(|&b| b == 0) {
|
||||
if entry.starts_with(prefix) {
|
||||
@@ -1022,7 +1022,7 @@ fn desktop_is_alive_for_instance(_instance_id: &str) -> bool {
|
||||
|
||||
/// Reap agent processes belonging to dead Sprout desktop instances.
|
||||
///
|
||||
/// Scans all user processes for `SPROUT_MANAGED_AGENT=*`, groups them by
|
||||
/// Scans all user processes for `BUZZ_MANAGED_AGENT=*`, groups them by
|
||||
/// instance ID, and for each foreign instance (≠ `our_instance_id`) checks
|
||||
/// whether a Sprout desktop binary is still alive for that instance. If not,
|
||||
/// all agents from that dead instance are reaped.
|
||||
@@ -1404,29 +1404,29 @@ pub(crate) fn build_respond_to_env(
|
||||
let mut remove: Vec<&'static str> = Vec::new();
|
||||
|
||||
set.push((
|
||||
"SPROUT_ACP_RESPOND_TO",
|
||||
"BUZZ_ACP_RESPOND_TO",
|
||||
record.respond_to.as_str().to_string(),
|
||||
));
|
||||
|
||||
if record.respond_to == super::types::RespondTo::Allowlist {
|
||||
set.push(("SPROUT_ACP_RESPOND_TO_ALLOWLIST", normalized.join(",")));
|
||||
set.push(("BUZZ_ACP_RESPOND_TO_ALLOWLIST", normalized.join(",")));
|
||||
} else {
|
||||
remove.push("SPROUT_ACP_RESPOND_TO_ALLOWLIST");
|
||||
remove.push("BUZZ_ACP_RESPOND_TO_ALLOWLIST");
|
||||
}
|
||||
|
||||
// Legacy fallback: agents created before NIP-OA lack `auth_tag`. Without
|
||||
// it the harness can't resolve the owner, and owner-dependent gate modes
|
||||
// would drop every event. Forwarding the workspace owner pubkey via
|
||||
// SPROUT_ACP_AGENT_OWNER keeps those records functional. Modern records
|
||||
// (`auth_tag = Some(...)`) use `SPROUT_AUTH_TAG` as before.
|
||||
// BUZZ_ACP_AGENT_OWNER keeps those records functional. Modern records
|
||||
// (`auth_tag = Some(...)`) use `BUZZ_AUTH_TAG` as before.
|
||||
if record.auth_tag.is_none() {
|
||||
if let Some(owner) = owner_hex {
|
||||
set.push(("SPROUT_ACP_AGENT_OWNER", owner.to_string()));
|
||||
set.push(("BUZZ_ACP_AGENT_OWNER", owner.to_string()));
|
||||
} else {
|
||||
remove.push("SPROUT_ACP_AGENT_OWNER");
|
||||
remove.push("BUZZ_ACP_AGENT_OWNER");
|
||||
}
|
||||
} else {
|
||||
remove.push("SPROUT_ACP_AGENT_OWNER");
|
||||
remove.push("BUZZ_ACP_AGENT_OWNER");
|
||||
}
|
||||
|
||||
Ok((set, remove))
|
||||
@@ -1535,16 +1535,16 @@ pub fn spawn_agent_child(
|
||||
command.env("PATH", path);
|
||||
}
|
||||
command.env("RUST_LOG", child_rust_log_filter());
|
||||
command.env("SPROUT_PRIVATE_KEY", &record.private_key_nsec);
|
||||
command.env("SPROUT_RELAY_URL", &record.relay_url);
|
||||
command.env("SPROUT_ACP_AGENT_COMMAND", &resolved_agent_command);
|
||||
command.env("SPROUT_ACP_AGENT_ARGS", agent_args.join(","));
|
||||
command.env("BUZZ_PRIVATE_KEY", &record.private_key_nsec);
|
||||
command.env("BUZZ_RELAY_URL", &record.relay_url);
|
||||
command.env("BUZZ_ACP_AGENT_COMMAND", &resolved_agent_command);
|
||||
command.env("BUZZ_ACP_AGENT_ARGS", agent_args.join(","));
|
||||
match &resolved_mcp_command {
|
||||
Some(mcp_cmd) => {
|
||||
command.env("SPROUT_ACP_MCP_COMMAND", mcp_cmd);
|
||||
command.env("BUZZ_ACP_MCP_COMMAND", mcp_cmd);
|
||||
}
|
||||
None => {
|
||||
command.env("SPROUT_ACP_MCP_COMMAND", "");
|
||||
command.env("BUZZ_ACP_MCP_COMMAND", "");
|
||||
}
|
||||
}
|
||||
// Enable MCP hook tools (_Stop, _PostCompact) for agents that need them.
|
||||
@@ -1553,23 +1553,23 @@ pub fn spawn_agent_child(
|
||||
if runtime_meta.is_some_and(|r| r.mcp_hooks) {
|
||||
command.env("MCP_HOOK_SERVERS", "*");
|
||||
}
|
||||
// Only emit SPROUT_ACP_IDLE_TIMEOUT when the user has explicitly set an
|
||||
// Only emit BUZZ_ACP_IDLE_TIMEOUT when the user has explicitly set an
|
||||
// override. When unset, the sprout-acp harness applies its own default
|
||||
// (see `DEFAULT_IDLE_TIMEOUT_SECS` in crates/sprout-acp/src/config.rs),
|
||||
// which is the single source of truth. The previously-emitted
|
||||
// `SPROUT_ACP_TURN_TIMEOUT` is deprecated upstream and was pinning every
|
||||
// `BUZZ_ACP_TURN_TIMEOUT` is deprecated upstream and was pinning every
|
||||
// agent to the desktop's stale default (320s), bypassing harness bumps.
|
||||
if let Some(idle) = record.idle_timeout_seconds {
|
||||
command.env("SPROUT_ACP_IDLE_TIMEOUT", idle.to_string());
|
||||
command.env("BUZZ_ACP_IDLE_TIMEOUT", idle.to_string());
|
||||
}
|
||||
|
||||
let max_dur = record
|
||||
.max_turn_duration_seconds
|
||||
.unwrap_or(super::types::DEFAULT_AGENT_MAX_TURN_DURATION_SECONDS);
|
||||
command.env("SPROUT_ACP_MAX_TURN_DURATION", max_dur.to_string());
|
||||
command.env("SPROUT_ACP_AGENTS", record.parallelism.to_string());
|
||||
command.env("SPROUT_ACP_MULTIPLE_EVENT_HANDLING", "owner-interrupt");
|
||||
command.env("SPROUT_ACP_DEDUP", "queue");
|
||||
command.env("BUZZ_ACP_MAX_TURN_DURATION", max_dur.to_string());
|
||||
command.env("BUZZ_ACP_AGENTS", record.parallelism.to_string());
|
||||
command.env("BUZZ_ACP_MULTIPLE_EVENT_HANDLING", "owner-interrupt");
|
||||
command.env("BUZZ_ACP_DEDUP", "queue");
|
||||
if let Some(meta) = runtime_meta {
|
||||
for (key, value) in meta.default_env {
|
||||
if std::env::var(key).is_err() {
|
||||
@@ -1580,8 +1580,8 @@ pub fn spawn_agent_child(
|
||||
if let (Some(team_dir), Some(persona_name)) =
|
||||
(&record.persona_team_dir, &record.persona_name_in_team)
|
||||
{
|
||||
command.env("SPROUT_ACP_PERSONA_PACK", team_dir);
|
||||
command.env("SPROUT_ACP_PERSONA_NAME", persona_name);
|
||||
command.env("BUZZ_ACP_PERSONA_PACK", team_dir);
|
||||
command.env("BUZZ_ACP_PERSONA_NAME", persona_name);
|
||||
}
|
||||
|
||||
// Resolve system prompt, model, and provider: the linked persona is the
|
||||
@@ -1598,14 +1598,14 @@ pub fn spawn_agent_child(
|
||||
);
|
||||
|
||||
if let Some(prompt) = &effective_prompt {
|
||||
command.env("SPROUT_ACP_SYSTEM_PROMPT", prompt);
|
||||
command.env("BUZZ_ACP_SYSTEM_PROMPT", prompt);
|
||||
} else {
|
||||
command.env_remove("SPROUT_ACP_SYSTEM_PROMPT");
|
||||
command.env_remove("BUZZ_ACP_SYSTEM_PROMPT");
|
||||
}
|
||||
if let Some(model) = &effective_model {
|
||||
command.env("SPROUT_ACP_MODEL", model);
|
||||
command.env("BUZZ_ACP_MODEL", model);
|
||||
} else {
|
||||
command.env_remove("SPROUT_ACP_MODEL");
|
||||
command.env_remove("BUZZ_ACP_MODEL");
|
||||
}
|
||||
if let Some(meta) = runtime_meta {
|
||||
for (key, value) in runtime_metadata_env_vars(
|
||||
@@ -1619,18 +1619,18 @@ pub fn spawn_agent_child(
|
||||
}
|
||||
}
|
||||
if let Some(toolsets) = &record.mcp_toolsets {
|
||||
command.env("SPROUT_TOOLSETS", toolsets);
|
||||
command.env("BUZZ_TOOLSETS", toolsets);
|
||||
} else {
|
||||
command.env("SPROUT_TOOLSETS", "default,canvas,forums,dms,media");
|
||||
command.env("BUZZ_TOOLSETS", "default,canvas,forums,dms,media");
|
||||
}
|
||||
command.env_remove("SPROUT_ACP_PRIVATE_KEY");
|
||||
command.env_remove("SPROUT_ACP_API_TOKEN");
|
||||
command.env_remove("SPROUT_API_TOKEN");
|
||||
command.env_remove("BUZZ_ACP_PRIVATE_KEY");
|
||||
command.env_remove("BUZZ_ACP_API_TOKEN");
|
||||
command.env_remove("BUZZ_API_TOKEN");
|
||||
|
||||
if let Some(ref auth_tag) = record.auth_tag {
|
||||
command.env("SPROUT_AUTH_TAG", auth_tag);
|
||||
command.env("BUZZ_AUTH_TAG", auth_tag);
|
||||
} else {
|
||||
command.env_remove("SPROUT_AUTH_TAG");
|
||||
command.env_remove("BUZZ_AUTH_TAG");
|
||||
}
|
||||
|
||||
// Inbound author gate: who is this agent allowed to respond to?
|
||||
@@ -1645,7 +1645,7 @@ pub fn spawn_agent_child(
|
||||
command.env_remove(key);
|
||||
}
|
||||
|
||||
command.env("SPROUT_ACP_RELAY_OBSERVER", "true");
|
||||
command.env("BUZZ_ACP_RELAY_OBSERVER", "true");
|
||||
|
||||
// ── Git credential helper for Sprout relay ──────────────────────────
|
||||
//
|
||||
@@ -1657,7 +1657,7 @@ pub fn spawn_agent_child(
|
||||
// filesystem writes) scoped to the relay's git URL so we don't
|
||||
// interfere with other remotes (e.g. GitHub).
|
||||
//
|
||||
// NOSTR_PRIVATE_KEY mirrors SPROUT_PRIVATE_KEY — keep in sync.
|
||||
// NOSTR_PRIVATE_KEY mirrors BUZZ_PRIVATE_KEY — keep in sync.
|
||||
if let Some(cred_helper) = resolve_command("git-credential-nostr") {
|
||||
let relay_http_url = crate::relay::relay_http_base_url(&record.relay_url);
|
||||
|
||||
@@ -1692,9 +1692,9 @@ pub fn spawn_agent_child(
|
||||
//
|
||||
// Precedence: desktop parent env < persona env_vars < agent env_vars.
|
||||
// These writes go LAST so user-provided values win over every Sprout-set
|
||||
// env above — EXCEPT reserved keys (SPROUT_PRIVATE_KEY, NOSTR_PRIVATE_KEY,
|
||||
// SPROUT_AUTH_TAG, SPROUT_API_TOKEN, SPROUT_ACP_PRIVATE_KEY,
|
||||
// SPROUT_ACP_API_TOKEN), which `merged_user_env` strips. Those carry
|
||||
// env above — EXCEPT reserved keys (BUZZ_PRIVATE_KEY, NOSTR_PRIVATE_KEY,
|
||||
// BUZZ_AUTH_TAG, BUZZ_API_TOKEN, BUZZ_ACP_PRIVATE_KEY,
|
||||
// BUZZ_ACP_API_TOKEN), which `merged_user_env` strips. Those carry
|
||||
// Sprout's identity and must never be GUI-overridable.
|
||||
// Fail closed on persona-lookup errors: persona env_vars carry API
|
||||
// credentials, so silently substituting an empty map would spawn an
|
||||
@@ -1710,7 +1710,7 @@ pub fn spawn_agent_child(
|
||||
// agents). Propagates automatically through the full tree (sprout-acp →
|
||||
// goose → MCP servers) because neither sprout-acp nor goose calls
|
||||
// env_clear().
|
||||
command.env("SPROUT_MANAGED_AGENT", current_instance_id(app));
|
||||
command.env("BUZZ_MANAGED_AGENT", current_instance_id(app));
|
||||
|
||||
// Spawn the harness in its own process group so we can kill the entire
|
||||
// tree (harness + MCP servers + agent subprocesses) on shutdown.
|
||||
|
||||
@@ -29,7 +29,7 @@ fn identifier_exact_match_with_quote_boundary() {
|
||||
#[test]
|
||||
fn identifier_match_with_null_boundary() {
|
||||
// In KERN_PROCARGS2, entries are null-delimited.
|
||||
let mut buf = b"SPROUT_MANAGED_AGENT=xyz.block.sprout.app.dev".to_vec();
|
||||
let mut buf = b"BUZZ_MANAGED_AGENT=xyz.block.sprout.app.dev".to_vec();
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(b"OTHER_VAR=value");
|
||||
let id = b"xyz.block.sprout.app.dev";
|
||||
@@ -71,7 +71,7 @@ fn marker_entry_is_namespaced_by_instance_id() {
|
||||
// release build's (`...app`) agents.
|
||||
assert_eq!(
|
||||
super::sprout_marker_entry("xyz.block.sprout.app"),
|
||||
b"SPROUT_MANAGED_AGENT=xyz.block.sprout.app".to_vec()
|
||||
b"BUZZ_MANAGED_AGENT=xyz.block.sprout.app".to_vec()
|
||||
);
|
||||
assert_ne!(
|
||||
super::sprout_marker_entry("xyz.block.sprout.app"),
|
||||
@@ -167,13 +167,13 @@ fn build_env_owner_only_sets_mode_and_removes_others() {
|
||||
let (set, remove) = build_respond_to_env(&rec, Some("owner")).unwrap();
|
||||
let set_map: std::collections::HashMap<_, _> = set.into_iter().collect();
|
||||
assert_eq!(
|
||||
set_map.get("SPROUT_ACP_RESPOND_TO").map(String::as_str),
|
||||
set_map.get("BUZZ_ACP_RESPOND_TO").map(String::as_str),
|
||||
Some("owner-only")
|
||||
);
|
||||
assert!(!set_map.contains_key("SPROUT_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
assert!(remove.contains(&"SPROUT_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
assert!(!set_map.contains_key("BUZZ_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
assert!(remove.contains(&"BUZZ_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
// auth_tag is present → no AGENT_OWNER fallback fires.
|
||||
assert!(remove.contains(&"SPROUT_ACP_AGENT_OWNER"));
|
||||
assert!(remove.contains(&"BUZZ_ACP_AGENT_OWNER"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -188,12 +188,12 @@ fn build_env_allowlist_sets_both_envs_and_joins() {
|
||||
let (set, _remove) = build_respond_to_env(&rec, Some("owner")).unwrap();
|
||||
let set_map: std::collections::HashMap<_, _> = set.into_iter().collect();
|
||||
assert_eq!(
|
||||
set_map.get("SPROUT_ACP_RESPOND_TO").map(String::as_str),
|
||||
set_map.get("BUZZ_ACP_RESPOND_TO").map(String::as_str),
|
||||
Some("allowlist")
|
||||
);
|
||||
assert_eq!(
|
||||
set_map
|
||||
.get("SPROUT_ACP_RESPOND_TO_ALLOWLIST")
|
||||
.get("BUZZ_ACP_RESPOND_TO_ALLOWLIST")
|
||||
.map(String::as_str),
|
||||
Some(format!("{a},{b}").as_str()),
|
||||
);
|
||||
@@ -205,11 +205,11 @@ fn build_env_anyone_omits_allowlist_var() {
|
||||
let (set, remove) = build_respond_to_env(&rec, Some("owner")).unwrap();
|
||||
let set_map: std::collections::HashMap<_, _> = set.into_iter().collect();
|
||||
assert_eq!(
|
||||
set_map.get("SPROUT_ACP_RESPOND_TO").map(String::as_str),
|
||||
set_map.get("BUZZ_ACP_RESPOND_TO").map(String::as_str),
|
||||
Some("anyone")
|
||||
);
|
||||
assert!(!set_map.contains_key("SPROUT_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
assert!(remove.contains(&"SPROUT_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
assert!(!set_map.contains_key("BUZZ_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
assert!(remove.contains(&"BUZZ_ACP_RESPOND_TO_ALLOWLIST"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -218,10 +218,10 @@ fn build_env_legacy_record_without_auth_tag_emits_agent_owner() {
|
||||
let (set, remove) = build_respond_to_env(&rec, Some("ownerhex")).unwrap();
|
||||
let set_map: std::collections::HashMap<_, _> = set.into_iter().collect();
|
||||
assert_eq!(
|
||||
set_map.get("SPROUT_ACP_AGENT_OWNER").map(String::as_str),
|
||||
set_map.get("BUZZ_ACP_AGENT_OWNER").map(String::as_str),
|
||||
Some("ownerhex")
|
||||
);
|
||||
assert!(!remove.contains(&"SPROUT_ACP_AGENT_OWNER"));
|
||||
assert!(!remove.contains(&"BUZZ_ACP_AGENT_OWNER"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -230,7 +230,7 @@ fn build_env_legacy_record_without_owner_hex_removes_agent_owner() {
|
||||
// env var from the parent.
|
||||
let rec = fixture(RespondTo::OwnerOnly, vec![], None);
|
||||
let (_set, remove) = build_respond_to_env(&rec, None).unwrap();
|
||||
assert!(remove.contains(&"SPROUT_ACP_AGENT_OWNER"));
|
||||
assert!(remove.contains(&"BUZZ_ACP_AGENT_OWNER"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -377,8 +377,8 @@ fn runtime_metadata_env_vars_injects_model_even_with_acp_model_switching() {
|
||||
// sprout-agent has supports_acp_model_switching=true but we still inject
|
||||
// the model env var because ACP model switching is post-bootstrap
|
||||
let vars = runtime_metadata_env_vars(
|
||||
Some("SPROUT_AGENT_MODEL"),
|
||||
Some("SPROUT_AGENT_PROVIDER"),
|
||||
Some("BUZZ_AGENT_MODEL"),
|
||||
Some("BUZZ_AGENT_PROVIDER"),
|
||||
false,
|
||||
Some("goose-claude-4-6-opus"),
|
||||
Some("databricks"),
|
||||
@@ -386,8 +386,8 @@ fn runtime_metadata_env_vars_injects_model_even_with_acp_model_switching() {
|
||||
assert_eq!(
|
||||
vars,
|
||||
vec![
|
||||
("SPROUT_AGENT_MODEL", "goose-claude-4-6-opus"),
|
||||
("SPROUT_AGENT_PROVIDER", "databricks"),
|
||||
("BUZZ_AGENT_MODEL", "goose-claude-4-6-opus"),
|
||||
("BUZZ_AGENT_PROVIDER", "databricks"),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ use serde::{Deserialize, Serialize};
|
||||
const DEFAULT_MESH_API_PORT: u16 = 9337;
|
||||
const DEFAULT_MESH_CONSOLE_PORT: u16 = 3131;
|
||||
const MESH_STATUS_KIND: u64 = 30_621;
|
||||
const MESH_API_PORT_ENV: &str = "SPROUT_MESH_API_PORT";
|
||||
const MESH_CONSOLE_PORT_ENV: &str = "SPROUT_MESH_CONSOLE_PORT";
|
||||
const MESH_API_PORT_ENV: &str = "BUZZ_MESH_API_PORT";
|
||||
const MESH_CONSOLE_PORT_ENV: &str = "BUZZ_MESH_CONSOLE_PORT";
|
||||
const RELAY_MESH_API_KEY_PLACEHOLDER: &str = "sprout-mesh-local";
|
||||
/// ACP provider relay-mesh agents run on. Sources of truth for its command +
|
||||
/// MCP live in the runtime catalog (`known_acp_runtime_exact`); these are
|
||||
|
||||
@@ -52,7 +52,7 @@ fn agent_preset_runs_on_sprout_agent_not_goose() {
|
||||
assert_eq!(
|
||||
preset
|
||||
.env_vars
|
||||
.get("SPROUT_AGENT_PROVIDER")
|
||||
.get("BUZZ_AGENT_PROVIDER")
|
||||
.map(String::as_str),
|
||||
Some("openai")
|
||||
);
|
||||
|
||||
@@ -53,7 +53,7 @@ pub fn agent_preset(request: MeshAgentPresetRequest) -> Result<MeshAgentPreset,
|
||||
mcp_command,
|
||||
model: model.to_string(),
|
||||
env_vars: BTreeMap::from([
|
||||
("SPROUT_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
("BUZZ_AGENT_PROVIDER".to_string(), "openai".to_string()),
|
||||
(
|
||||
"OPENAI_COMPAT_BASE_URL".to_string(),
|
||||
relay_mesh_api_base_url()?,
|
||||
|
||||
Reference in New Issue
Block a user