Skip to content
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
d6fe9de
Refine agent catalog sharing
klopez4212 Jul 22, 2026
ea89b93
Stack team card avatars
klopez4212 Jul 23, 2026
d41e6cc
Simplify team avatar stack styling
klopez4212 Jul 23, 2026
1320d64
Add catalog update publishing
klopez4212 Jul 23, 2026
eddd919
Clarify catalog copy behavior
klopez4212 Jul 23, 2026
b561105
Use community in catalog sharing copy
klopez4212 Jul 23, 2026
691b305
Update agent creation test selectors
klopez4212 Jul 23, 2026
40301c3
Constrain shared agent catalog details
klopez4212 Jul 23, 2026
c7aa81a
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 23, 2026
f08ab69
Align catalog publish option with dialog footer
klopez4212 Jul 23, 2026
6699102
Place catalog publish action before toggle
klopez4212 Jul 23, 2026
a1570f1
Group catalog sharing with link controls
klopez4212 Jul 23, 2026
20afa18
Use catalog sharing access menu
klopez4212 Jul 23, 2026
5dfc793
Keep copy link action last
klopez4212 Jul 23, 2026
597bdf0
Default catalog sharing to off
klopez4212 Jul 23, 2026
4873d35
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 23, 2026
0520fe0
Publish catalog updates on save
klopez4212 Jul 23, 2026
453dd7a
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 23, 2026
2b8f1d7
Publish agent catalog entries to relay
klopez4212 Jul 23, 2026
d89b7db
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 23, 2026
e17d283
Fix catalog snapshot test after main merge
klopez4212 Jul 23, 2026
2a780d4
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 24, 2026
a7c5be1
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 24, 2026
e2abf33
fix(desktop): update agent creation e2e selector
klopez4212 Jul 24, 2026
b72e024
fix(desktop): align agent header with card grid
klopez4212 Jul 24, 2026
e09fa64
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 24, 2026
dd22a7c
fix(desktop): harden catalog publication imports
klopez4212 Jul 24, 2026
e549945
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 25, 2026
d211ace
Use persona events for catalog sharing
klopez4212 Jul 26, 2026
a305bc9
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 26, 2026
ec4ec14
Stabilize agent catalog E2E coverage
klopez4212 Jul 27, 2026
fe36d15
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 27, 2026
d1005cd
Keep merged agent files within size limits
klopez4212 Jul 27, 2026
53c3245
Fix portable descendant probe fixture
klopez4212 Jul 27, 2026
8eba922
Scope catalog sharing to communities
klopez4212 Jul 27, 2026
9f082be
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
klopez4212 Jul 27, 2026
fbc9d51
fix(desktop): migrate the legacy global retention db into the active …
wpfleger96 Jul 27, 2026
4340f83
fix(desktop): keep agent list and edits working in recovery mode
wpfleger96 Jul 27, 2026
b6439c7
feat(desktop): add update_persona_and_publish for save-and-publish
wpfleger96 Jul 27, 2026
5fffd16
feat(desktop): track catalog provenance on a copied persona
wpfleger96 Jul 27, 2026
ac9f33d
fix(desktop): scope an inbound persona event to the relay it arrived on
wpfleger96 Jul 27, 2026
89b055e
fix(desktop): honor publishCatalogUpdates on "Save and publish"
wpfleger96 Jul 27, 2026
90cba05
fix(desktop): stop re-adding an already-added foreign catalog agent
wpfleger96 Jul 27, 2026
97ab227
fix(desktop): page the persona catalog past the relay's 1,000-row clamp
wpfleger96 Jul 27, 2026
954cc23
test(desktop): stop the e2e mock publishing from update_persona
wpfleger96 Jul 27, 2026
09afe06
Merge branch 'main' into kennylopez-agent-catalog-sharing
wpfleger96 Jul 27, 2026
184deb2
Merge branch 'main' into kennylopez-agent-catalog-sharing
wpfleger96 Jul 27, 2026
ccba0c1
fix(desktop): label the catalog share toggle Shared instead of Agent …
wpfleger96 Jul 27, 2026
36af667
fix(desktop): unify the share dialog memory level into one selector
wpfleger96 Jul 27, 2026
b248eee
fix(desktop): group the share dialog's delivery rows above its options
wpfleger96 Jul 28, 2026
2a38069
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
wpfleger96 Jul 28, 2026
9080a79
refactor(desktop): remove the Active in communities settings card
wpfleger96 Jul 28, 2026
0b3e93b
fix(desktop): keep emoji avatars on catalog entries
wpfleger96 Jul 28, 2026
7980428
test(desktop): cover emoji avatars on catalog entries
wpfleger96 Jul 28, 2026
cd3e8c2
Merge remote-tracking branch 'origin/main' into kennylopez-agent-cata…
wpfleger96 Jul 28, 2026
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
5 changes: 3 additions & 2 deletions desktop/src-tauri/src/commands/agent_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ use crate::{

/// Subset of the goose file config exposed to the frontend for gate evaluation.
///
/// Only the fields the dialog gate needs — not the full `RuntimeConfigSurface`.
/// The gate uses this to know which requirements are already satisfied in the
/// Only the fields the dialog gate needs. This tracks which requirements are already satisfied in the
/// harness config file, so it can show "Set in goose config" rather than
/// surfacing a false missing-key marker.
#[derive(Debug, Serialize)]
Expand Down Expand Up @@ -685,6 +684,7 @@ mod tests {
name_pool: Vec::new(),
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
definition_respond_to: None,
Expand All @@ -709,6 +709,7 @@ mod tests {
name_pool: Vec::new(),
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
env_vars: Default::default(),
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/commands/agent_models_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ fn model_discovery_ignores_stale_record_for_linked_agent() {
name_pool: Vec::new(),
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
env_vars: BTreeMap::new(),
Expand Down
41 changes: 18 additions & 23 deletions desktop/src-tauri/src/commands/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ use crate::{
managed_agents::{
build_managed_agent_summary, current_instance_id, discover_provider_candidates,
ensure_persona_is_active, find_managed_agent_mut, load_managed_agents, load_personas,
load_teams, managed_agent_avatar_url, managed_agents_base_dir, normalize_agent_args,
provider_deploy, resolve_provider_binary, save_managed_agents, start_managed_agent_process,
load_teams, managed_agent_avatar_url, normalize_agent_args, provider_deploy,
resolve_provider_binary, save_managed_agents, start_managed_agent_process,
stop_managed_agent_process, stop_managed_agent_workspace_pair,
sync_managed_agent_processes, try_regenerate_nest, validate_provider_config, BackendKind,
CreateManagedAgentRequest, CreateManagedAgentResponse, ManagedAgentRecord,
Expand All @@ -18,8 +18,7 @@ use crate::{
util::now_iso,
};

/// Read the workspace owner's pubkey hex from app state without holding the
/// lock for longer than necessary. Used to populate `BUZZ_ACP_AGENT_OWNER`
/// Read the workspace owner pubkey without holding the lock. Used to populate `BUZZ_ACP_AGENT_OWNER`
/// as a fallback for legacy agent records that have no NIP-OA `auth_tag`.
pub(super) fn workspace_owner_hex(state: &AppState) -> Result<String, String> {
let keys = state.keys.lock().map_err(|e| e.to_string())?;
Expand Down Expand Up @@ -54,14 +53,15 @@ pub(super) fn retain_managed_agent_pending(
use nostr::JsonUtil;

let result = (|| -> Result<(), String> {
let conn = open_retention_db(&managed_agents_base_dir(app)?.join("retention.db"))?;
let scope = crate::managed_agents::retention::active_retention_scope(app, state)?;
let conn = open_retention_db(&scope.db_path)?;
// The published content is the opt-IN projection JSON, independent of
// signing and created_at. Compute it once to drive the no-republish
// guard without signing twice.
let content = serde_json::to_string(&agent_event_content(record))
.map_err(|e| format!("failed to serialize managed-agent content: {e}"))?;
let (owner_pubkey, event) = {
let keys = state.signing_keys()?;
let keys = &scope.owner_keys;
let owner_pubkey = keys.public_key().to_hex();
let existing =
get_retained_event(&conn, KIND_MANAGED_AGENT, &owner_pubkey, &record.pubkey)?;
Expand All @@ -75,7 +75,7 @@ pub(super) fn retain_managed_agent_pending(
// Monotonic created_at: bump past the retained head (NIP-AP step 3).
let event = build_agent_event(record)?
.custom_created_at(monotonic_created_at(existing.map(|row| row.created_at)))
.sign_with_keys(&keys)
.sign_with_keys(keys)
.map_err(|e| format!("failed to sign managed-agent event: {e}"))?;
(owner_pubkey, event)
};
Expand Down Expand Up @@ -126,15 +126,12 @@ pub(super) fn tombstone_managed_agent_pending(
const KIND_DELETE: u32 = 5;

let result = (|| -> Result<(), String> {
let (owner_pubkey, event) = {
let keys = state.signing_keys()?;
let owner_pubkey = keys.public_key().to_hex();
let event = build_agent_delete(agent_pubkey, &owner_pubkey)?
.sign_with_keys(&keys)
.map_err(|e| format!("failed to sign managed-agent tombstone: {e}"))?;
(owner_pubkey, event)
};
let conn = open_retention_db(&managed_agents_base_dir(app)?.join("retention.db"))?;
let scope = crate::managed_agents::retention::active_retention_scope(app, state)?;
let owner_pubkey = scope.owner_keys.public_key().to_hex();
let event = build_agent_delete(agent_pubkey, &owner_pubkey)?
.sign_with_keys(&scope.owner_keys)
.map_err(|e| format!("failed to sign managed-agent tombstone: {e}"))?;
let conn = open_retention_db(&scope.db_path)?;
delete_retained_event(&conn, KIND_MANAGED_AGENT, &owner_pubkey, agent_pubkey)?;
retain_event(
&conn,
Expand Down Expand Up @@ -220,13 +217,10 @@ pub(super) fn archive_managed_agent_pending(app: &AppHandle, state: &AppState, a
use nostr::JsonUtil;

let result = (|| -> Result<(), String> {
let (owner_pubkey, event) = {
let keys = state.signing_keys()?;
let owner_pubkey = keys.public_key().to_hex();
let event = build_agent_archive_request(&keys, agent_pubkey)?;
(owner_pubkey, event)
};
let conn = open_retention_db(&managed_agents_base_dir(app)?.join("retention.db"))?;
let scope = crate::managed_agents::retention::active_retention_scope(app, state)?;
let owner_pubkey = scope.owner_keys.public_key().to_hex();
let event = build_agent_archive_request(&scope.owner_keys, agent_pubkey)?;
let conn = open_retention_db(&scope.db_path)?;
retain_event(
&conn,
&RetainedEvent {
Expand Down Expand Up @@ -941,6 +935,7 @@ pub async fn create_managed_agent(
name_pool: Vec::new(),
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
definition_respond_to: None,
Expand Down
2 changes: 2 additions & 0 deletions desktop/src-tauri/src/commands/agents_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fn bare_agent_record(
name_pool: vec![],
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
relay_mesh: None,
Expand All @@ -75,6 +76,7 @@ fn persona_record(id: &str, model: Option<&str>, provider: Option<&str>) -> Agen
name_pool: vec![],
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
env_vars: BTreeMap::new(),
Expand Down
3 changes: 3 additions & 0 deletions desktop/src-tauri/src/commands/media_download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,7 @@ mod tests {
version: FORMAT_VERSION,
definition: AgentSnapshotDefinition {
name: "test".to_string(),
source_is_builtin: false,
system_prompt: None,
runtime: None,
model: None,
Expand Down Expand Up @@ -659,6 +660,7 @@ mod tests {
version: FORMAT_VERSION,
definition: AgentSnapshotDefinition {
name: "test".to_string(),
source_is_builtin: false,
system_prompt: None,
runtime: None,
model: None,
Expand Down Expand Up @@ -704,6 +706,7 @@ mod tests {
version: FORMAT_VERSION,
definition: AgentSnapshotDefinition {
name: "test".to_string(),
source_is_builtin: false,
system_prompt: None,
runtime: None,
model: None,
Expand Down
1 change: 1 addition & 0 deletions desktop/src-tauri/src/commands/media_snapshot_png.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ mod tests {
version: 1,
definition: AgentSnapshotDefinition {
name: "Tree Trunks".to_string(),
source_is_builtin: false,
system_prompt: Some("You are a helpful agent.".to_string()),
runtime: Some("goose".to_string()),
model: None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ fn make_agent(
name_pool: vec![],
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
relay_mesh: None,
Expand Down
3 changes: 3 additions & 0 deletions desktop/src-tauri/src/commands/personas/inbound_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ fn local_in_app() -> AgentDefinition {
name_pool: vec!["Local".to_string()],
is_builtin: false,
is_active: true,
shared: false,
source_team: Some("team-1".to_string()),
source_team_persona_slug: None,
env_vars: BTreeMap::from([("API_KEY".to_string(), "secret".to_string())]),
Expand All @@ -45,6 +46,7 @@ fn inbound_for(d_tag: &str, display_name: &str) -> AgentDefinition {
name_pool: vec!["Remote".to_string()],
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: Some(d_tag.to_string()),
env_vars: BTreeMap::new(),
Expand Down Expand Up @@ -203,6 +205,7 @@ fn local_agent() -> ManagedAgentRecord {
name_pool: Vec::new(),
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
definition_respond_to: None,
Expand Down
20 changes: 15 additions & 5 deletions desktop/src-tauri/src/commands/personas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ fn trim_optional(value: Option<String>) -> Option<String> {
mod pending;
pub(in crate::commands) use pending::retain_persona_pending;
pub(super) use pending::tombstone_persona_pending;
mod sharing;
pub use sharing::set_persona_shared;

#[tauri::command]
pub async fn list_personas(app: AppHandle) -> Result<Vec<AgentDefinition>, String> {
Expand All @@ -43,7 +45,9 @@ pub async fn list_personas(app: AppHandle) -> Result<Vec<AgentDefinition>, Strin
.managed_agents_store_lock
.lock()
.map_err(|error| error.to_string())?;
load_personas(&app)
let mut personas = load_personas(&app)?;
pending::project_active_persona_sharing(&app, &state, &mut personas)?;
Ok(personas)
})
.await
.map_err(|e| format!("spawn_blocking failed: {e}"))?
Expand All @@ -70,6 +74,7 @@ pub async fn create_persona(
.lock()
.map_err(|error| error.to_string())?;
let mut personas = load_personas(&app)?;
pending::project_active_persona_sharing(&app, &state, &mut personas)?;
let name_pool: Vec<String> = input
.name_pool
.into_iter()
Expand All @@ -88,6 +93,7 @@ pub async fn create_persona(
name_pool,
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
env_vars: input.env_vars,
Expand Down Expand Up @@ -170,6 +176,7 @@ pub async fn update_persona(
.lock()
.map_err(|error| error.to_string())?;
let mut personas = load_personas(&app)?;
pending::project_active_persona_sharing(&app, &state, &mut personas)?;
let persona = personas
.iter_mut()
.find(|record| record.id == input.id)
Expand Down Expand Up @@ -551,7 +558,7 @@ fn reconcile_inbound_persona_event_blocking(
) -> Result<(), String> {
use crate::managed_agents::{
agent_events::managed_agent_content_from_event,
load_managed_agents, load_teams, managed_agents_base_dir,
load_managed_agents, load_teams,
persona_events::persona_from_event,
retention::{open_retention_db, retain_inbound_event, InboundOutcome, RetainedEvent},
save_managed_agents, save_teams,
Expand Down Expand Up @@ -599,7 +606,8 @@ fn reconcile_inbound_persona_event_blocking(
.map_err(|error| error.to_string())?;

// Resolve inbound vs. any pending local edit before touching the store.
let conn = open_retention_db(&managed_agents_base_dir(&app)?.join("retention.db"))?;
let scope = crate::managed_agents::retention::active_retention_scope(&app, &state)?;
let conn = open_retention_db(&scope.db_path)?;
let outcome = retain_inbound_event(
&conn,
&RetainedEvent {
Expand Down Expand Up @@ -703,7 +711,7 @@ fn reconcile_inbound_tombstone(
state: &AppState,
) -> Result<(), String> {
use crate::managed_agents::{
load_managed_agents, load_teams, managed_agents_base_dir,
load_managed_agents, load_teams,
retention::{
open_retention_db, retain_inbound_event, tombstone_retention_d_tag, InboundOutcome,
RetainedEvent,
Expand All @@ -728,7 +736,8 @@ fn reconcile_inbound_tombstone(
// Resolve against the retained tombstone row (keyed by the target
// coordinate, F2c) so a re-received tombstone or one older than a pending
// local edit is a no-op.
let conn = open_retention_db(&managed_agents_base_dir(app)?.join("retention.db"))?;
let scope = crate::managed_agents::retention::active_retention_scope(app, state)?;
let conn = open_retention_db(&scope.db_path)?;
let outcome = retain_inbound_event(
&conn,
&RetainedEvent {
Expand Down Expand Up @@ -815,6 +824,7 @@ fn apply_inbound_persona(personas: &mut Vec<AgentDefinition>, inbound: AgentDefi
local.respond_to = inbound.respond_to;
local.respond_to_allowlist = inbound.respond_to_allowlist;
local.parallelism = inbound.parallelism;
local.shared = inbound.shared;
local.updated_at = inbound.updated_at;
}
None => personas.push(inbound),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ fn agent(persona_id: &str, name: &str, display_name: Option<&str>) -> ManagedAge
name_pool: vec![],
is_builtin: false,
is_active: true,
shared: false,
source_team: None,
source_team_persona_slug: None,
definition_respond_to: None,
Expand Down
Loading
Loading