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
10 changes: 5 additions & 5 deletions desktop/src-tauri/src/commands/agent_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ use super::agent_update_rollback::{rollback_failed_agent_update, AgentUpdateRoll
use crate::{
app_state::AppState,
managed_agents::{
build_managed_agent_summary, current_instance_id, discovery_env_with_baked_floor,
find_managed_agent_mut, known_acp_runtime, load_global_agent_config, load_managed_agents,
load_personas, managed_agent_avatar_url, missing_command_message, normalize_agent_args,
resolve_command, save_managed_agents, sync_managed_agent_processes, try_regenerate_nest,
AgentModelInfo, AgentModelsResponse, ManagedAgentRecord, UpdateManagedAgentRequest,
current_instance_id, discovery_env_with_baked_floor, find_managed_agent_mut,
known_acp_runtime, load_global_agent_config, load_managed_agents, load_personas,
managed_agent_avatar_url, missing_command_message, normalize_agent_args, resolve_command,
save_managed_agents, sync_managed_agent_processes, try_regenerate_nest, AgentModelInfo,
AgentModelsResponse, ManagedAgentRecord, UpdateManagedAgentRequest,
UpdateManagedAgentResponse, DEFAULT_ACP_COMMAND,
},
relay::{relay_ws_url_with_override, sync_managed_agent_profile},
Expand Down
11 changes: 1 addition & 10 deletions desktop/src-tauri/src/commands/agent_models_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,16 +250,7 @@ pub async fn update_managed_agent(
None
};

let summary = {
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?
};
let summary = { super::super::agents::summarize_from_disk(&app, record, &runtimes)? };
let rollback = name_changed
.then(|| AgentUpdateRollback::new(previous_record, record, access_policy_changed));
(
Expand Down
23 changes: 4 additions & 19 deletions desktop/src-tauri/src/commands/agent_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ use tauri::{AppHandle, Manager, State};
use crate::{
app_state::AppState,
managed_agents::{
build_managed_agent_summary, current_instance_id, find_managed_agent_mut,
load_managed_agents, load_personas, save_managed_agents, sync_managed_agent_processes,
ManagedAgentSummary,
current_instance_id, find_managed_agent_mut, load_managed_agents, save_managed_agents,
sync_managed_agent_processes, ManagedAgentSummary,
},
util::now_iso,
};
Expand Down Expand Up @@ -56,14 +55,7 @@ pub async fn set_managed_agent_start_on_app_launch(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
super::agents::summarize_from_disk(&app, record, &runtimes)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
Expand Down Expand Up @@ -107,14 +99,7 @@ pub async fn set_managed_agent_auto_restart(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
super::agents::summarize_from_disk(&app, record, &runtimes)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
Expand Down
91 changes: 40 additions & 51 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,28 @@ pub(super) fn workspace_owner_hex(state: &AppState) -> Result<String, String> {
Ok(keys.public_key().to_hex())
}

/// Build a summary from fresh disk state (personas, teams, global config).
/// For one-shot command paths only — the 5s list poll calls
/// `build_managed_agent_summary` directly with stores loaded once per call,
/// not once per record.
pub(super) fn summarize_from_disk(
app: &AppHandle,
record: &ManagedAgentRecord,
runtimes: &std::collections::HashMap<
crate::managed_agents::ManagedAgentRuntimeKey,
crate::managed_agents::ManagedAgentPairRuntime,
>,
) -> Result<ManagedAgentSummary, String> {
build_managed_agent_summary(
app,
record,
runtimes,
&load_personas(app).unwrap_or_default(),
&load_teams(app).unwrap_or_default(),
&crate::managed_agents::load_global_agent_config(app).unwrap_or_default(),
)
}

/// Retain a freshly authored managed-agent event in the local store, flagged
/// for relay sync. MUST be called inside the `managed_agents_store_lock`-held
/// body after `save_managed_agents`, NEVER across an `.await`: it acquires
Expand Down Expand Up @@ -333,18 +355,11 @@ pub(super) async fn start_local_agent_pairs_with_preflight(
.managed_agent_processes
.lock()
.map_err(|e| e.to_string())?;
let personas = load_personas(app).unwrap_or_default();
let record = records
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
build_managed_agent_summary(
app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(app).unwrap_or_default(),
)
summarize_from_disk(app, record, &runtimes)
}

pub(super) async fn start_local_agent_with_preflight(
Expand Down Expand Up @@ -436,6 +451,7 @@ pub(super) async fn start_local_agent_with_preflight(
record,
&runtimes,
&personas,
&load_teams(app).unwrap_or_default(),
&crate::managed_agents::load_global_agent_config(app).unwrap_or_default(),
)
}
Expand Down Expand Up @@ -474,14 +490,22 @@ pub async fn list_managed_agents(app: AppHandle) -> Result<Vec<ManagedAgentSumma

let personas = load_personas(&app).unwrap_or_default();
// One disk read for the whole list — build_managed_agent_summary takes
// the config as a parameter precisely so this poll-every-5s call does
// not re-read it per record.
// teams and config as parameters precisely so this poll-every-5s call
// does not re-read them per record.
let teams = load_teams(&app).unwrap_or_default();
let global_config =
crate::managed_agents::load_global_agent_config(&app).unwrap_or_default();
records
.iter()
.map(|record| {
build_managed_agent_summary(&app, record, &runtimes, &personas, &global_config)
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&teams,
&global_config,
)
})
.collect()
})
Expand Down Expand Up @@ -854,15 +878,8 @@ pub async fn create_managed_agent(
// before any .await — owner-authored, every agent (Will's ruling: no
// is_builtin/persona-membership gate).
retain_managed_agent_pending(&app, &state, record);
let personas = load_personas(&app).unwrap_or_default();
(
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?,
summarize_from_disk(&app, record, &runtimes)?,
resolved_avatar_url,
)
};
Expand Down Expand Up @@ -891,14 +908,7 @@ pub async fn create_managed_agent(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| "created agent disappeared unexpectedly".to_string())?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?
summarize_from_disk(&app, record, &runtimes)?
}
}
} else {
Expand Down Expand Up @@ -967,14 +977,7 @@ pub async fn create_managed_agent(
.iter()
.find(|r| r.pubkey == pubkey)
.ok_or_else(|| "agent disappeared".to_string())?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)?
summarize_from_disk(&app, record, &runtimes)?
} else {
agent
};
Expand Down Expand Up @@ -1083,14 +1086,7 @@ pub async fn start_managed_agent(
.iter()
.find(|r| r.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
summarize_from_disk(&app, record, &runtimes)
}
StartTarget::Provider { backend, .. } => Err(format!(
"agent {pubkey} has unsupported backend kind: {backend:?}"
Expand Down Expand Up @@ -1171,14 +1167,7 @@ pub async fn stop_managed_agent(
.iter()
.find(|record| record.pubkey == pubkey)
.ok_or_else(|| format!("agent {pubkey} not found"))?;
let personas = load_personas(&app).unwrap_or_default();
build_managed_agent_summary(
&app,
record,
&runtimes,
&personas,
&crate::managed_agents::load_global_agent_config(&app).unwrap_or_default(),
)
summarize_from_disk(&app, record, &runtimes)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
Expand Down
12 changes: 5 additions & 7 deletions desktop/src-tauri/src/managed_agents/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ pub fn build_managed_agent_summary(
record: &ManagedAgentRecord,
runtimes: &HashMap<ManagedAgentRuntimeKey, ManagedAgentPairRuntime>,
personas: &[crate::managed_agents::types::AgentDefinition],
teams: &[crate::managed_agents::TeamRecord],
global_config: &crate::managed_agents::GlobalAgentConfig,
) -> Result<ManagedAgentSummary, String> {
use crate::managed_agents::BackendKind;
Expand Down Expand Up @@ -195,12 +196,10 @@ pub fn build_managed_agent_summary(

let (persona_out_of_date, persona_orphaned) = persona_drift_state(record, personas);

let global_for_summary =
crate::managed_agents::load_global_agent_config(app).unwrap_or_default();
let effective_cfg = crate::managed_agents::effective_config::resolve_effective_config(
record,
personas,
&global_for_summary,
global_config,
);
let (effective_model, effective_provider, effective_prompt, model_source) = match effective_cfg
{
Expand Down Expand Up @@ -242,14 +241,13 @@ pub fn build_managed_agent_summary(
// env layering below — the caller loads it once and passes it in, so
// list-style callers pay one disk read per call rather than one per record.

// The prospective side is computed only for a tracked pair: it costs a
// teams-store read, and an unstamped agent has nothing to compare against.
// The prospective side is computed only for a tracked pair: an unstamped
// agent has nothing to compare against.
let tracked_spawn = pair_key.as_ref().zip(pair_runtime).map(|(key, runtime)| {
let teams = crate::managed_agents::load_teams(app).unwrap_or_default();
let current = crate::managed_agents::spawn_snapshot::prospective_spawn_config_snapshot(
record,
personas,
&teams,
teams,
&key.relay_url,
global_config,
super::owner_only_access_build(),
Expand Down
36 changes: 25 additions & 11 deletions desktop/src/features/agents/observerRelayStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,11 +243,26 @@ function appendAgentEvents(
: events;
if (admissible.length === 0) return null;

const seen = new Set(
current.map(
(event) => `${event.timestamp.length}:${event.timestamp}:${event.seq}`,
),
);
// Ordinary live path: the harness publishes frames in order once per
// second, so the whole batch lands strictly after the retained tail. In
// that case no admissible event can collide with a retained one (the
// journal is sorted), so dedup only needs to look inside the batch and the
// merged journal is a plain concat — no Set over the full journal and no
// whole-journal re-sort (whose comparator Date.parses per comparison).
// Out-of-order or replayed arrivals take the full dedup + re-sort path.
const currentLast = current.at(-1);
const allAtEnd =
!currentLast ||
admissible.every((event) => isObserverEventAfter(event, currentLast));

const seen = allAtEnd
? new Set<string>()
: new Set(
current.map(
(event) =>
`${event.timestamp.length}:${event.timestamp}:${event.seq}`,
),
);
const added: ObserverEvent[] = [];
for (const event of admissible) {
const eventKey = `${event.timestamp.length}:${event.timestamp}:${event.seq}`;
Expand All @@ -258,7 +273,9 @@ function appendAgentEvents(
if (added.length === 0) return null;

const sortedAdded = added.sort(compareObserverEvents);
const sorted = [...current, ...sortedAdded].sort(compareObserverEvents);
const sorted = allAtEnd
? [...current, ...sortedAdded]
: [...current, ...sortedAdded].sort(compareObserverEvents);
const trimmed = sorted.length > MAX_OBSERVER_EVENTS;
const final = trimmed
? sorted.slice(sorted.length - OBSERVER_EVENTS_LOW_WATER)
Expand All @@ -276,14 +293,11 @@ function appendAgentEvents(
});
}

// The common live path appends a sorted batch after the retained window. Fold
// The common live path appends a sorted batch after the retained window
// (the same `allAtEnd` that authorized the concat fast-path above). Fold
// that batch through the transcript state once without rebuilding history.
// Out-of-order arrivals and cap eviction rebuild from the final window so
// stateful tool/permission relationships remain correct.
const currentLast = current.at(-1);
const allAtEnd =
!currentLast ||
sortedAdded.every((event) => compareObserverEvents(event, currentLast) > 0);
if (allAtEnd && !trimmed) {
let transcriptState =
transcriptByAgent.get(key) ?? createEmptyTranscriptState();
Expand Down
Loading
Loading