mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
feat(desktop): restart-required badge from spawn-time config hash (#1602)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -125,7 +125,9 @@ const overrides = new Map([
|
||||
// config-bridge-aware requirements: getRuntimeFileConfig command adds ~15 lines.
|
||||
// +26 lines from PRs landing on main between prior rebase and this rebase.
|
||||
// baked-env-required-badge: getBakedBuildEnvKeys wrapper adds ~16 lines. Queued to split.
|
||||
["src/shared/api/tauri.ts", 1388],
|
||||
// restart-badge: started the queued split — start/stopManagedAgent moved to
|
||||
// tauriManagedAgents.ts; limit ratcheted down 1388 → 1380 to bank the headroom.
|
||||
["src/shared/api/tauri.ts", 1380],
|
||||
// readiness-gate: PersonaDialog.tsx threads computeLocalModeGate +
|
||||
// requiredCredentialEnvKeys + RequiredFieldLabel so the "New agent" dialog
|
||||
// shows required markers and credential amber rows (parity with
|
||||
|
||||
@@ -22,6 +22,7 @@ mod repos;
|
||||
mod restore;
|
||||
pub mod retention;
|
||||
mod runtime;
|
||||
pub(crate) mod spawn_hash;
|
||||
mod storage;
|
||||
pub(crate) mod team_events;
|
||||
mod team_repair;
|
||||
|
||||
@@ -133,6 +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,
|
||||
agent_name: &str,
|
||||
) -> super::ManagedAgentProcess {
|
||||
let job = create_job_for_child(child.id());
|
||||
@@ -145,6 +146,7 @@ pub fn finish_spawn(
|
||||
super::ManagedAgentProcess {
|
||||
child,
|
||||
log_path,
|
||||
spawn_config_hash,
|
||||
job,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1355,6 +1355,21 @@ pub fn build_managed_agent_summary(
|
||||
// next to an "out of date" badge would contradict it.
|
||||
let (persona_out_of_date, persona_orphaned) = persona_drift_state(record, personas);
|
||||
|
||||
// Restart badge: the running process stamped its effective spawn config
|
||||
// at launch; recompute from current disk state and flag drift. Only a
|
||||
// tracked live process can drift — stopped agents spawn fresh, and
|
||||
// adopted (runtime_pid-only) processes have no stamped hash to compare.
|
||||
let needs_restart = runtimes.get(&record.pubkey).is_some_and(|runtime| {
|
||||
use tauri::Manager;
|
||||
let state = app.state::<crate::app_state::AppState>();
|
||||
runtime.spawn_config_hash
|
||||
!= crate::managed_agents::spawn_hash::spawn_config_hash(
|
||||
record,
|
||||
personas,
|
||||
&crate::relay::relay_ws_url_with_override(&state),
|
||||
)
|
||||
});
|
||||
|
||||
// Resolve the effective harness the same way, then derive args/mcp from it,
|
||||
// so the UI reflects the persona's current harness (or an explicit pin).
|
||||
let effective_command = crate::managed_agents::effective_agent_command(
|
||||
@@ -1388,6 +1403,7 @@ pub fn build_managed_agent_summary(
|
||||
provider: record.provider.clone(),
|
||||
persona_out_of_date,
|
||||
persona_orphaned,
|
||||
needs_restart,
|
||||
mcp_toolsets: record.mcp_toolsets.clone(),
|
||||
env_vars: record.env_vars.clone(),
|
||||
backend: record.backend.clone(),
|
||||
@@ -1861,6 +1877,13 @@ pub fn spawn_agent_child(
|
||||
)
|
||||
})?;
|
||||
|
||||
// Stamp the effective spawn config so the summary builder can flag
|
||||
// needs_restart when disk state drifts from what this process runs.
|
||||
// `effective_relay_url` is already resolved, and resolution is idempotent,
|
||||
// so it serves as the workspace-relay input here.
|
||||
let spawn_config_hash =
|
||||
super::spawn_hash::spawn_config_hash(record, &personas, &effective_relay_url);
|
||||
|
||||
let _ = super::write_agent_pid_file(app, &record.pubkey, child.id());
|
||||
|
||||
// Windows: assign the harness to a Job Object so its whole tree dies with
|
||||
@@ -1869,10 +1892,15 @@ pub fn spawn_agent_child(
|
||||
return Ok(super::process_lifecycle::finish_spawn(
|
||||
child,
|
||||
log_path,
|
||||
spawn_config_hash,
|
||||
&record.name,
|
||||
));
|
||||
#[cfg(not(windows))]
|
||||
Ok(crate::managed_agents::ManagedAgentProcess { child, log_path })
|
||||
Ok(crate::managed_agents::ManagedAgentProcess {
|
||||
child,
|
||||
log_path,
|
||||
spawn_config_hash,
|
||||
})
|
||||
}
|
||||
|
||||
fn child_rust_log_filter() -> String {
|
||||
|
||||
111
desktop/src-tauri/src/managed_agents/spawn_hash.rs
Normal file
111
desktop/src-tauri/src/managed_agents/spawn_hash.rs
Normal file
@@ -0,0 +1,111 @@
|
||||
//! Spawn-time config hash for the restart-required badge.
|
||||
//!
|
||||
//! [`spawn_config_hash`] digests the *effective spawned values* — what a
|
||||
//! process launch of `record` would actually receive — so the UI can compare
|
||||
//! a running process's hash (stamped on [`super::ManagedAgentProcess`] at
|
||||
//! spawn) against a recomputation from current disk state and show a
|
||||
//! "restart required" badge only when a restart would change what runs.
|
||||
//!
|
||||
//! Scope rules (decided in #centralize-personas-and-agents, revised in PR
|
||||
//! #1602 review):
|
||||
//! - Inputs mirror what a start would actually run: the start/restore paths
|
||||
//! re-snapshot the linked persona's prompt/model/provider/env onto the
|
||||
//! record immediately before spawning (`start_local_agent_with_preflight`,
|
||||
//! `restore_managed_agents_on_launch`), so persona edits to those fields DO
|
||||
//! apply on a plain restart and are hashed via the same prospective
|
||||
//! re-snapshot. Harness command, args/mcp, env layering, and the record
|
||||
//! fields the spawn env writes read are hashed as spawn resolves them.
|
||||
//! - The relay URL is hashed in resolved form (`effective_agent_relay_url`):
|
||||
//! a record with a blank relay spawns against the active workspace relay,
|
||||
//! so a workspace relay change means a restart would change what runs.
|
||||
//! - Channel membership is not an input: agents pick up channel changes live
|
||||
//! (#1468), never via restart.
|
||||
//!
|
||||
//! The hash never crosses a process or persistence boundary, so
|
||||
//! `DefaultHasher` (not stable across Rust releases) is sufficient.
|
||||
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
|
||||
use super::{
|
||||
effective_agent_command, known_acp_runtime, normalize_agent_args,
|
||||
persona_events::persona_snapshot_with_agent_config_fallback,
|
||||
resolve_effective_agent_env,
|
||||
types::{ManagedAgentRecord, PersonaRecord},
|
||||
};
|
||||
|
||||
/// Digest the effective spawn configuration of `record` under the current
|
||||
/// `personas`, resolving a blank record relay against `workspace_relay`.
|
||||
/// Pure — no `AppHandle`, no disk, no keyring.
|
||||
pub(crate) fn spawn_config_hash(
|
||||
record: &ManagedAgentRecord,
|
||||
personas: &[PersonaRecord],
|
||||
workspace_relay: &str,
|
||||
) -> u64 {
|
||||
// Prospective re-snapshot: mirror the mutation start/restore apply to the
|
||||
// record right before spawning, so the hash covers what a restart would
|
||||
// actually run. Idempotent, so the spawn-time stamp (post-snapshot record)
|
||||
// and later recomputes (persisted record) agree when nothing changed.
|
||||
let mut record = record.clone();
|
||||
if let Some(persona_id) = record.persona_id.clone() {
|
||||
if let Some(persona) = personas.iter().find(|p| p.id == persona_id) {
|
||||
let snapshot = persona_snapshot_with_agent_config_fallback(
|
||||
persona,
|
||||
&record.env_vars,
|
||||
record.model.as_deref(),
|
||||
record.provider.as_deref(),
|
||||
);
|
||||
if let Some(prompt) = snapshot.system_prompt {
|
||||
record.system_prompt = Some(prompt);
|
||||
}
|
||||
record.model = snapshot.model;
|
||||
record.provider = snapshot.provider;
|
||||
record.env_vars = snapshot.env_vars;
|
||||
}
|
||||
}
|
||||
let record = &record;
|
||||
|
||||
let effective_command = effective_agent_command(
|
||||
record.persona_id.as_deref(),
|
||||
personas,
|
||||
record.agent_command_override.as_deref(),
|
||||
);
|
||||
let runtime_meta = known_acp_runtime(&effective_command);
|
||||
let effective = resolve_effective_agent_env(record, personas, runtime_meta);
|
||||
|
||||
let mut hasher = DefaultHasher::new();
|
||||
|
||||
// Harness identity and derivations (live-persona-resolved, like spawn).
|
||||
record.acp_command.hash(&mut hasher);
|
||||
effective_command.hash(&mut hasher);
|
||||
normalize_agent_args(&effective_command, record.agent_args.clone()).hash(&mut hasher);
|
||||
runtime_meta
|
||||
.and_then(|r| r.mcp_command)
|
||||
.unwrap_or("")
|
||||
.hash(&mut hasher);
|
||||
|
||||
// Effective env layering (baked floor → runtime metadata → user env).
|
||||
// BTreeMap iteration is ordered, so this is deterministic.
|
||||
effective.env.hash(&mut hasher);
|
||||
|
||||
// Record fields the spawn env writes read directly. The relay is hashed
|
||||
// resolved: a blank record relay spawns on the workspace relay, so a
|
||||
// workspace relay change must trip the badge.
|
||||
crate::relay::effective_agent_relay_url(&record.relay_url, workspace_relay).hash(&mut hasher);
|
||||
record.system_prompt.hash(&mut hasher);
|
||||
record.model.hash(&mut hasher);
|
||||
record.provider.hash(&mut hasher);
|
||||
record.auth_tag.hash(&mut hasher);
|
||||
record.respond_to.as_str().hash(&mut hasher);
|
||||
record.respond_to_allowlist.hash(&mut hasher);
|
||||
record.idle_timeout_seconds.hash(&mut hasher);
|
||||
record.max_turn_duration_seconds.hash(&mut hasher);
|
||||
record.parallelism.hash(&mut hasher);
|
||||
record.mcp_toolsets.hash(&mut hasher);
|
||||
record.persona_team_dir.hash(&mut hasher);
|
||||
record.persona_name_in_team.hash(&mut hasher);
|
||||
|
||||
hasher.finish()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
177
desktop/src-tauri/src/managed_agents/spawn_hash/tests.rs
Normal file
177
desktop/src-tauri/src/managed_agents/spawn_hash/tests.rs
Normal file
@@ -0,0 +1,177 @@
|
||||
use super::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
fn record() -> ManagedAgentRecord {
|
||||
ManagedAgentRecord {
|
||||
pubkey: "p".repeat(64),
|
||||
name: "agent".into(),
|
||||
persona_id: None,
|
||||
private_key_nsec: "nsec1fake".into(),
|
||||
auth_tag: None,
|
||||
relay_url: "ws://localhost:3000".into(),
|
||||
avatar_url: None,
|
||||
acp_command: "buzz-acp".into(),
|
||||
agent_command: "goose".into(),
|
||||
agent_command_override: None,
|
||||
agent_args: vec![],
|
||||
mcp_command: String::new(),
|
||||
turn_timeout_seconds: 320,
|
||||
idle_timeout_seconds: None,
|
||||
max_turn_duration_seconds: None,
|
||||
parallelism: 1,
|
||||
system_prompt: Some("You are a test agent.".into()),
|
||||
model: None,
|
||||
provider: None,
|
||||
persona_source_version: None,
|
||||
mcp_toolsets: None,
|
||||
env_vars: BTreeMap::new(),
|
||||
start_on_app_launch: false,
|
||||
runtime_pid: None,
|
||||
backend: Default::default(),
|
||||
backend_agent_id: None,
|
||||
provider_binary_path: None,
|
||||
persona_team_dir: None,
|
||||
persona_name_in_team: None,
|
||||
created_at: "now".into(),
|
||||
updated_at: "now".into(),
|
||||
last_started_at: None,
|
||||
last_stopped_at: None,
|
||||
last_exit_code: None,
|
||||
last_error: None,
|
||||
respond_to: Default::default(),
|
||||
respond_to_allowlist: vec![],
|
||||
relay_mesh: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn persona(id: &str, runtime: Option<&str>, prompt: &str) -> PersonaRecord {
|
||||
PersonaRecord {
|
||||
id: id.into(),
|
||||
display_name: id.into(),
|
||||
avatar_url: None,
|
||||
system_prompt: prompt.into(),
|
||||
runtime: runtime.map(str::to_string),
|
||||
model: None,
|
||||
provider: None,
|
||||
name_pool: vec![],
|
||||
is_builtin: false,
|
||||
is_active: true,
|
||||
source_team: None,
|
||||
source_team_persona_slug: None,
|
||||
env_vars: BTreeMap::new(),
|
||||
created_at: "now".into(),
|
||||
updated_at: "now".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_is_deterministic() {
|
||||
let rec = record();
|
||||
assert_eq!(
|
||||
spawn_config_hash(&rec, &[], "wss://ws.example"),
|
||||
spawn_config_hash(&rec, &[], "wss://ws.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_env_var_edit_changes_hash() {
|
||||
let rec = record();
|
||||
let mut edited = record();
|
||||
edited
|
||||
.env_vars
|
||||
.insert("SOME_KEY".into(), "some-value".into());
|
||||
assert_ne!(
|
||||
spawn_config_hash(&rec, &[], "wss://ws.example"),
|
||||
spawn_config_hash(&edited, &[], "wss://ws.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_prompt_edit_changes_hash() {
|
||||
let rec = record();
|
||||
let mut edited = record();
|
||||
edited.system_prompt = Some("Edited prompt.".into());
|
||||
assert_ne!(
|
||||
spawn_config_hash(&rec, &[], "wss://ws.example"),
|
||||
spawn_config_hash(&edited, &[], "wss://ws.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persona_runtime_edit_changes_hash() {
|
||||
// The harness command resolves live personas at spawn, so a persona
|
||||
// runtime change means a restart WOULD change what runs → badge trips.
|
||||
let mut rec = record();
|
||||
rec.persona_id = Some("pers".into());
|
||||
let before = [persona("pers", Some("goose"), "prompt")];
|
||||
let after = [persona("pers", Some("claude"), "prompt")];
|
||||
assert_ne!(
|
||||
spawn_config_hash(&rec, &before, "wss://ws.example"),
|
||||
spawn_config_hash(&rec, &after, "wss://ws.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn persona_prompt_edit_changes_hash() {
|
||||
// Start/restore re-snapshot the persona prompt onto the record right
|
||||
// before spawning, so a persona prompt edit DOES apply on a plain
|
||||
// restart → the badge must trip.
|
||||
let mut rec = record();
|
||||
rec.persona_id = Some("pers".into());
|
||||
let before = [persona("pers", Some("goose"), "old prompt")];
|
||||
let after = [persona("pers", Some("goose"), "new prompt")];
|
||||
assert_ne!(
|
||||
spawn_config_hash(&rec, &before, "wss://ws.example"),
|
||||
spawn_config_hash(&rec, &after, "wss://ws.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_relay_change_trips_hash_for_blank_record_relay() {
|
||||
// A blank record relay spawns against the active workspace relay, so a
|
||||
// workspace relay change means a restart would change what runs.
|
||||
let mut rec = record();
|
||||
rec.relay_url = String::new();
|
||||
assert_ne!(
|
||||
spawn_config_hash(&rec, &[], "wss://relay-a.example"),
|
||||
spawn_config_hash(&rec, &[], "wss://relay-b.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn workspace_relay_change_ignored_for_pinned_record_relay() {
|
||||
// An explicit per-agent relay pins the agent regardless of workspace, so
|
||||
// a workspace relay change must NOT badge a pinned agent.
|
||||
let rec = record();
|
||||
assert_eq!(
|
||||
spawn_config_hash(&rec, &[], "wss://relay-a.example"),
|
||||
spawn_config_hash(&rec, &[], "wss://relay-b.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn respond_to_allowlist_edit_changes_hash() {
|
||||
let rec = record();
|
||||
let mut edited = record();
|
||||
edited.respond_to_allowlist = vec!["a".repeat(64)];
|
||||
assert_ne!(
|
||||
spawn_config_hash(&rec, &[], "wss://ws.example"),
|
||||
spawn_config_hash(&edited, &[], "wss://ws.example")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_spawn_bookkeeping_fields_do_not_change_hash() {
|
||||
// updated_at / runtime_pid / last_* are lifecycle bookkeeping, not spawn
|
||||
// inputs — routine record saves must not trip the badge.
|
||||
let rec = record();
|
||||
let mut edited = record();
|
||||
edited.updated_at = "later".into();
|
||||
edited.runtime_pid = Some(12345);
|
||||
edited.last_started_at = Some("later".into());
|
||||
edited.last_exit_code = Some(0);
|
||||
assert_eq!(
|
||||
spawn_config_hash(&rec, &[], "wss://ws.example"),
|
||||
spawn_config_hash(&edited, &[], "wss://ws.example")
|
||||
);
|
||||
}
|
||||
@@ -240,6 +240,13 @@ pub struct RelayMeshConfig {
|
||||
pub struct ManagedAgentProcess {
|
||||
pub child: Child,
|
||||
pub log_path: PathBuf,
|
||||
/// Digest of the effective spawn config at launch (see
|
||||
/// `spawn_hash::spawn_config_hash`). Runtime-only — never persisted. The
|
||||
/// summary builder recomputes the hash from current disk state and flags
|
||||
/// `needs_restart` on mismatch. Agents adopted via a persisted
|
||||
/// `runtime_pid` have no `ManagedAgentProcess` entry, so their spawn
|
||||
/// config is unknown and the badge stays off.
|
||||
pub spawn_config_hash: u64,
|
||||
/// Win32 Job Object owning the harness + its entire process tree. Closing
|
||||
/// the handle (via `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`) kills the whole
|
||||
/// tree — the Windows mirror of the Unix process-group teardown. `None`
|
||||
@@ -283,6 +290,13 @@ pub struct ManagedAgentSummary {
|
||||
/// so the UI should not prompt a respawn — the pinned snapshot is all the
|
||||
/// config that remains.
|
||||
pub persona_orphaned: bool,
|
||||
/// `true` when the running process was spawned with a config that no
|
||||
/// longer matches what a spawn would use today — a plain restart would
|
||||
/// change what runs. Complements `persona_out_of_date`: the badge means
|
||||
/// "a restart would change what runs"; out-of-date means "a respawn
|
||||
/// would." Always `false` for stopped agents and for processes adopted
|
||||
/// via a persisted `runtime_pid` (their spawn config is unknown).
|
||||
pub needs_restart: bool,
|
||||
pub mcp_toolsets: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub env_vars: BTreeMap<String, String>,
|
||||
|
||||
@@ -12,9 +12,9 @@ import {
|
||||
createManagedAgent,
|
||||
getChannelMembers,
|
||||
listManagedAgents,
|
||||
startManagedAgent,
|
||||
updateManagedAgent,
|
||||
} from "@/shared/api/tauri";
|
||||
import { startManagedAgent } from "@/shared/api/tauriManagedAgents";
|
||||
import type {
|
||||
AcpRuntime,
|
||||
ChannelRole,
|
||||
|
||||
@@ -21,10 +21,13 @@ import {
|
||||
installAcpRuntime,
|
||||
listManagedAgents,
|
||||
listRelayAgents,
|
||||
startManagedAgent,
|
||||
stopManagedAgent,
|
||||
updateManagedAgent,
|
||||
} from "@/shared/api/tauri";
|
||||
import {
|
||||
setManagedAgentStartOnAppLaunch,
|
||||
startManagedAgent,
|
||||
stopManagedAgent,
|
||||
} from "@/shared/api/tauriManagedAgents";
|
||||
import {
|
||||
createPersona,
|
||||
deletePersona,
|
||||
@@ -33,7 +36,6 @@ import {
|
||||
setPersonaActive,
|
||||
updatePersona,
|
||||
} from "@/shared/api/tauriPersonas";
|
||||
import { setManagedAgentStartOnAppLaunch } from "@/shared/api/tauriManagedAgents";
|
||||
import {
|
||||
createTeam,
|
||||
deleteTeam,
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { AlertTriangle, ChevronDown, ChevronRight } from "lucide-react";
|
||||
import {
|
||||
AlertTriangle,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
RefreshCw,
|
||||
} from "lucide-react";
|
||||
|
||||
import { useAppNavigation } from "@/app/navigation/useAppNavigation";
|
||||
import { PresenceDot } from "@/features/presence/ui/PresenceBadge";
|
||||
@@ -229,6 +234,12 @@ function AgentSummary({
|
||||
<Badge variant="secondary">{personaLabel}</Badge>
|
||||
) : null}
|
||||
<AgentOriginBadge agent={agent} />
|
||||
{agent.needsRestart ? (
|
||||
<Badge className="gap-1" variant="warning">
|
||||
<RefreshCw className="h-3 w-3" />
|
||||
Restart required
|
||||
</Badge>
|
||||
) : null}
|
||||
{agent.personaOutOfDate ? (
|
||||
<Badge className="gap-1" variant="warning">
|
||||
<AlertTriangle className="h-3 w-3" />
|
||||
@@ -246,6 +257,12 @@ function AgentSummary({
|
||||
<span>Remote deployment</span>
|
||||
)}
|
||||
</div>
|
||||
{agent.needsRestart ? (
|
||||
<p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400">
|
||||
Configuration changed since this agent started. Restart to apply
|
||||
it.
|
||||
</p>
|
||||
) : null}
|
||||
{agent.personaOutOfDate ? (
|
||||
<p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400">
|
||||
Persona updated since this agent was created. Respawn to apply the
|
||||
|
||||
@@ -215,6 +215,7 @@ export type RawManagedAgent = {
|
||||
provider: string | null;
|
||||
persona_out_of_date: boolean;
|
||||
persona_orphaned: boolean;
|
||||
needs_restart: boolean;
|
||||
mcp_toolsets: string | null;
|
||||
env_vars?: Record<string, string>;
|
||||
status: ManagedAgent["status"];
|
||||
@@ -991,6 +992,7 @@ export function fromRawManagedAgent(agent: RawManagedAgent): ManagedAgent {
|
||||
provider: agent.provider ?? null,
|
||||
personaOutOfDate: agent.persona_out_of_date ?? false,
|
||||
personaOrphaned: agent.persona_orphaned ?? false,
|
||||
needsRestart: agent.needs_restart ?? false,
|
||||
mcpToolsets: agent.mcp_toolsets,
|
||||
envVars: agent.env_vars ?? {},
|
||||
status: agent.status,
|
||||
@@ -1161,20 +1163,6 @@ export async function createManagedAgent(input: CreateManagedAgentInput) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function startManagedAgent(pubkey: string): Promise<ManagedAgent> {
|
||||
const response = await invokeTauri<RawManagedAgent>("start_managed_agent", {
|
||||
pubkey,
|
||||
});
|
||||
return fromRawManagedAgent(response);
|
||||
}
|
||||
|
||||
export async function stopManagedAgent(pubkey: string): Promise<ManagedAgent> {
|
||||
const response = await invokeTauri<RawManagedAgent>("stop_managed_agent", {
|
||||
pubkey,
|
||||
});
|
||||
return fromRawManagedAgent(response);
|
||||
}
|
||||
|
||||
export async function deleteManagedAgent(
|
||||
pubkey: string,
|
||||
forceRemoteDelete?: boolean,
|
||||
|
||||
@@ -5,6 +5,20 @@ import {
|
||||
} from "@/shared/api/tauri";
|
||||
import type { ManagedAgent } from "@/shared/api/types";
|
||||
|
||||
export async function startManagedAgent(pubkey: string): Promise<ManagedAgent> {
|
||||
const response = await invokeTauri<RawManagedAgent>("start_managed_agent", {
|
||||
pubkey,
|
||||
});
|
||||
return fromRawManagedAgent(response);
|
||||
}
|
||||
|
||||
export async function stopManagedAgent(pubkey: string): Promise<ManagedAgent> {
|
||||
const response = await invokeTauri<RawManagedAgent>("stop_managed_agent", {
|
||||
pubkey,
|
||||
});
|
||||
return fromRawManagedAgent(response);
|
||||
}
|
||||
|
||||
export async function setManagedAgentStartOnAppLaunch(
|
||||
pubkey: string,
|
||||
startOnAppLaunch: boolean,
|
||||
|
||||
@@ -398,6 +398,13 @@ export type ManagedAgent = {
|
||||
* a respawn — the pinned snapshot is all the config that remains.
|
||||
*/
|
||||
personaOrphaned: boolean;
|
||||
/**
|
||||
* `true` when the running process was spawned with a config that no longer
|
||||
* matches what a spawn would use today — a plain restart would change what
|
||||
* runs. Complements `personaOutOfDate` ("a respawn would change it").
|
||||
* Always `false` for stopped agents.
|
||||
*/
|
||||
needsRestart: boolean;
|
||||
mcpToolsets: string | null;
|
||||
/** Per-agent env vars. Layered on top of persona envVars. */
|
||||
envVars: Record<string, string>;
|
||||
|
||||
Reference in New Issue
Block a user