mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Mask hidden harness effort defaults
Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -69,7 +69,9 @@ pub(super) fn build_deploy_payload(
|
||||
// on key collision). Without this, provider-backed agents wouldn't receive
|
||||
// credentials saved on the persona or the agent itself.
|
||||
let global_config = crate::managed_agents::load_global_agent_config(app).unwrap_or_default();
|
||||
let global_env = global_config.env_vars.clone();
|
||||
let personas = load_personas(app).unwrap_or_default();
|
||||
let global_env =
|
||||
crate::managed_agents::global_env_vars_for_record(record, &personas, &global_config);
|
||||
let persona_env =
|
||||
crate::managed_agents::resolve_persona_env(app, record.persona_id.as_deref())?;
|
||||
// Merge: global < persona (persona wins over global).
|
||||
@@ -78,7 +80,6 @@ pub(super) fn build_deploy_payload(
|
||||
let merged_env =
|
||||
crate::managed_agents::merged_user_env(&global_persona_merged, &record.env_vars);
|
||||
|
||||
let personas = load_personas(app).unwrap_or_default();
|
||||
let cfg = crate::managed_agents::effective_config::resolve_effective_config(
|
||||
record,
|
||||
&personas,
|
||||
|
||||
@@ -33,6 +33,8 @@ use crate::managed_agents::env_vars::{
|
||||
use crate::managed_agents::storage::{atomic_write_json_restricted, managed_agents_base_dir};
|
||||
use crate::managed_agents::types::{AgentDefinition, ManagedAgentRecord};
|
||||
|
||||
const HARNESS_DEPENDENT_GLOBAL_ENV_KEYS: &[&str] = &["BUZZ_AGENT_THINKING_EFFORT"];
|
||||
|
||||
/// The global agent configuration record.
|
||||
///
|
||||
/// Shape mirrors the per-agent/persona trio (`env_vars` + `provider` + `model`)
|
||||
@@ -45,11 +47,12 @@ use crate::managed_agents::types::{AgentDefinition, ManagedAgentRecord};
|
||||
/// consulted); for a definition-less instance, instance → global.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct GlobalAgentConfig {
|
||||
/// Global env vars injected into ALL agents unconditionally.
|
||||
/// Global env vars injected below persona and per-agent overrides.
|
||||
///
|
||||
/// Lowest user-settable layer — per-agent and persona values win on any
|
||||
/// key collision. Reserved and derived keys are rejected at save time and
|
||||
/// stripped at spawn time.
|
||||
/// key collision. Harness-dependent effort is masked when a runtime-less
|
||||
/// definition falls back away from the preferred runtime. Reserved and
|
||||
/// derived keys are rejected at save time and stripped at spawn time.
|
||||
#[serde(default)]
|
||||
pub env_vars: BTreeMap<String, String>,
|
||||
|
||||
@@ -207,11 +210,11 @@ pub fn save_global_agent_config(app: &AppHandle, config: &GlobalAgentConfig) ->
|
||||
atomic_write_json_restricted(&path, &payload)
|
||||
}
|
||||
|
||||
/// Return the global provider/model values that are safe for this record.
|
||||
/// Whether harness-dependent global defaults are safe for this record.
|
||||
///
|
||||
/// A runtime-less definition can be started on a fallback runtime when its
|
||||
/// saved global preference is hidden or unavailable. In that case the selected
|
||||
/// command is stored as the record snapshot, and provider/model defaults
|
||||
/// command is stored as the record snapshot, and harness-dependent defaults
|
||||
/// belonging to a different preferred runtime must not cross the harness
|
||||
/// boundary. A non-empty `agent_command_override` keeps normal global
|
||||
/// inheritance only when it represents an explicit harness selection. An
|
||||
@@ -220,14 +223,13 @@ pub fn save_global_agent_config(app: &AppHandle, config: &GlobalAgentConfig) ->
|
||||
/// harness. Explicitly configured definitions and standalone agents keep
|
||||
/// normal global inheritance. Configs written before `preferred_runtime`
|
||||
/// existed implicitly belong to Buzz Agent.
|
||||
pub(crate) fn global_model_provider_for_record<'a>(
|
||||
fn global_harness_defaults_apply_to_record(
|
||||
record: &ManagedAgentRecord,
|
||||
personas: &[AgentDefinition],
|
||||
global: &'a GlobalAgentConfig,
|
||||
) -> (Option<&'a str>, Option<&'a str>) {
|
||||
let global_values = (global.model.as_deref(), global.provider.as_deref());
|
||||
global: &GlobalAgentConfig,
|
||||
) -> bool {
|
||||
if record.persona_id.is_none() {
|
||||
return global_values;
|
||||
return true;
|
||||
}
|
||||
|
||||
let definition_runtime = record.runtime.as_deref().or_else(|| {
|
||||
@@ -238,7 +240,7 @@ pub(crate) fn global_model_provider_for_record<'a>(
|
||||
.and_then(|persona| persona.runtime.as_deref())
|
||||
});
|
||||
if definition_runtime.is_some_and(|runtime| !runtime.trim().is_empty()) {
|
||||
return global_values;
|
||||
return true;
|
||||
}
|
||||
|
||||
if !record.agent_command_override_is_implicit
|
||||
@@ -247,7 +249,7 @@ pub(crate) fn global_model_provider_for_record<'a>(
|
||||
.as_deref()
|
||||
.is_some_and(|command| !command.trim().is_empty())
|
||||
{
|
||||
return global_values;
|
||||
return true;
|
||||
}
|
||||
|
||||
let preferred_runtime = global
|
||||
@@ -258,15 +260,40 @@ pub(crate) fn global_model_provider_for_record<'a>(
|
||||
let selected_command = crate::managed_agents::record_agent_command(record, personas);
|
||||
let selected_runtime = crate::managed_agents::known_acp_runtime(&selected_command);
|
||||
|
||||
if selected_runtime.is_some_and(|selected| {
|
||||
selected_runtime.is_some_and(|selected| {
|
||||
preferred_runtime.is_some_and(|preferred| preferred.id == selected.id)
|
||||
}) {
|
||||
global_values
|
||||
})
|
||||
}
|
||||
|
||||
/// Return the global provider/model values that are safe for this record.
|
||||
pub(crate) fn global_model_provider_for_record<'a>(
|
||||
record: &ManagedAgentRecord,
|
||||
personas: &[AgentDefinition],
|
||||
global: &'a GlobalAgentConfig,
|
||||
) -> (Option<&'a str>, Option<&'a str>) {
|
||||
if global_harness_defaults_apply_to_record(record, personas, global) {
|
||||
(global.model.as_deref(), global.provider.as_deref())
|
||||
} else {
|
||||
(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return global env vars with harness-dependent defaults masked when an
|
||||
/// implicit fallback crosses the preferred-runtime boundary.
|
||||
pub(crate) fn global_env_vars_for_record(
|
||||
record: &ManagedAgentRecord,
|
||||
personas: &[AgentDefinition],
|
||||
global: &GlobalAgentConfig,
|
||||
) -> BTreeMap<String, String> {
|
||||
let mut env_vars = global.env_vars.clone();
|
||||
if !global_harness_defaults_apply_to_record(record, personas, global) {
|
||||
for key in HARNESS_DEPENDENT_GLOBAL_ENV_KEYS {
|
||||
env_vars.remove(*key);
|
||||
}
|
||||
}
|
||||
env_vars
|
||||
}
|
||||
|
||||
/// Resolve the effective model and provider for an agent.
|
||||
///
|
||||
/// Delegates to `effective_config::resolve_effective_config` which enforces
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::{
|
||||
normalize_global_config_fields, resolve_effective_model_provider, strip_empty_env_vars,
|
||||
validate_global_config, GlobalAgentConfig,
|
||||
global_env_vars_for_record, normalize_global_config_fields, resolve_effective_model_provider,
|
||||
strip_empty_env_vars, validate_global_config, GlobalAgentConfig,
|
||||
};
|
||||
use crate::managed_agents::{AgentDefinition, BackendKind, ManagedAgentRecord, RespondTo};
|
||||
|
||||
@@ -494,6 +494,10 @@ fn explicit_harness_override_keeps_defaults_from_a_different_preferred_runtime()
|
||||
global_env_vars_for_record(&record, &personas, &global),
|
||||
global.env_vars
|
||||
);
|
||||
assert_eq!(
|
||||
global_env_vars_for_record(&record, &personas, &global),
|
||||
global.env_vars
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -505,6 +509,10 @@ fn implicit_fallback_override_masks_defaults_from_a_different_preferred_runtime(
|
||||
record.agent_command_override_is_implicit = true;
|
||||
let personas = vec![persona("p1", None, None)];
|
||||
let global = GlobalAgentConfig {
|
||||
env_vars: BTreeMap::from([
|
||||
("BUZZ_AGENT_THINKING_EFFORT".to_string(), "high".to_string()),
|
||||
("SHARED_API_KEY".to_string(), "kept".to_string()),
|
||||
]),
|
||||
model: Some("auto".to_string()),
|
||||
provider: Some("relay-mesh".to_string()),
|
||||
preferred_runtime: Some("buzz-agent".to_string()),
|
||||
@@ -515,6 +523,10 @@ fn implicit_fallback_override_masks_defaults_from_a_different_preferred_runtime(
|
||||
resolve_effective_model_provider(&record, &personas, &global),
|
||||
(None, None)
|
||||
);
|
||||
assert_eq!(
|
||||
global_env_vars_for_record(&record, &personas, &global),
|
||||
BTreeMap::from([("SHARED_API_KEY".to_string(), "kept".to_string())])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -52,8 +52,9 @@ pub use env_vars::*;
|
||||
pub(crate) use git_bash::git_bash_available;
|
||||
pub(crate) use git_bash::{discover_git_bash, GitBashPrerequisite};
|
||||
pub(crate) use global_config::{
|
||||
global_model_provider_for_record, load_global_agent_config, resolve_effective_model_provider,
|
||||
save_global_agent_config, validate_global_config, GlobalAgentConfig,
|
||||
global_env_vars_for_record, global_model_provider_for_record, load_global_agent_config,
|
||||
resolve_effective_model_provider, save_global_agent_config, validate_global_config,
|
||||
GlobalAgentConfig,
|
||||
};
|
||||
pub(crate) use managed_node_paths::*;
|
||||
pub use nest::*;
|
||||
|
||||
@@ -256,7 +256,9 @@ fn resolve_effective_agent_env_with_def(
|
||||
// Injected before persona/agent so per-agent values win on collision.
|
||||
// `merged_user_env` with an empty "lower" map applies reserved/malformed-key
|
||||
// filtering to the global map for free.
|
||||
let global_env = merged_user_env(&BTreeMap::new(), &global.env_vars);
|
||||
let applicable_global_env =
|
||||
super::global_config::global_env_vars_for_record(record, personas, global);
|
||||
let global_env = merged_user_env(&BTreeMap::new(), &applicable_global_env);
|
||||
env.extend(global_env);
|
||||
|
||||
// Layer 3b: merged user env — live persona env under the record's own
|
||||
|
||||
@@ -94,8 +94,9 @@ with a TypeScript lookup table or an id comparison in a component.
|
||||
agent that already uses it. If the disabled runtime was the saved global
|
||||
default, consumers immediately ignore that preference and the defaults
|
||||
editor persists its visible fallback on the next save. Its dependent
|
||||
provider/model defaults are also ignored for new implicit fallback agents,
|
||||
without changing the persisted configuration used by existing agents.
|
||||
provider/model/effort defaults are also ignored for new implicit fallback
|
||||
agents, without changing the persisted configuration used by existing
|
||||
agents.
|
||||
Legacy global defaults with no saved preferred runtime are treated as
|
||||
Buzz Agent-owned for this masking boundary, so hiding Buzz Agent cannot
|
||||
leak its provider/model into another implicit fallback. A persisted
|
||||
|
||||
@@ -104,7 +104,10 @@ test("stored runtime visibility is read from the versioned device key", () => {
|
||||
|
||||
test("a disabled saved runtime and its dependent defaults are masked", () => {
|
||||
const config = {
|
||||
env_vars: {},
|
||||
env_vars: {
|
||||
BUZZ_AGENT_THINKING_EFFORT: "high",
|
||||
SHARED_API_KEY: "kept",
|
||||
},
|
||||
provider: "relay-mesh",
|
||||
model: "auto",
|
||||
preferred_runtime: "Goose",
|
||||
@@ -112,6 +115,7 @@ test("a disabled saved runtime and its dependent defaults are masked", () => {
|
||||
|
||||
assert.deepEqual(maskDisabledAcpRuntimePreference(config, ["goose"]), {
|
||||
...config,
|
||||
env_vars: { SHARED_API_KEY: "kept" },
|
||||
provider: null,
|
||||
model: null,
|
||||
preferred_runtime: null,
|
||||
@@ -121,7 +125,10 @@ test("a disabled saved runtime and its dependent defaults are masked", () => {
|
||||
|
||||
test("legacy defaults without a saved runtime are owned by buzz-agent", () => {
|
||||
const config = {
|
||||
env_vars: {},
|
||||
env_vars: {
|
||||
BUZZ_AGENT_THINKING_EFFORT: "medium",
|
||||
SHARED_API_KEY: "kept",
|
||||
},
|
||||
provider: "relay-mesh",
|
||||
model: "auto",
|
||||
preferred_runtime: null,
|
||||
@@ -129,6 +136,7 @@ test("legacy defaults without a saved runtime are owned by buzz-agent", () => {
|
||||
|
||||
assert.deepEqual(maskDisabledAcpRuntimePreference(config, ["buzz-agent"]), {
|
||||
...config,
|
||||
env_vars: { SHARED_API_KEY: "kept" },
|
||||
provider: null,
|
||||
model: null,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { withoutHarnessDependentAgentEnvVars } from "../ui/buzzAgentConfig";
|
||||
|
||||
export const ACP_RUNTIME_VISIBILITY_STORAGE_KEY =
|
||||
"buzz-agent-runtime-visibility.v1";
|
||||
@@ -166,11 +167,12 @@ export function visibleAcpRuntimeSeedForCreate<T extends { id: string }>(
|
||||
* The persisted config is left untouched until the user next saves defaults;
|
||||
* existing agents keep their configuration while new consumers immediately
|
||||
* fall back through the normal runtime selection path without carrying a
|
||||
* provider or model selected for the hidden harness. Configs written before
|
||||
* preferred_runtime existed implicitly belong to Buzz Agent.
|
||||
* provider, model, or effort selected for the hidden harness. Configs written
|
||||
* before preferred_runtime existed implicitly belong to Buzz Agent.
|
||||
*/
|
||||
export function maskDisabledAcpRuntimePreference<
|
||||
T extends {
|
||||
env_vars: Record<string, string>;
|
||||
model: string | null;
|
||||
preferred_runtime: string | null;
|
||||
provider: string | null;
|
||||
@@ -188,6 +190,7 @@ export function maskDisabledAcpRuntimePreference<
|
||||
|
||||
return {
|
||||
...config,
|
||||
env_vars: withoutHarnessDependentAgentEnvVars(config.env_vars),
|
||||
model: null,
|
||||
preferred_runtime: null,
|
||||
provider: null,
|
||||
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
AcpRuntimeCatalogEntry,
|
||||
GlobalAgentConfig,
|
||||
} from "@/shared/api/types";
|
||||
import { BUZZ_AGENT_THINKING_EFFORT } from "./buzzAgentConfig";
|
||||
import { withoutHarnessDependentAgentEnvVars } from "./buzzAgentConfig";
|
||||
import type { RuntimeFileConfigSubset } from "@/shared/api/tauri";
|
||||
// Dialogs import getDefaultPersonaRuntime via this re-export; lib code imports
|
||||
// directly from lib/resolvePersonaRuntime.
|
||||
@@ -222,12 +222,9 @@ export function resetConfigForHarnessChange(
|
||||
config: GlobalAgentConfig,
|
||||
runtimeId: string,
|
||||
): GlobalAgentConfig {
|
||||
const nextEnvVars = { ...config.env_vars };
|
||||
delete nextEnvVars[BUZZ_AGENT_THINKING_EFFORT];
|
||||
|
||||
return {
|
||||
...config,
|
||||
env_vars: nextEnvVars,
|
||||
env_vars: withoutHarnessDependentAgentEnvVars(config.env_vars),
|
||||
model: null,
|
||||
preferred_runtime: runtimeId || null,
|
||||
provider:
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
/** Env var key for the thinking/effort level sent to the LLM. */
|
||||
export const BUZZ_AGENT_THINKING_EFFORT = "BUZZ_AGENT_THINKING_EFFORT";
|
||||
|
||||
/** Remove env-backed values whose semantics change with the harness. */
|
||||
export function withoutHarnessDependentAgentEnvVars(
|
||||
envVars: Record<string, string>,
|
||||
): Record<string, string> {
|
||||
const next = { ...envVars };
|
||||
delete next[BUZZ_AGENT_THINKING_EFFORT];
|
||||
return next;
|
||||
}
|
||||
|
||||
/** Env var key for the maximum output token count per turn. */
|
||||
export const BUZZ_AGENT_MAX_OUTPUT_TOKENS = "BUZZ_AGENT_MAX_OUTPUT_TOKENS";
|
||||
|
||||
|
||||
@@ -1037,7 +1037,7 @@ export type ChannelMessagesPageResponse = {
|
||||
* Precedence: baked floor < global < persona < per-agent.
|
||||
*/
|
||||
export type GlobalAgentConfig = {
|
||||
/** Global env vars injected into all agents unconditionally. */
|
||||
/** Global env vars injected below persona and per-agent overrides. */
|
||||
env_vars: Record<string, string>;
|
||||
/** Global fallback provider (e.g. "anthropic", "databricks_v2"). Null = no global default. */
|
||||
provider: string | null;
|
||||
|
||||
Reference in New Issue
Block a user