Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions desktop/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export default defineConfig({
"**/where-to-run-config.spec.ts",
"**/huddle-transcription.spec.ts",
"**/agent-numeric-tuning.spec.ts",
"**/needs-restart-screenshots.spec.ts",
],
use: {
...devices["Desktop Chrome"],
Expand Down
29 changes: 4 additions & 25 deletions desktop/src-tauri/src/commands/agent_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ use crate::{
RuntimeConfigSurface, SessionConfigCache,
},
},
current_instance_id, is_reserved_env_key, is_well_formed_env_key, known_acp_runtime,
load_managed_agents, load_personas, save_managed_agents, sync_managed_agent_processes,
AgentDefinition, GlobalAgentConfig, KnownAcpRuntime, ManagedAgentRecord,
ManagedAgentRuntimeKey, MAX_ENV_VALUE_BYTES,
current_instance_id, is_reserved_env_key, is_safe_to_reveal, is_well_formed_env_key,
known_acp_runtime, load_managed_agents, load_personas, save_managed_agents,
sync_managed_agent_processes, AgentDefinition, GlobalAgentConfig, KnownAcpRuntime,
ManagedAgentRecord, ManagedAgentRuntimeKey, MAX_ENV_VALUE_BYTES,
},
};

Expand Down Expand Up @@ -209,27 +209,6 @@ pub struct BakedEnvEntry {
pub masked: bool,
}

/// Returns `true` when a baked-env key is safe to display unmasked in the UI.
///
/// This uses an explicit allowlist of keys that are known safe (non-secret).
/// Any key NOT in this set is masked — default-deny for a security surface.
///
/// Allowlist (case-insensitive):
/// - `BUZZ_AGENT_PROVIDER`, `BUZZ_AGENT_MODEL` — agent runtime selection
/// - `BUZZ_AGENT_THINKING_EFFORT` — non-secret enum (none/minimal/low/medium/high/xhigh/max)
/// - `DATABRICKS_HOST`, `DATABRICKS_MODEL` — Block non-secret defaults
fn is_safe_to_reveal(key: &str) -> bool {
const SAFE_KEYS: &[&str] = &[
"BUZZ_AGENT_PROVIDER",
"BUZZ_AGENT_MODEL",
"BUZZ_AGENT_THINKING_EFFORT",
"DATABRICKS_HOST",
"DATABRICKS_MODEL",
];
let upper = key.to_ascii_uppercase();
SAFE_KEYS.iter().any(|safe| upper == *safe)
}

/// Expose the baked build env to the frontend with values shown, but any
/// key not in the safe-to-reveal allowlist has its value replaced by `••••••`.
///
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/commands/agents_deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub(super) fn build_launch_block(
policy_env.insert(SESSION_TITLE_ENV_VAR.into(), value);
}
if let Some(value) =
crate::managed_agents::spawn_hash::effective_team_instructions(record, teams)
crate::managed_agents::spawn_snapshot::effective_team_instructions(record, teams)
{
policy_env.insert("BUZZ_ACP_TEAM_INSTRUCTIONS".into(), value);
}
Expand Down
22 changes: 22 additions & 0 deletions desktop/src-tauri/src/managed_agents/env_vars.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,28 @@ pub fn validate_user_env_keys(env_vars: &BTreeMap<String, String>) -> Result<(),
Ok(())
}

/// Returns `true` when `key` is safe to show verbatim — not a credential.
///
/// Default-deny: every key NOT in this explicit allowlist is masked. Callers
/// that display env values (baked-env UI, spawn-diff tooltip) share this
/// single authority — no second list.
///
/// Allowlist (case-insensitive):
/// - `BUZZ_AGENT_PROVIDER`, `BUZZ_AGENT_MODEL` — agent runtime selection
/// - `BUZZ_AGENT_THINKING_EFFORT` — non-secret enum (none/minimal/low/medium/high/xhigh/max)
/// - `DATABRICKS_HOST`, `DATABRICKS_MODEL` — Block non-secret defaults
pub(crate) fn is_safe_to_reveal(key: &str) -> bool {
const SAFE_KEYS: &[&str] = &[
"BUZZ_AGENT_PROVIDER",
"BUZZ_AGENT_MODEL",
"BUZZ_AGENT_THINKING_EFFORT",
"DATABRICKS_HOST",
"DATABRICKS_MODEL",
];
let upper = key.to_ascii_uppercase();
SAFE_KEYS.iter().any(|safe| upper == *safe)
}

