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
5 changes: 3 additions & 2 deletions crates/sprout-acp/src/base_prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ Run `sprout --help` or `sprout <group> --help` for full usage.
- Respond promptly to @mentions.
- Be direct. State what you did, what you found, or what you need. No preamble.
- Message content supports GitHub-flavored Markdown. Use fenced code blocks with a language tag (` ```python `, ` ```typescript `, etc.) for syntax-highlighted rendering on desktop and mobile. Omitting the language tag renders monochrome.
- When responding in-thread, use `sprout messages send --reply-to <thread-root-event-id>` to keep replies scoped to the thread. Post new top-level messages for new topics.
- Reply to the thread root (`sprout messages send --reply-to <thread-root-event-id>`), not the latest message — flat threads stay readable; reply chains bury context 3+ levels deep. One thread = one unit of work: ask sub-questions inline. A real tangent starts a new top-level message.
- Work in the thread, report milestones at the root. The thread is the messy middle — progress, dead ends, clarifying questions, and routine updates. Use a top-level post for channel-visible milestones: picked up, blocked + need input, change ready / PR up, done, or anything teammates skimming only root-level messages must act on. Thread notifications are easy to miss; a top-level post ensures the requester sees the outcome.
- New topic → new top-level message. Don't graft an unrelated task onto an existing thread.
- When you are mentioned in multiple threads, prioritize the most recent one chronologically. If someone steers or redirects you in a newer thread while you are working from an older dispatch, reply in the newer thread to acknowledge — do not bury your response in the original thread where it may go unseen.
- When you complete a task (e.g., PR created, implementation finished, research delivered), post a top-level channel message with the result — do not only reply in-thread. Thread notifications are easy to miss; a broadcast message ensures the requester sees the outcome promptly.
- No push notifications — poll with `sprout messages get --channel <UUID> --since <ts>`. When `since` is set without `before`, results are oldest-first (chronological).

## Startup Recovery
Expand Down
1 change: 1 addition & 0 deletions desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const overrides = new Map([
["src-tauri/src/commands/agents.rs", 1208],
["src-tauri/src/managed_agents/nest.rs", 1420],
["src-tauri/src/managed_agents/runtime.rs", 1465],
["src-tauri/src/managed_agents/personas.rs", 1080],
["src-tauri/src/managed_agents/persona_card.rs", 1050],
["src-tauri/src/huddle/tts.rs", 1364],
["src/shared/api/tauri.ts", 1196],
Expand Down
4 changes: 4 additions & 0 deletions desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,10 @@ pub fn run() {
migration::reconcile_provider_mcp_commands(&app_handle);
migration::migrate_persona_provider_to_runtime(&app_handle);

if let Err(e) = managed_agents::sync_pack_personas(&app_handle) {
eprintln!("sprout-desktop: sync-pack-personas: {e}");
}

// Resolve persisted identity key (env var → file → generate+save).
// This is fatal — the app should not start with an ephemeral identity
// that will be lost on restart, as that silently breaks channel
Expand Down
89 changes: 89 additions & 0 deletions desktop/src-tauri/src/managed_agents/personas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,95 @@ pub struct PackSummary {
pub path: PathBuf,
}

/// Re-read pack directories and update persona records whose source content
/// has changed. Runs on launch so pack edits on disk propagate without
/// manual intervention.
pub fn sync_pack_personas(app: &AppHandle) -> Result<(), String> {
let mut records = load_personas(app)?;
let packs = packs_dir(app)?;

if !packs.exists() {
return Ok(());
}

let mut changed = false;

for record in records.iter_mut() {
let pack_id = match &record.source_pack {
Some(id) => id.clone(),
None => continue,
};
let slug = match &record.source_pack_persona_slug {
Some(s) => s.clone(),
None => continue,
};

// Find the pack directory whose resolved ID matches
let pack_dir_entries =
fs::read_dir(&packs).map_err(|e| format!("failed to read packs dir: {e}"))?;

let mut found = false;
for entry in pack_dir_entries {
let entry = entry.map_err(|e| format!("dir entry error: {e}"))?;
let dir = entry.path();
if !dir.is_dir() {
continue;
}
let resolved = match sprout_persona::resolve::resolve_pack(&dir) {
Ok(r) => r,
Err(_) => continue,
};
if resolved.id != pack_id {
continue;
}

// Found the matching pack — find the persona by slug
if let Some(persona) = resolved.personas.iter().find(|p| p.name == slug) {
let mut record_changed = false;

if record.system_prompt != persona.system_prompt {
record.system_prompt = persona.system_prompt.clone();
record_changed = true;
}
if record.model != persona.model {
record.model = persona.model.clone();
record_changed = true;
}
if record.avatar_url != persona.avatar {
record.avatar_url = persona.avatar.clone();
record_changed = true;
}
if record.display_name != persona.display_name {
record.display_name = persona.display_name.clone();
record_changed = true;
}

if record_changed {
record.updated_at = now_iso();
changed = true;
eprintln!(
"sprout-desktop: sync-pack-personas: updated {:?} from pack {:?}",
record.display_name, pack_id
);
}
}
found = true;
break;
}

if !found {
// Pack directory no longer exists or doesn't resolve — skip silently
continue;
}
}

if changed {
save_personas(app, &records)?;
}

Ok(())
}

pub fn load_personas(app: &AppHandle) -> Result<Vec<PersonaRecord>, String> {
let path = personas_store_path(app)?;
let now = now_iso();
Expand Down
Loading