Skip to content

Commit f489fd6

Browse files
npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7npub1fgdl5qqnh3k3f2xkqrvt7cujalhm623x4s7fdjdj5yrtp5fzjl9qrjpucw
authored andcommitted
refactor: consolidate packs into teams — business logic and frontend
Implements the business logic half of the packs→teams consolidation: - Add import_team_from_directory() with copy/symlink support, ID validation, and defense-in-depth re-validation after install - Add delete_team_with_cascade() that removes directory + personas for directory-backed teams, preserves personas for JSON-only teams - Add sync_team_from_dir() to reconcile directory changes with stored records (add/remove/update personas, update team metadata) - Update runtime.rs to read persona_team_dir/persona_name_in_team for ACP env var resolution (env var names unchanged for compatibility) - Remove import_persona_pack, uninstall_persona_pack, list_installed_packs and all supporting pack infrastructure from personas.rs - Remove pack Tauri commands, add team directory commands (install_team_from_directory, sync_team_directory, pick_team_directory) - Update all field references across commands, tests, nest, relay_mesh - Frontend: sourcePack→sourceTeam, 'Managed by pack'→'Managed by team' - Bump file size limits for teams.rs and migration.rs Includes Paul's type changes (serde aliases, new TeamRecord fields, SyncResult/MigrationReport structs, migration function) as the foundation these business logic changes build on.
1 parent 15f610d commit f489fd6

15 files changed

Lines changed: 1106 additions & 417 deletions

File tree

desktop/scripts/check-file-sizes.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ const overrides = new Map([
3636
["src/shared/api/tauri.ts", 1196],
3737
["src-tauri/src/nostr_convert.rs", 1116],
3838
["src/shared/api/relayClientSession.ts", 1022],
39-
["src-tauri/src/migration.rs", 1130],
39+
["src-tauri/src/managed_agents/teams.rs", 1020],
40+
["src-tauri/src/migration.rs", 1200],
4041
]);
4142