/// Per-value byte cap for env values. 32 KiB is generous for credentials,
/// JWT-ish tokens, certs etc., but small enough that a malformed IPC
/// caller can't blow up the persona/agent JSON file. Tune up if real
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ mod runtime;
mod runtime_commands;
mod runtime_types;
pub(crate) mod snapshot_avatar;
pub(crate) mod spawn_hash;
pub(crate) mod spawn_snapshot;
pub(crate) mod storage;
pub(crate) mod team_events;
mod team_repair;
Expand Down
15 changes: 8 additions & 7 deletions desktop/src-tauri/src/managed_agents/persona_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -450,12 +450,12 @@ pub fn persona_snapshot(persona: &AgentDefinition) -> PersonaSnapshot {
/// This is the single apply used by every snapshot-apply site: the spawn
/// re-pin (`start_local_agent_with_preflight`), the launch backfill and
/// restore re-snapshot (`restore.rs`), and the prospective re-snapshot inside
/// `spawn_config_hash` — so a future `PersonaSnapshot` field addition
/// propagates to all of them at once.
/// `prospective_spawn_config_snapshot` — so a future `PersonaSnapshot` field
/// addition propagates to all of them at once.
///
/// Deliberately does NOT touch `updated_at`: persistence stamps are the
/// caller's concern, and `spawn_config_hash` (which applies this to a clone)
/// must stay pure.
/// caller's concern, and the prospective snapshot (which applies this to a
/// clone) must stay pure.
pub fn apply_persona_snapshot(record: &mut ManagedAgentRecord, persona: &AgentDefinition) {
let snapshot = persona_snapshot(persona);
if let Some(prompt) = snapshot.system_prompt {
Expand Down Expand Up @@ -498,16 +498,17 @@ pub fn apply_persona_snapshot(record: &mut ManagedAgentRecord, persona: &AgentDe
/// paths re-pin it to its linked persona, without mutating `record` itself.
///
/// Every decision made ahead of the real re-pin — the relay-mesh preflight in
/// `start_local_agent_with_preflight`, the restart-badge hash in
/// `spawn_config_hash` — needs to reason about spawn-time state, not
/// `start_local_agent_with_preflight`, the restart-badge snapshot in
/// `prospective_spawn_config_snapshot` — needs to reason about spawn-time
/// state, not
/// pre-snapshot bytes, so a persona edit that flips a field (e.g. `provider`
/// to/from relay-mesh) between saves is reflected in the decision instead of
/// the stale value the real [`apply_persona_snapshot`] is about to overwrite
/// anyway. Idempotent: applying it to an already-current record is a no-op,
/// so the spawn-time stamp and later recomputes agree when nothing changed.
///
/// Orphaned records (persona deleted) pass through unchanged: the caller's
/// own orphan handling — refusing to spawn, hashing as `(None, None, None)`
/// own orphan handling — refusing to spawn, snapshotting as `(None, None, None)`
/// — runs on the real record downstream, not on this preview.
pub fn preview_prospective_persona_snapshot(
record: &ManagedAgentRecord,
Expand Down
4 changes: 2 additions & 2 deletions desktop/src-tauri/src/managed_agents/process_lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ pub fn taskkill_tree(pid: u32) -> Result<(), String> {
pub fn finish_spawn(
child: std::process::Child,
log_path: std::path::PathBuf,
spawn_config_hash: u64,
spawn_config: super::spawn_snapshot::SpawnConfigSnapshot,
setup_mode: bool,
adapter_availability: Option<super::AcpAvailabilityStatus>,
start_nonce: String,
Expand All @@ -149,7 +149,7 @@ pub fn finish_spawn(
super::ManagedAgentProcess {
child,
log_path,
spawn_config_hash,
spawn_config,
setup_mode,
adapter_availability,
start_nonce,
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub(crate) struct EffectiveAgentEnv {
//
// A single owned type that fully describes what a spawn would run. Produced
// by `resolve_effective_harness_descriptor` and consumed by spawn_agent_child,
// spawn_config_hash, build_managed_agent_summary, get_agent_models, and
// spawn_snapshot, build_managed_agent_summary, get_agent_models, and
// agent_readiness — so the harness-definition lookup and arg/env resolution
// happen exactly once, in one place.

Expand Down
10 changes: 7 additions & 3 deletions desktop/src-tauri/src/managed_agents/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ use tauri::Manager;
/// restore would kill reconcile's lazy child by its receipt and replace it with
/// an eager one, flipping the pair's laziness on a startup race.
enum SpawnOutcome {
Spawned(super::ManagedAgentRuntimeKey, ManagedAgentProcess),
/// Boxed: the spawned process carries its full spawn-config snapshot, so an
/// inline variant would make every `Skipped`/`Failed` outcome pay for it.
Spawned(super::ManagedAgentRuntimeKey, Box<ManagedAgentProcess>),
Skipped,
Failed(String),
}
Expand Down Expand Up @@ -338,7 +340,9 @@ pub async fn restore_managed_agents_on_launch(
owner_hex_ref,
)
}) {
Ok(process) => SpawnOutcome::Spawned(key, process),
Ok(process) => {
SpawnOutcome::Spawned(key, Box::new(process))
}
Err(error) => SpawnOutcome::Failed(error),
}
}
Expand Down Expand Up @@ -400,7 +404,7 @@ pub async fn restore_managed_agents_on_launch(
record.last_stopped_at = None;
record.last_exit_code = None;
record.last_error = None;
runtimes.insert(key, super::ManagedAgentPairRuntime::starting(process));
runtimes.insert(key, super::ManagedAgentPairRuntime::starting(*process));
successfully_spawned.push(pubkey);
}
SpawnOutcome::Failed(error) => {
Expand Down
Loading
Loading