diff --git a/desktop/src-tauri/src/commands/agent_models.rs b/desktop/src-tauri/src/commands/agent_models.rs index 4704582372d..5ab2069289f 100644 --- a/desktop/src-tauri/src/commands/agent_models.rs +++ b/desktop/src-tauri/src/commands/agent_models.rs @@ -1,10 +1,9 @@ use std::collections::{BTreeMap, HashSet}; +use super::agent_model_process::run_agent_models_command; use nostr::Keys; use serde::Deserialize; use tauri::{AppHandle, State}; - -use super::agent_model_process::run_agent_models_command; // The map-only lookup is reached solely from the base-URL helpers that exist for // their unit tests; discovery itself always goes through the process-env variant. #[cfg(test)] @@ -13,7 +12,6 @@ use super::agent_models_env::{ effective_discovery_provider, env_or_process_value, redaction_env_with_value, DiscoveryProvider, }; use super::agent_update_rollback::{rollback_failed_agent_update, AgentUpdateRollback}; - use crate::{ app_state::AppState, managed_agents::{ @@ -727,9 +725,9 @@ fn apply_model_provider_prompt_update( /// Update mutable fields on an existing managed agent record. /// -/// Does NOT auto-restart the agent. Runtime config changes (system prompt, -/// parallelism, commands, toolsets) take effect on the next agent spawn. -/// Name changes are synced to the relay immediately via a kind:0 re-publish. +/// Local runtime config changes take effect on the next spawn. Provider-backed +/// agents are re-deployed after a successful save. Name changes are synced to +/// the relay immediately via a kind:0 re-publish. #[tauri::command] pub async fn update_managed_agent( input: UpdateManagedAgentRequest, @@ -931,6 +929,8 @@ pub async fn update_managed_agent( } } + super::agent_update_deploy::apply_update(&app, &state, &input.pubkey).await?; + Ok(UpdateManagedAgentResponse { agent: summary, profile_sync_error: None, diff --git a/desktop/src-tauri/src/commands/agent_update_deploy.rs b/desktop/src-tauri/src/commands/agent_update_deploy.rs new file mode 100644 index 00000000000..f0549353289 --- /dev/null +++ b/desktop/src-tauri/src/commands/agent_update_deploy.rs @@ -0,0 +1,47 @@ +use tauri::AppHandle; + +use crate::{ + app_state::AppState, + managed_agents::{load_managed_agents, BackendKind}, +}; + +/// Apply an edited provider-backed record to its remote provider. Local records +/// return without action because their next spawn consumes the saved change. +pub(super) async fn apply_update( + app: &AppHandle, + state: &AppState, + pubkey: &str, +) -> Result<(), String> { + let provider_deploy = { + let _store_guard = state + .managed_agents_store_lock + .lock() + .map_err(|e| e.to_string())?; + let records = load_managed_agents(app)?; + let record = records + .iter() + .find(|record| record.pubkey == pubkey) + .ok_or_else(|| format!("agent {pubkey} not found"))?; + let BackendKind::Provider { id, config } = &record.backend else { + return Ok(()); + }; + ( + id.clone(), + config.clone(), + record.provider_binary_path.clone(), + super::agents::build_deploy_payload(app, state, record)?, + ) + }; + + let (provider_id, provider_config, cached_binary_path, agent_json) = provider_deploy; + super::agents::deploy_to_provider( + app, + state, + pubkey, + &provider_id, + &provider_config, + agent_json, + cached_binary_path.as_deref(), + ) + .await +} diff --git a/desktop/src-tauri/src/commands/agents.rs b/desktop/src-tauri/src/commands/agents.rs index dd61fc9398a..22ff7cfb353 100644 --- a/desktop/src-tauri/src/commands/agents.rs +++ b/desktop/src-tauri/src/commands/agents.rs @@ -456,7 +456,7 @@ pub(super) async fn start_local_agent_with_preflight( /// /// Returns Ok(()) on success, Err(message) on failure. Either way the record is /// updated and saved before returning. -async fn deploy_to_provider( +pub(super) async fn deploy_to_provider( app: &AppHandle, state: &AppState, pubkey: &str, @@ -1358,7 +1358,7 @@ pub async fn delete_managed_agent( #[path = "agents_deploy.rs"] mod deploy; pub(super) mod provider_access; -use deploy::build_deploy_payload; +pub(super) use deploy::build_deploy_payload; #[cfg(test)] use deploy::{deploy_payload_json, DeployProjections}; #[cfg(test)] diff --git a/desktop/src-tauri/src/commands/agents_deploy.rs b/desktop/src-tauri/src/commands/agents_deploy.rs index 47ee5f92d49..483eb60134f 100644 --- a/desktop/src-tauri/src/commands/agents_deploy.rs +++ b/desktop/src-tauri/src/commands/agents_deploy.rs @@ -121,7 +121,7 @@ pub(super) fn ensure_remote_provider_supported(provider: Option<&str>) -> Result } /// Build the standard agent JSON payload for provider deploy calls. -pub(super) fn build_deploy_payload( +pub(crate) fn build_deploy_payload( app: &AppHandle, state: &AppState, record: &ManagedAgentRecord, diff --git a/desktop/src-tauri/src/commands/mod.rs b/desktop/src-tauri/src/commands/mod.rs index 322834630a3..9de0cc0e781 100644 --- a/desktop/src-tauri/src/commands/mod.rs +++ b/desktop/src-tauri/src/commands/mod.rs @@ -9,6 +9,7 @@ mod agent_models; mod agent_models_env; mod agent_providers; mod agent_settings; +mod agent_update_deploy; mod agent_update_rollback; mod agents; mod canvas; diff --git a/desktop/src/features/agents/AGENTS.md b/desktop/src/features/agents/AGENTS.md index b578326eba3..05d938a49b3 100644 --- a/desktop/src/features/agents/AGENTS.md +++ b/desktop/src/features/agents/AGENTS.md @@ -171,6 +171,12 @@ with a TypeScript lookup table or an id comparison in a component. `getAgentAccessOwnerOnly()` is true, every managed agent's access control is locked to owner-only, including provider-backed agents. A provider backend does not prove remote execution and must never create a policy carve-out. +12. **Provider-backed edits re-deploy immediately.** `update_managed_agent` + persists the Desktop record, completes any required relay profile sync, and + then calls the provider's idempotent deploy operation with the revised + launch payload. Never require a local Stop/Start transition to apply a + remote agent's updated instruction, model, harness, or policy: provider + agents have no local runtime pair to restart. ## The tests that enforce this