From 8b69b411cdc429d4747a8c1a5cbd03230ce83497 Mon Sep 17 00:00:00 2001 From: Fizz Date: Fri, 28 Aug 2026 11:18:05 -0400 Subject: [PATCH] refactor(desktop): keep agent storage within size gate On-behalf-of: mahanti Signed-off-by: Fizz Co-authored-by: Codex --- .../src-tauri/src/managed_agents/storage.rs | 134 ++---------------- .../managed_agents/storage/build_filter.rs | 131 +++++++++++++++++ 2 files changed, 140 insertions(+), 125 deletions(-) create mode 100644 desktop/src-tauri/src/managed_agents/storage/build_filter.rs diff --git a/desktop/src-tauri/src/managed_agents/storage.rs b/desktop/src-tauri/src/managed_agents/storage.rs index 099eaec36e..faeb21ff2a 100644 --- a/desktop/src-tauri/src/managed_agents/storage.rs +++ b/desktop/src-tauri/src/managed_agents/storage.rs @@ -13,6 +13,15 @@ use crate::managed_agents::{ }; use crate::secret_store::{KeyringProbe, SecretStore}; +mod build_filter; + +use build_filter::instances_for_save; +pub use build_filter::load_managed_agents; +pub(crate) use build_filter::{ + filter_agent_definitions_for_build, filter_managed_agents_for_build, load_agent_definitions, + load_agent_definitions_unfiltered, load_agent_store_from_path, +}; + /// Keyring key name for an agent's nsec, namespaced from the human identity /// key (`"identity"`) which shares the service. fn agent_keyring_name(pubkey: &str) -> String { @@ -245,89 +254,6 @@ fn load_agent_store( load_agent_store_from_path(&path) } -pub(crate) fn load_agent_store_from_path(path: &Path) -> Result, String> { - if !path.exists() { - return Ok(Vec::new()); - } - - let content = - fs::read_to_string(path).map_err(|error| format!("failed to read agent store: {error}"))?; - serde_json::from_str(&content).map_err(|error| { - // Fail loudly and preserve the evidence: a later in-app save rewrites - // this file wholesale, which would silently destroy a malformed hand - // edit. Best-effort file-authoring contract (see managed_agents:: - // reconcile): the broken content survives as `.invalid` for the user - // to recover, and the parse error propagates instead of being - // swallowed into an empty store. - backup_invalid_store(path); - format!("failed to parse agent store (preserved as .invalid): {error}") - }) -} - -/// Load the keyed agent *instances*. Key-less definitions (former personas, -/// folded into the same store) are filtered out so every pre-fold call site -/// keeps seeing exactly the records it always did. -pub fn load_managed_agents( - app: &AppHandle, -) -> Result, String> { - let mut records = load_agent_store(app)?; - filter_managed_agents_for_build( - &mut records, - crate::managed_agents::personas::bestie_build_enabled(), - ); - hydrate_keys(&mut records); - Ok(records) -} - -pub(crate) fn filter_managed_agents_for_build( - records: &mut Vec, - include_bestie: bool, -) { - records.retain(|record| { - !record.pubkey.is_empty() - && record.persona_id.as_deref().is_none_or(|persona_id| { - crate::managed_agents::personas::persona_available_in_build( - persona_id, - include_bestie, - ) - }) - }); -} - -/// Load the key-less agent *definitions* (former personas) from the unified -/// store. The persona compatibility shim (`load_personas`) presents these in -/// the legacy shape via `to_definition_view`. -pub(crate) fn load_agent_definitions( - app: &AppHandle, -) -> Result, String> { - let mut records = load_agent_definitions_unfiltered(app)?; - filter_agent_definitions_for_build( - &mut records, - crate::managed_agents::personas::bestie_build_enabled(), - ); - Ok(records) -} - -pub(crate) fn filter_agent_definitions_for_build( - records: &mut Vec, - include_bestie: bool, -) { - records.retain(|record| { - record.pubkey.is_empty() - && record.slug.as_deref().is_none_or(|slug| { - crate::managed_agents::personas::persona_available_in_build(slug, include_bestie) - }) - }); -} - -pub(crate) fn load_agent_definitions_unfiltered( - app: &AppHandle, -) -> Result, String> { - let mut records = load_agent_store(app)?; - records.retain(|record| record.pubkey.is_empty()); - Ok(records) -} - /// Preserve a malformed store file as `.invalid` before the error path /// unwinds. Copy, not rename: the original stays in place so repeated boots /// keep failing loudly (rename would make the next launch look like a fresh @@ -443,48 +369,6 @@ pub fn save_managed_agents( write_agent_store(app, definitions, sorted) } -fn instances_for_save( - records: &[ManagedAgentRecord], - existing: &[ManagedAgentRecord], - include_bestie: bool, -) -> Vec { - let mut complete: Vec<_> = records - .iter() - .filter(|record| { - !record.pubkey.is_empty() - && record.persona_id.as_deref().is_none_or(|persona_id| { - crate::managed_agents::personas::persona_available_in_build( - persona_id, - include_bestie, - ) - }) - }) - .cloned() - .collect(); - - if !include_bestie { - let hidden: Vec<_> = existing - .iter() - .filter(|record| { - !record.pubkey.is_empty() - && record.persona_id.as_deref().is_some_and(|persona_id| { - !crate::managed_agents::personas::persona_available_in_build( - persona_id, - include_bestie, - ) - }) - && !complete - .iter() - .any(|candidate| candidate.pubkey == record.pubkey) - }) - .cloned() - .collect(); - complete.extend(hidden); - } - - complete -} - /// Save the key-less agent *definitions*, preserving the keyed instances — /// the definition-side mirror of [`save_managed_agents`]. pub(crate) fn save_agent_definitions( diff --git a/desktop/src-tauri/src/managed_agents/storage/build_filter.rs b/desktop/src-tauri/src/managed_agents/storage/build_filter.rs new file mode 100644 index 0000000000..26c64cbcda --- /dev/null +++ b/desktop/src-tauri/src/managed_agents/storage/build_filter.rs @@ -0,0 +1,131 @@ +use std::{fs, path::Path}; + +use crate::managed_agents::ManagedAgentRecord; +use tauri::AppHandle; + +use super::{backup_invalid_store, hydrate_keys, load_agent_store}; + +pub(crate) fn load_agent_store_from_path(path: &Path) -> Result, String> { + if !path.exists() { + return Ok(Vec::new()); + } + + let content = + fs::read_to_string(path).map_err(|error| format!("failed to read agent store: {error}"))?; + serde_json::from_str(&content).map_err(|error| { + // Fail loudly and preserve the evidence: a later in-app save rewrites + // this file wholesale, which would silently destroy a malformed hand + // edit. Best-effort file-authoring contract (see managed_agents:: + // reconcile): the broken content survives as `.invalid` for the user + // to recover, and the parse error propagates instead of being + // swallowed into an empty store. + backup_invalid_store(path); + format!("failed to parse agent store (preserved as .invalid): {error}") + }) +} + +/// Load the keyed agent *instances*. Key-less definitions (former personas, +/// folded into the same store) are filtered out so every pre-fold call site +/// keeps seeing exactly the records it always did. +pub fn load_managed_agents( + app: &AppHandle, +) -> Result, String> { + let mut records = load_agent_store(app)?; + filter_managed_agents_for_build( + &mut records, + crate::managed_agents::personas::bestie_build_enabled(), + ); + hydrate_keys(&mut records); + Ok(records) +} + +/// Load the key-less agent *definitions* (former personas) from the unified +/// store. The persona compatibility shim (`load_personas`) presents these in +/// the legacy shape via `to_definition_view`. +pub(crate) fn load_agent_definitions( + app: &AppHandle, +) -> Result, String> { + let mut records = load_agent_definitions_unfiltered(app)?; + filter_agent_definitions_for_build( + &mut records, + crate::managed_agents::personas::bestie_build_enabled(), + ); + Ok(records) +} + +pub(crate) fn load_agent_definitions_unfiltered( + app: &AppHandle, +) -> Result, String> { + let mut records = load_agent_store(app)?; + records.retain(|record| record.pubkey.is_empty()); + Ok(records) +} + +pub(crate) fn filter_managed_agents_for_build( + records: &mut Vec, + include_bestie: bool, +) { + records.retain(|record| { + !record.pubkey.is_empty() + && record.persona_id.as_deref().is_none_or(|persona_id| { + crate::managed_agents::personas::persona_available_in_build( + persona_id, + include_bestie, + ) + }) + }); +} + +pub(crate) fn filter_agent_definitions_for_build( + records: &mut Vec, + include_bestie: bool, +) { + records.retain(|record| { + record.pubkey.is_empty() + && record.slug.as_deref().is_none_or(|slug| { + crate::managed_agents::personas::persona_available_in_build(slug, include_bestie) + }) + }); +} + +pub(super) fn instances_for_save( + records: &[ManagedAgentRecord], + existing: &[ManagedAgentRecord], + include_bestie: bool, +) -> Vec { + let mut complete: Vec<_> = records + .iter() + .filter(|record| { + !record.pubkey.is_empty() + && record.persona_id.as_deref().is_none_or(|persona_id| { + crate::managed_agents::personas::persona_available_in_build( + persona_id, + include_bestie, + ) + }) + }) + .cloned() + .collect(); + + if !include_bestie { + let hidden: Vec<_> = existing + .iter() + .filter(|record| { + !record.pubkey.is_empty() + && record.persona_id.as_deref().is_some_and(|persona_id| { + !crate::managed_agents::personas::persona_available_in_build( + persona_id, + include_bestie, + ) + }) + && !complete + .iter() + .any(|candidate| candidate.pubkey == record.pubkey) + }) + .cloned() + .collect(); + complete.extend(hidden); + } + + complete +}