4243
await runFileSizeCheck({

desktop/src-tauri/src/commands/agents.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -420,13 +420,13 @@ pub async fn create_managed_agent(
420420
requested_persona_id.as_deref().and_then(|pid| {
421421
let personas = load_personas(&app).ok()?;
422422
let persona = personas.iter().find(|p| p.id == pid)?;
423-
let pack_id = persona.source_pack.as_deref()?;
424-
let slug = persona.source_pack_persona_slug.as_deref()?;
423+
let team_id = persona.source_team.as_deref()?;
424+
let slug = persona.source_team_persona_slug.as_deref()?;
425425
let base = managed_agents_base_dir(&app).ok()?;
426-
let pack_path = base.join("packs").join(pack_id);
426+
let team_path = base.join("teams").join(team_id);
427427
// Use the validated slug stored during import — no need to
428428
// re-resolve the pack. The slug is [a-zA-Z0-9_-]+ by construction.
429-
Some((pack_path, slug.to_owned()))
429+
Some((team_path, slug.to_owned()))
430430
});
431431

432432
let record = crate::managed_agents::ManagedAgentRecord {
@@ -485,11 +485,11 @@ pub async fn create_managed_agent(
485485
backend: input.backend.clone(),
486486
backend_agent_id: None,
487487
provider_binary_path,
488-
// Pack-backed personas: record path + internal slug so the runtime
489-
// can resolve pack config at startup. Must be the slug (e.g., "lep"),
488+
// Team-backed personas: record path + internal slug so the runtime
489+
// can resolve team config at startup. Must be the slug (e.g., "lep"),
490490
// NOT the display_name — ACP's resolve_persona_by_name() matches slugs.
491-
persona_pack_path: pack_metadata.as_ref().map(|(path, _)| path.clone()),
492-
persona_name_in_pack: pack_metadata.as_ref().map(|(_, name)| name.clone()),
491+
persona_team_dir: pack_metadata.as_ref().map(|(path, _)| path.clone()),
492+
persona_name_in_team: pack_metadata.as_ref().map(|(_, name)| name.clone()),
493493
env_vars: input.env_vars.clone(),
494494
created_at: now_iso(),
495495
updated_at: now_iso(),

desktop/src-tauri/src/commands/personas.rs

Lines changed: 7 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ use super::export_util::save_json_with_dialog;
55
use crate::{
66
app_state::AppState,
77
managed_agents::{
8-
encode_persona_json, import_persona_pack, list_installed_packs, load_managed_agents,
9-
load_personas, load_teams, parse_json_persona, parse_md_persona, parse_png_persona,
10-
parse_zip_personas, save_managed_agents, save_personas, try_regenerate_nest,
11-
uninstall_persona_pack as do_uninstall_persona_pack, validate_persona_activation_change,
12-
validate_persona_deletion, CreatePersonaRequest, PackSummary, ParsePersonaFilesResult,
13-
PersonaRecord, UpdatePersonaRequest,
8+
encode_persona_json, load_managed_agents, load_personas, load_teams, parse_json_persona,
9+
parse_md_persona, parse_png_persona, parse_zip_personas, save_managed_agents,
10+
save_personas, try_regenerate_nest, validate_persona_activation_change,
11+
validate_persona_deletion, CreatePersonaRequest, ParsePersonaFilesResult, PersonaRecord,
12+
UpdatePersonaRequest,
1413
},
1514
util::now_iso,
1615
};
@@ -77,8 +76,8 @@ pub fn create_persona(
7776
name_pool,
7877
is_builtin: false,
7978
is_active: true,
80-
source_pack: None,
81-
source_pack_persona_slug: None,
79+
source_team: None,
80+
source_team_persona_slug: None,
8281
env_vars: input.env_vars,
8382
created_at: now.clone(),
8483
updated_at: now,
@@ -368,44 +367,3 @@ pub async fn export_persona_to_json(
368367
let filename = format!("{slug}.persona.json");
369368
save_json_with_dialog(&app, &filename, &json_bytes).await
370369
}
371-
372-
// ── Pack management commands ──────────────────────────────────────────────────
373-
374-
#[tauri::command]
375-
pub fn install_persona_pack(
376-
app: AppHandle,
377-
state: State<'_, AppState>,
378-
path: String,
379-
) -> Result<Vec<PersonaRecord>, String> {
380-
let _lock = state
381-
.managed_agents_store_lock
382-
.lock()
383-
.map_err(|e| e.to_string())?;
384-
let source = std::path::PathBuf::from(&path);
385-
if !source.is_dir() {
386-
return Err(format!("pack path is not a directory: {path}"));
387-
}
388-
let result = import_persona_pack(&app, &source)?;
389-
try_regenerate_nest(&app);
390-
Ok(result)
391-
}
392-
393-
#[tauri::command]
394-
pub fn uninstall_persona_pack(
395-
app: AppHandle,
396-
state: State<'_, AppState>,
397-
pack_id: String,
398-
) -> Result<(), String> {
399-
let _lock = state
400-
.managed_agents_store_lock
401-
.lock()
402-
.map_err(|e| e.to_string())?;
403-
do_uninstall_persona_pack(&app, &pack_id)?;
404-
try_regenerate_nest(&app);
405-
Ok(())
406-
}
407-
408-
#[tauri::command]
409-
pub fn list_persona_packs(app: AppHandle) -> Result<Vec<PackSummary>, String> {
410-
list_installed_packs(&app)
411-
}

desktop/src-tauri/src/commands/teams.rs

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use super::export_util::save_json_with_dialog;
55
use crate::{
66
app_state::AppState,
77
managed_agents::{
8-
encode_team_json, ensure_persona_ids_are_active, load_personas, load_teams,
9-
parse_team_json, save_teams, validate_team_deletion, CreateTeamRequest, ParsedTeamPreview,
10-
TeamRecord, UpdateTeamRequest,
8+
delete_team_with_cascade, encode_team_json, ensure_persona_ids_are_active,
9+
import_team_from_directory as do_import_team, load_personas, load_teams, parse_team_json,
10+
save_teams, sync_team_from_dir as do_sync_team, try_regenerate_nest, CreateTeamRequest,
11+
ParsedTeamPreview, SyncResult, TeamRecord, UpdateTeamRequest,
1112
},
1213
util::now_iso,
1314
};
@@ -59,6 +60,10 @@ pub fn create_team(
5960
description,
6061
persona_ids: input.persona_ids,
6162
is_builtin: false,
63+
source_dir: None,
64+
is_symlink: false,
65+
symlink_target: None,
66+
version: None,
6267
created_at: now.clone(),
6368
updated_at: now,
6469
};
@@ -104,14 +109,51 @@ pub fn delete_team(id: String, app: AppHandle, state: State<'_, AppState>) -> Re
104109
.managed_agents_store_lock
105110
.lock()
106111
.map_err(|error| error.to_string())?;
107-
let mut teams = load_teams(&app)?;
108-
let team = teams
109-
.iter()
110-
.find(|record| record.id == id)
111-
.ok_or_else(|| format!("team {id} not found"))?;
112-
validate_team_deletion(team)?;
113-
teams.retain(|record| record.id != id);
114-
save_teams(&app, &teams)
112+
delete_team_with_cascade(&app, &id)?;
113+
try_regenerate_nest(&app);
114+
Ok(())
115+
}
116+
117+
#[tauri::command]
118+
pub fn install_team_from_directory(
119+
app: AppHandle,
120+
state: State<'_, AppState>,
121+
path: String,
122+
symlink: Option<bool>,
123+
) -> Result<TeamRecord, String> {
124+
let _store_guard = state
125+
.managed_agents_store_lock
126+
.lock()
127+
.map_err(|e| e.to_string())?;
128+
let source = std::path::PathBuf::from(&path);
129+
if !source.is_dir() {
130+
return Err(format!("team path is not a directory: {path}"));
131+
}
132+
let result = do_import_team(&app, &source, symlink.unwrap_or(false))?;
133+
try_regenerate_nest(&app);
134+
Ok(result)
135+
}
136+
137+
#[tauri::command]
138+
pub fn sync_team_directory(
139+
app: AppHandle,
140+
state: State<'_, AppState>,
141+
team_id: String,
142+
) -> Result<SyncResult, String> {
143+
let _store_guard = state
144+
.managed_agents_store_lock
145+
.lock()
146+
.map_err(|e| e.to_string())?;
147+
let result = do_sync_team(&app, &team_id)?;
148+
try_regenerate_nest(&app);
149+
Ok(result)
150+
}
151+
152+
#[tauri::command]
153+
pub async fn pick_team_directory(app: AppHandle) -> Result<Option<String>, String> {
154+
use tauri_plugin_dialog::DialogExt;
155+
let path = app.dialog().file().blocking_pick_folder();
156+
Ok(path.map(|p| p.to_string()))
115157
}
116158

117159
// ---------------------------------------------------------------------------

desktop/src-tauri/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -498,7 +498,8 @@ pub fn run() {
498498
// this worktree's data directory. Must run before
499499
// restore_managed_agents_on_launch (which reads managed-agents.json).
500500
migration::sync_shared_agent_data(&app_handle);
501-
migration::reconcile_persona_pack_paths(&app_handle);
501+
migration::migrate_packs_to_teams(&app_handle);
502+
migration::reconcile_persona_team_dirs(&app_handle);
502503
migration::reconcile_provider_mcp_commands(&app_handle);
503504
migration::migrate_persona_provider_to_runtime(&app_handle);
504505

@@ -695,13 +696,13 @@ pub fn run() {
695696
create_team,
696697
update_team,
697698
delete_team,
699+
install_team_from_directory,
700+
sync_team_directory,
701+
pick_team_directory,
698702
export_team_to_json,
699703
parse_team_file,
700704
parse_persona_files,
701705
export_persona_to_json,
702-
install_persona_pack,
703-
uninstall_persona_pack,
704-
list_persona_packs,
705706
get_channel_workflows,
706707
get_workflow,
707708
create_workflow,

desktop/src-tauri/src/managed_agents/nest.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -946,8 +946,8 @@ mod tests {
946946
name_pool: vec![],
947947
is_builtin: false,
948948
is_active: true,
949-
source_pack: None,
950-
source_pack_persona_slug: None,
949+
source_team: None,
950+
source_team_persona_slug: None,
951951
env_vars: std::collections::BTreeMap::new(),
952952
created_at: String::new(),
953953
updated_at: String::new(),
@@ -978,8 +978,8 @@ mod tests {
978978
backend: BackendKind::default(),
979979
backend_agent_id: None,
980980
provider_binary_path: None,
981-
persona_pack_path: None,
982-
persona_name_in_pack: None,
981+
persona_team_dir: None,
982+
persona_name_in_team: None,
983983
created_at: String::new(),
984984
updated_at: String::new(),
985985
last_started_at: None,

0 commit comments

Comments
 (0)