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 @@ -85,6 +85,7 @@ fn agent_record() -> ManagedAgentRecord {
model: None,
env_vars: Default::default(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: BackendKind::Local,
Expand Down
44 changes: 44 additions & 0 deletions desktop/src-tauri/src/commands/agent_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,50 @@ pub async fn set_managed_agent_start_on_app_launch(
.map_err(|e| format!("spawn_blocking failed: {e}"))?
}

#[tauri::command]
pub async fn set_managed_agent_disable_local_spawn(
pubkey: String,
disable_local_spawn: bool,
app: AppHandle,
) -> Result<ManagedAgentSummary, String> {
tokio::task::spawn_blocking(move || {
let state = app.state::<AppState>();
let _store_guard = state
.managed_agents_store_lock
.lock()
.map_err(|error| error.to_string())?;
let mut records = load_managed_agents(&app)?;
let mut runtimes = state
.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 {
save_managed_agents(&app, &records)?;
}
for pubkey in &exited_pubkeys {
state.clear_agent_session_caches(pubkey);
}

{
let record = find_managed_agent_mut(&mut records, &pubkey)?;
record.disable_local_spawn = disable_local_spawn;
record.updated_at = now_iso();
}

save_managed_agents(&app, &records)?;
let record = records
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
super::agents::summarize_from_disk(&app, record, &runtimes)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
}

#[tauri::command]
pub async fn set_managed_agent_auto_restart(
pubkey: String,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -685,6 +685,7 @@ pub async fn create_managed_agent(
} else {
input.start_on_app_launch
},
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: input.backend.clone(),
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 @@ -31,6 +31,7 @@ fn bare_agent_record(
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
runtime_pid: None,
backend: BackendKind::Local,
backend_agent_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn make_agent(
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
runtime_pid,
backend: BackendKind::Local,
backend_agent_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ fn local_agent() -> ManagedAgentRecord {
persona_source_version: Some("local-hash".to_string()),
env_vars: BTreeMap::from([("API_KEY".to_string(), "localsecret".to_string())]),
start_on_app_launch: true,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: Some(1234),
backend: crate::managed_agents::BackendKind::Provider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ fn make_definition(slug: &str) -> ManagedAgentRecord {
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: false,
runtime_pid: None,
backend: BackendKind::Local,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,7 @@ pub async fn confirm_agent_snapshot_import(
persona_source_version: None,
env_vars: std::collections::BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: crate::managed_agents::BackendKind::Local,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn make_definition(slug: &str) -> ManagedAgentRecord {
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: false,
runtime_pid: None,
backend: BackendKind::Local,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn agent(persona_id: &str, name: &str, display_name: Option<&str>) -> ManagedAge
persona_source_version: None,
env_vars: std::collections::BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: Default::default(),
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 @@ -575,6 +575,7 @@ pub async fn confirm_team_snapshot_import(
persona_source_version: None,
env_vars: std::collections::BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: crate::managed_agents::BackendKind::Local,
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 @@ -202,6 +202,7 @@ fn team_export_with_instance_and_memory_level_uses_supplied_entries() {
persona_source_version: None,
env_vars: Default::default(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: crate::managed_agents::BackendKind::Local,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ pub fn run() {
stop_managed_agent,
set_agent_managed_profiles,
set_managed_agent_start_on_app_launch,
set_managed_agent_disable_local_spawn,
set_managed_agent_auto_restart,
delete_managed_agent,
get_managed_agent_log,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/agent_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ mod tests {
persona_source_version: Some("abc123".to_string()),
env_vars: BTreeMap::from([("OPENAI_API_KEY".to_string(), "sk-secret".to_string())]),
start_on_app_launch: true,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: Some(4242),
backend: super::super::BackendKind::Provider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ mod tests {
model: None,
env_vars: std::collections::BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: crate::managed_agents::types::BackendKind::Local,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ fn minimal_record() -> ManagedAgentRecord {
m
},
start_on_app_launch: true,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: Some(12345), // MUST NOT appear
backend: BackendKind::Provider {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fn test_record() -> ManagedAgentRecord {
model: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: crate::managed_agents::types::BackendKind::Local,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/discovery/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ fn record_with(
provider: None,
persona_source_version: None,
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: Default::default(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn record(
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
runtime_pid: None,
backend: BackendKind::Local,
backend_agent_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ fn bare_record() -> ManagedAgentRecord {
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
runtime_pid: None,
backend: BackendKind::Local,
backend_agent_id: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ fn make_agent(name: &str, persona_id: Option<&str>) -> ManagedAgentRecord {
provider: None,
persona_source_version: None,
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: BackendKind::default(),
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/parallelism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ mod tests {
provider: None,
persona_source_version: None,
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: Default::default(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub(super) fn sample_record() -> ManagedAgentRecord {
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: BackendKind::Local,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/readiness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1498,6 +1498,7 @@ mod tests {
persona_source_version: None,
env_vars,
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: Default::default(),
Expand Down
6 changes: 5 additions & 1 deletion desktop/src-tauri/src/managed_agents/restore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,11 @@ pub async fn restore_managed_agents_on_launch(

let candidates: Vec<String> = records
.iter()
.filter(|record| record.start_on_app_launch && record.backend == BackendKind::Local)
.filter(|record| {
record.start_on_app_launch
&& record.backend == BackendKind::Local
&& !record.disable_local_spawn
})
.map(|record| record.pubkey.clone())
.collect();

Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,7 @@ pub fn build_managed_agent_summary(
last_error: record.last_error.clone(),
last_error_code: record.last_error_code,
start_on_app_launch: record.start_on_app_launch,
disable_local_spawn: record.disable_local_spawn,
auto_restart_on_config_change: record.auto_restart_on_config_change,
log_path,
respond_to: record.respond_to,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub(super) fn fixture(
persona_source_version: None,
env_vars: std::collections::BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: Default::default(),
Expand Down
9 changes: 8 additions & 1 deletion desktop/src-tauri/src/managed_agents/runtime_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ fn start_pair(
if record.backend != BackendKind::Local {
return Err("managed runtime pairs require a local agent".into());
}
if record.disable_local_spawn {
return Err("local spawn disabled — runtime is externally managed".into());
}
if expected_updated_at.is_some_and(|expected| record.updated_at != expected) {
return Err("managed agent changed while runtime reconciliation was in flight".into());
}
Expand Down Expand Up @@ -468,7 +471,11 @@ pub async fn reconcile_managed_agent_runtimes(
for community in communities {
for record in records
.iter()
.filter(|record| record.start_on_app_launch && record.backend == BackendKind::Local)
.filter(|record| {
record.start_on_app_launch
&& record.backend == BackendKind::Local
&& !record.disable_local_spawn
})
// The legacy per-record relay pin is deliberately ignored here — see
// `effective_agent_relay_url`. Every local auto-start agent fans out
// to every configured community.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ fn record() -> ManagedAgentRecord {
persona_source_version: None,
env_vars: BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: Default::default(),
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/team_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,7 @@ mod tests {
m
},
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: BackendKind::Local,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/teams_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ fn managed_agent(name: &str) -> ManagedAgentRecord {
persona_source_version: None,
env_vars: std::collections::BTreeMap::new(),
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: false,
runtime_pid: None,
backend: crate::managed_agents::BackendKind::Local,
Expand Down
9 changes: 9 additions & 0 deletions desktop/src-tauri/src/managed_agents/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ impl AgentDefinition {
persona_source_version: None,
env_vars: self.env_vars,
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: true,
runtime_pid: None,
backend: BackendKind::default(),
Expand Down Expand Up @@ -309,6 +310,13 @@ pub struct ManagedAgentRecord {
pub env_vars: BTreeMap<String, String>,
#[serde(default = "default_start_on_app_launch")]
pub start_on_app_launch: bool,
/// When `true`, Desktop never spawns a local process for this agent — neither
/// at boot-time reconciliation nor on reactive triggers (channel open, @mention).
/// The persona and key remain in the store so the agent can be re-activated at
/// any time by flipping this back to `false`. Intended for agents whose canonical
/// runtime is an external host (e.g. a VPS systemd unit).
#[serde(default)]
pub disable_local_spawn: bool,
/// Auto-restart this agent when its effective spawn config drifts from
/// the running process (Chunk F). Default ON; the policy loop in the
/// frontend only fires when the agent is idle, connected, and local.
Expand Down Expand Up @@ -550,6 +558,7 @@ pub struct ManagedAgentSummary {
pub last_error: Option<String>,
pub last_error_code: Option<i64>,
pub start_on_app_launch: bool,
pub disable_local_spawn: bool,
pub auto_restart_on_config_change: bool,
pub log_path: String,
pub respond_to: RespondTo,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/managed_agents/types/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,7 @@ fn summary_fixture(
last_error: None,
last_error_code: None,
start_on_app_launch: false,
disable_local_spawn: false,
auto_restart_on_config_change: false,
log_path: String::new(),
respond_to: RespondTo::OwnerOnly,
Expand Down
19 changes: 19 additions & 0 deletions desktop/src/features/agents/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import type { HarnessDefinitionInput } from "@/shared/api/tauri";
import { discoverAcpRuntimes } from "@/shared/api/tauriAcpDiscovery";
import {
setManagedAgentAutoRestart,
setManagedAgentDisableLocalSpawn,
setManagedAgentStartOnAppLaunch,
startManagedAgent,
stopManagedAgent,
Expand Down Expand Up @@ -648,6 +649,24 @@ export function useSetManagedAgentStartOnAppLaunchMutation() {
});
}

export function useSetManagedAgentDisableLocalSpawnMutation() {
const queryClient = useQueryClient();

return useMutation({
mutationFn: ({
pubkey,
disableLocalSpawn,
}: {
pubkey: string;
disableLocalSpawn: boolean;
}) => setManagedAgentDisableLocalSpawn(pubkey, disableLocalSpawn),
onSettled: async () => {
await queryClient.invalidateQueries({ queryKey: managedAgentsQueryKey });
await queryClient.invalidateQueries({ queryKey: relayAgentsQueryKey });
},
});
}

export function useDeleteManagedAgentMutation() {
const queryClient = useQueryClient();

Expand Down
6 changes: 5 additions & 1 deletion desktop/src/features/agents/ui/ManagedAgentRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,11 @@ function AgentSummary({
<PubKey pubkey={agent.pubkey} />
{agent.backend.type === "local" ? (
<span>
{agent.startOnAppLaunch ? "Auto-start" : "Manual start"}
{agent.disableLocalSpawn
? "Remote runtime"
: agent.startOnAppLaunch
? "Auto-start"
: "Manual start"}
</span>
) : (
<span>Remote deployment</span>
Expand Down
Loading