Skip to content
Open
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/src-tauri/src/commands/agent_config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ fn agent_record() -> ManagedAgentRecord {
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_args: vec![],
Expand Down
16 changes: 8 additions & 8 deletions desktop/src-tauri/src/commands/agent_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ pub async fn update_managed_agent(
app: AppHandle,
state: State<'_, AppState>,
) -> Result<UpdateManagedAgentResponse, String> {
let working_directory_update = match input.working_directory.as_ref() {
Some(Some(path)) => Some(Some(crate::managed_agents::normalize_agent_workdir(path)?)),
Some(None) => Some(None),
None => None,
};
// Phase 1: local save (synchronous, under lock)
let (summary, sync_params, rollback) = {
let _store_guard = state
Expand All @@ -752,10 +757,8 @@ pub async fn update_managed_agent(
for pubkey in &exited_pubkeys {
state.clear_agent_session_caches(pubkey);
}

let record = find_managed_agent_mut(&mut records, &input.pubkey)?;
let previous_record = record.clone();

let mut name_changed = false;
if let Some(name_update) = input.name {
let trimmed = name_update.trim().to_string();
Expand All @@ -782,6 +785,9 @@ pub async fn update_managed_agent(
if let Some(relay_url) = input.relay_url {
record.relay_url = relay_url.trim().to_string();
}
if let Some(value) = working_directory_update {
record.working_directory = value;
}
if let Some(acp_command) = input.acp_command {
record.acp_command = acp_command;
}
Expand Down Expand Up @@ -813,7 +819,6 @@ pub async fn update_managed_agent(
crate::managed_agents::validate_user_env_keys(&env_vars)?;
record.env_vars = env_vars;
}

// Native provider/model fields are authoritative. Keep the typed marker
// derived for new records while retaining legacy typed records for
// non-native providers.
Expand All @@ -828,7 +833,6 @@ pub async fn update_managed_agent(
record.model = Some(model_ref.clone());
record.relay_mesh = Some(crate::managed_agents::RelayMeshConfig { model_ref });
}

// Inbound author gate: merge patch onto current values, then validate
// the merged state. This lets a single update switch to Allowlist AND
// supply pubkeys atomically.
Expand All @@ -851,16 +855,12 @@ pub async fn update_managed_agent(
if input.respond_to_allowlist.is_some() {
record.respond_to_allowlist = prospective_allowlist;
}

record.updated_at = now_iso();

save_managed_agents(&app, &records)?;

let record = records
.iter()
.find(|r| r.pubkey == input.pubkey)
.ok_or_else(|| format!("agent {} not found", input.pubkey))?;

// Publish the edit to the relay. After-save, inside the lock, before
// any .await. The retention upsert hashes the opt-IN projection, so an
// update that touched only runtime/local fields is a no-op publish.
Expand Down
12 changes: 6 additions & 6 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,11 @@ pub async fn create_managed_agent(
}
}
crate::managed_agents::validate_user_env_keys(&input.env_vars)?;

let working_directory = input
.working_directory
.as_deref()
.map(crate::managed_agents::normalize_agent_workdir)
.transpose()?;
// Validate & normalize the respond-to allowlist BEFORE any side effects.
// The harness has its own validator (buzz-acp/src/config.rs) but we want
// to catch malformed input at the boundary so the agent never tries to
Expand All @@ -600,11 +604,9 @@ pub async fn create_managed_agent(
"respond-to mode 'allowlist' requires at least one pubkey in the allowlist".to_string(),
);
}

// Snapshot the workspace owner pubkey for the legacy-record auth_tag
// fallback. Computed outside the records lock to keep lock ordering simple.
let owner_hex = workspace_owner_hex(&state)?;

// ── Phase 1: generate keys (sync lock) ────────────────────────────────────
let (agent_keys, private_key_nsec, pubkey, resolved_relay_url, input) = {
let _store_guard = state
Expand All @@ -616,7 +618,6 @@ pub async fn create_managed_agent(
.managed_agent_processes
.lock()
.map_err(|error| error.to_string())?;

let (sync_changed, exited_pubkeys) =
sync_managed_agent_processes(&mut records, &mut runtimes, &current_instance_id(&app));
if sync_changed {
Expand All @@ -638,7 +639,6 @@ pub async fn create_managed_agent(
.secret_key()
.to_bech32()
.map_err(|error| format!("failed to encode private key: {error}"))?;

// Store the relay override exactly as supplied (trimmed). An explicit
// value pins the agent; empty stays empty and resolves to the active
// workspace relay at read-time. Uniform for Local and Provider.
Expand All @@ -648,7 +648,6 @@ pub async fn create_managed_agent(
.map(str::trim)
.unwrap_or("")
.to_string();

(keys, private_key_nsec, pubkey, resolved_relay_url, input)
};

Expand Down Expand Up @@ -838,6 +837,7 @@ pub async fn create_managed_agent(
auth_tag: auth_tag.clone(),
relay_url: resolved_relay_url.clone(),
avatar_url: resolved_avatar_url.clone(),
working_directory: working_directory.clone(),
acp_command: input
.acp_command
.as_deref()
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/commands/agents_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn bare_agent_record(
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command_override: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ fn make_agent(
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "buzz-agent".to_string(),
agent_command_override: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ fn local_agent() -> ManagedAgentRecord {
auth_tag: Some("localauthtag".to_string()),
relay_url: "wss://relay.local".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command_override: Some("claude".to_string()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn make_definition(slug: &str) -> ManagedAgentRecord {
auth_tag: None,
relay_url: String::new(),
avatar_url: None,
working_directory: None,
acp_command: String::new(),
agent_command: String::new(),
agent_command_override: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ pub async fn confirm_agent_snapshot_import(
auth_tag: auth_tag.clone(),
relay_url: String::new(), // resolves to workspace relay at runtime
avatar_url: effective_avatar.clone(),
working_directory: None,
// Machine-local commands: derive from the runtime catalog at
// spawn time — never manufacture from snapshot data.
acp_command: crate::managed_agents::DEFAULT_ACP_COMMAND.to_string(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fn make_definition(slug: &str) -> ManagedAgentRecord {
auth_tag: None,
relay_url: String::new(),
avatar_url: None,
working_directory: None,
acp_command: String::new(),
agent_command: String::new(),
agent_command_override: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fn agent(persona_id: &str, name: &str, display_name: Option<&str>) -> ManagedAge
auth_tag: None,
relay_url: String::new(),
avatar_url: None,
working_directory: None,
acp_command: String::new(),
agent_command: String::new(),
agent_command_override: None,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/commands/team_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ pub async fn confirm_team_snapshot_import(
auth_tag: auth_tag.clone(),
relay_url: String::new(),
avatar_url: effective_avatar_url.clone(),
working_directory: None,
acp_command: crate::managed_agents::DEFAULT_ACP_COMMAND.to_string(),
agent_command: String::new(),
agent_command_override: None,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/commands/team_snapshot/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ fn team_export_with_instance_and_memory_level_uses_supplied_entries() {
auth_tag: None,
relay_url: String::new(),
avatar_url: None,
working_directory: None,
acp_command: crate::managed_agents::DEFAULT_ACP_COMMAND.to_string(),
agent_command: String::new(),
agent_command_override: None,
Expand Down
3 changes: 3 additions & 0 deletions desktop/src-tauri/src/managed_agents/agent_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ mod tests {
auth_tag: Some("authtagsecret".to_string()),
relay_url: "wss://relay.example".to_string(),
avatar_url: Some("https://example.com/a.png".to_string()),
working_directory: Some("/SENTINEL_LOCAL_WORKSPACE".to_string()),
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command_override: None,
Expand Down Expand Up @@ -289,6 +290,8 @@ mod tests {
assert!(!json.contains("backend_agent_id"));
assert!(!json.contains("provider_binary_path"));
assert!(!json.contains("relay_url"));
assert!(!json.contains("working_directory"));
assert!(!json.contains("SENTINEL_LOCAL_WORKSPACE"));

// Identity fields — must appear.
assert!(json.contains("\"name\""));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ mod tests {
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_args: vec![],
Expand Down
10 changes: 9 additions & 1 deletion desktop/src-tauri/src/managed_agents/agent_snapshot_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn minimal_record() -> ManagedAgentRecord {
auth_tag: Some("auth-tag-secret".to_string()), // MUST NOT appear in snapshot
relay_url: "wss://relay.example.com".to_string(), // MUST NOT appear in snapshot
avatar_url: Some("https://example.com/avatar.png".to_string()),
working_directory: Some("/SENTINEL_LOCAL_WORKSPACE".to_string()),
acp_command: "/usr/local/bin/acp".to_string(), // MUST NOT appear in snapshot
agent_command: "goose".to_string(), // MUST NOT appear in snapshot
agent_command_override: Some("goose-override".to_string()), // MUST NOT appear
Expand Down Expand Up @@ -384,7 +385,14 @@ fn snapshot_omits_removed_mcp_toolsets_config() {
fn secret_exclusion_machine_commands_absent() {
let record = minimal_record();
let json = snapshot_json_string(&record);
// acp_command / agent_command / agent_command_override / agent_args / mcp_command
// working_directory / acp_command / agent_command / agent_command_override /
// agent_args / mcp_command
assert!(
!json.contains("workingDirectory")
&& !json.contains("working_directory")
&& !json.contains("SENTINEL_LOCAL_WORKSPACE"),
"machine-local working directory must not appear"
);
assert!(
!json.contains("/usr/local/bin/acp"),
"acp_command path must not appear"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn test_record() -> ManagedAgentRecord {
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_args: vec![],
Expand Down
2 changes: 1 addition & 1 deletion desktop/src-tauri/src/managed_agents/discovery/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ fn record_with(
auth_tag: None,
relay_url: String::new(),
avatar_url: None,
working_directory: None,
acp_command: String::new(),
agent_command: String::new(),
agent_command_override: override_cmd.map(str::to_string),
Expand Down Expand Up @@ -285,7 +286,6 @@ fn record_with(
relay_mesh: None,
}
}

#[test]
fn record_agent_command_own_runtime_wins_over_persona() {
// A record with its own materialized runtime never consults the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ fn record(
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command_override: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ fn bare_record() -> ManagedAgentRecord {
auth_tag: None,
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
working_directory: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command_override: None,
Expand Down
Loading