diff --git a/desktop/src-tauri/src/managed_agents/persona_events.rs b/desktop/src-tauri/src/managed_agents/persona_events.rs index b4f62d5662..cbea080f8a 100644 --- a/desktop/src-tauri/src/managed_agents/persona_events.rs +++ b/desktop/src-tauri/src/managed_agents/persona_events.rs @@ -433,6 +433,9 @@ pub struct PersonaSnapshot { pub system_prompt: Option, pub model: Option, pub provider: Option, + /// Exact publisher-pinned shared-instruction coordinates, copied verbatim + /// from the definition so edits reach an existing instance on restart. + pub assigned_relay_skills: Vec, /// Preferred ACP runtime ID, copied verbatim from the persona (including /// `None`). Unlike `model`/`provider`, there is no record-fallback: the /// materialized instance `runtime` must mirror the definition so that @@ -454,14 +457,15 @@ pub fn persona_snapshot(persona: &AgentDefinition) -> PersonaSnapshot { system_prompt: Some(persona.system_prompt.clone()), model: persona.model.clone(), provider: persona.provider.clone(), + assigned_relay_skills: persona.assigned_relay_skills.clone(), runtime: persona.runtime.clone(), source_version: persona_content_hash(&persona_event_content(persona)), } } /// Re-pin `record` to `persona`: build a snapshot via [`persona_snapshot`] -/// and mirror it onto the record — the definition quad -/// (`system_prompt`/`model`/`provider`/`runtime`), the env-override +/// and mirror it onto the record — the definition fields +/// (`system_prompt`/`model`/`provider`/`runtime`/shared instructions), the env-override /// self-heal, and the `persona_source_version` drift basis. /// /// Definition-authoritative: blank definition model/provider produce `None` @@ -484,6 +488,7 @@ pub fn apply_persona_snapshot(record: &mut ManagedAgentRecord, persona: &AgentDe } record.model = snapshot.model; record.provider = snapshot.provider; + record.assigned_relay_skills = snapshot.assigned_relay_skills; record.runtime = snapshot.runtime; // Drop a stale create-time harness pin when the definition switches to a // different known runtime (builtin, static preset, or loaded custom). A pin diff --git a/desktop/src-tauri/src/managed_agents/persona_events/tests.rs b/desktop/src-tauri/src/managed_agents/persona_events/tests.rs index 8d08608692..6a05db75c4 100644 --- a/desktop/src-tauri/src/managed_agents/persona_events/tests.rs +++ b/desktop/src-tauri/src/managed_agents/persona_events/tests.rs @@ -168,6 +168,18 @@ pub(super) fn sample_persona() -> AgentDefinition { } } +#[test] +fn apply_snapshot_refreshes_shared_instructions_for_existing_instance() { + let mut record = sample_record(); + record.assigned_relay_skills = vec![format!("30023:{}:old", "a".repeat(64))]; + let mut persona = sample_persona(); + persona.assigned_relay_skills = vec![format!("30023:{}:new", "b".repeat(64))]; + + apply_persona_snapshot(&mut record, &persona); + + assert_eq!(record.assigned_relay_skills, persona.assigned_relay_skills); +} + #[test] fn monotonic_created_at_bumps_past_head() { // No head: uses now (floor 0). diff --git a/desktop/src-tauri/src/managed_agents/spawn_snapshot.rs b/desktop/src-tauri/src/managed_agents/spawn_snapshot.rs index 8a6f68a693..1a921e47b9 100644 --- a/desktop/src-tauri/src/managed_agents/spawn_snapshot.rs +++ b/desktop/src-tauri/src/managed_agents/spawn_snapshot.rs @@ -111,6 +111,8 @@ pub(crate) struct SpawnConfigSnapshot { pub system_prompt: Option, pub model: Option, pub provider: Option, + /// Exact publisher-pinned shared-instruction coordinates exported to ACP. + pub assigned_relay_skills: Vec, /// `None` when a user env override shadows `BUZZ_ACP_SESSION_TITLE`: spawn /// writes the title BEFORE the user env layer, so the override is what /// actually runs and it already reaches this snapshot through `env`. @@ -193,6 +195,7 @@ impl SpawnConfigSnapshot { system_prompt: system_prompt.map(str::to_string), model: model.map(str::to_string), provider: provider.map(str::to_string), + assigned_relay_skills: record.assigned_relay_skills.clone(), session_title: (!descriptor.env.contains_key(SESSION_TITLE_ENV_VAR)) .then(|| resolve_session_title(record.display_name.as_deref(), &record.name)) .flatten(), diff --git a/desktop/src-tauri/src/managed_agents/spawn_snapshot/diff/tests.rs b/desktop/src-tauri/src/managed_agents/spawn_snapshot/diff/tests.rs index e21dc4735c..cb770b903e 100644 --- a/desktop/src-tauri/src/managed_agents/spawn_snapshot/diff/tests.rs +++ b/desktop/src-tauri/src/managed_agents/spawn_snapshot/diff/tests.rs @@ -21,6 +21,7 @@ fn base() -> SpawnConfigSnapshot { system_prompt: Some("You are a test agent.".into()), model: Some("gpt-5".into()), provider: Some("openai".into()), + assigned_relay_skills: vec![format!("30023:{}:review", "a".repeat(64))], session_title: Some("Fizz".into()), auth_tag: Some("tag-abcdefgh".into()), respond_to: "owner-only".into(), @@ -62,6 +63,7 @@ fn mutations() -> Vec { ("system_prompt", |s| s.system_prompt = None), ("model", |s| s.model = None), ("provider", |s| s.provider = None), + ("assigned_relay_skills", |s| s.assigned_relay_skills.clear()), ("session_title", |s| s.session_title = None), ("auth_tag", |s| s.auth_tag = None), ("respond_to", |s| s.respond_to = "anyone".into()), diff --git a/desktop/src-tauri/src/managed_agents/spawn_snapshot/tests.rs b/desktop/src-tauri/src/managed_agents/spawn_snapshot/tests.rs index 3352883960..84566e8e89 100644 --- a/desktop/src-tauri/src/managed_agents/spawn_snapshot/tests.rs +++ b/desktop/src-tauri/src/managed_agents/spawn_snapshot/tests.rs @@ -223,6 +223,27 @@ fn persona_prompt_edit_changes_snapshot() { ); } +#[test] +fn persona_shared_instruction_edit_changes_snapshot() { + let mut rec = record(); + rec.persona_id = Some("pers".into()); + let mut before = persona("pers", Some("goose"), "prompt"); + before.assigned_relay_skills = vec![format!("30023:{}:before", "a".repeat(64))]; + let mut after = before.clone(); + after.assigned_relay_skills = vec![format!("30023:{}:after", "b".repeat(64))]; + + assert_ne!( + snapshot( + &rec, + &[before], + &[], + "wss://ws.example", + &Default::default() + ), + snapshot(&rec, &[after], &[], "wss://ws.example", &Default::default()) + ); +} + #[test] fn workspace_relay_change_trips_snapshot_even_for_stored_record_relay() { // The legacy per-record relay pin is ignored (#2122): every record spawns