Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
483b515
refactor(desktop): unify EditAgentDialog styling with PersonaDialog
klopez4212 Jul 6, 2026
d525588
fix(desktop): use PersonaDropdownField for respond-to in EditAgentDialog
klopez4212 Jul 6, 2026
ee64a9e
fix(desktop): match respond-to allowlist styling to persona variant
klopez4212 Jul 6, 2026
5ffdc64
fix(desktop): remove help text and paste-pubkeys section in persona v…
klopez4212 Jul 6, 2026
c655372
fix(desktop): restore load-bearing comments and fix stale placeholder
klopez4212 Jul 6, 2026
a2cc9eb
fix(desktop): make avatar preview read-only in EditAgentDialog
klopez4212 Jul 6, 2026
2cc3c24
fix(desktop): remove dead description prop from EditAgentDialog
klopez4212 Jul 6, 2026
8343da7
feat(desktop): allow pasting hex pubkeys in allowlist search box
klopez4212 Jul 6, 2026
a7efd49
fix(desktop): pin harness when Custom command selected in EditAgentDi…
Jul 7, 2026
7f7eb07
fix(desktop): only pin harness when runtime can supply a command
Jul 7, 2026
2705e57
fix(desktop): reveal + gate custom command when Custom selected
Jul 7, 2026
85d4ef6
test(desktop): fix edit-dialog e2e provider selector after refactor
Jul 7, 2026
923098f
fix(desktop): persist custom pin when prefilled command is unchanged
Jul 7, 2026
25d7efd
fix(desktop): surface + gate required credentials in EditAgentDialog
Jul 7, 2026
056601e
fix(desktop): resolve inherit-target runtime from the persona
Jul 7, 2026
fa708c8
fix(desktop): preserve same-runtime custom pins through the update path
Jul 7, 2026
f3d0178
fix(desktop): persist persona provider+env snapshot when inheriting
Jul 7, 2026
c4a5c73
fix(desktop): preserve user-edited provider while inheriting
Jul 7, 2026
09d94a6
fix(desktop): let Default clear an inheriting agent's provider override
Jul 7, 2026
79b96da
fix(desktop): snapshot persona model + static runtime capability on i…
Jul 7, 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
10 changes: 8 additions & 2 deletions desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,11 @@ const overrides = new Map([
// config-parity: max_tokens_env_var + context_limit_env_var fields added to
// KnownAcpRuntime (2 fields × 4 runtimes + discovery tests = ~13 lines).
// Load-bearing — required for buzz-agent normalized config parity.
["src-tauri/src/managed_agents/discovery.rs", 1124],
// same-runtime-pin: update_time_agent_command_override + its override /
// same-runtime / alias / sentinel / non-override / persona-less test matrix
// (~135 lines, mostly tests) so a deliberate Custom pin survives the update
// path instead of being dropped back to inherit. Load-bearing, not debt.
["src-tauri/src/managed_agents/discovery.rs", 1259],
// migration_tests.rs carries the harness-sync migration coverage plus the
// patch_json_records owner-only writeback regression test (SECURITY.md:90
// crash-safe 0o600 fallback). Load-bearing security + feature coverage, not
Expand Down Expand Up @@ -207,7 +211,9 @@ const overrides = new Map([
// a GUI-launched DMG (the discovery_env_with_baked_floor fold).
// +3: provider tri-state applied in update_managed_agent handler
// (if let Some(provider_update) = input.provider { record.provider = provider_update; }).
["src-tauri/src/commands/agent_models.rs", 1071],
// +8: harness_override thread-through in update_managed_agent so a deliberate
// Custom pin routes to update_time_agent_command_override (comment + call).
["src-tauri/src/commands/agent_models.rs", 1079],
// draft-persistence predicate: submit-time `loadDraft` check + inline comment
// + deps-array entry in submitMessage closes the never-persisted-boundary
// defect (Thufir Pass-3 finding). Load-bearing correctness fix; queued to
Expand Down
18 changes: 13 additions & 5 deletions desktop/src-tauri/src/commands/agent_models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -860,13 +860,21 @@ pub async fn update_managed_agent(
// that diverges from the persona. An empty/whitespace value (the
// "Inherit from persona" sentinel) clears the pin back to `None`. A
// name-only edit (`agent_command == None`) leaves the pin intact.
//
// `harness_override` threads the user's explicit intent: when they pick
// a runtime/Custom command in the dialog it is a real pin even if it
// maps to the persona's own runtime, so a same-runtime pick is kept
// rather than dropped back to inherit (see
// `update_time_agent_command_override`).
if let Some(agent_command) = input.agent_command {
let personas = load_personas(&app).unwrap_or_default();
record.agent_command_override = crate::managed_agents::divergent_agent_command_override(
record.persona_id.as_deref(),
&personas,
Some(&agent_command),
);
record.agent_command_override =
crate::managed_agents::update_time_agent_command_override(
record.persona_id.as_deref(),
&personas,
Some(&agent_command),
input.harness_override,
);
}
if let Some(agent_args) = input.agent_args {
record.agent_args = agent_args;
Expand Down
139 changes: 137 additions & 2 deletions desktop/src-tauri/src/managed_agents/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,47 @@ pub fn divergent_agent_command_override(
}
}

/// Decide the `agent_command_override` to persist at AGENT UPDATE time.
///
/// The edit dialog sends `agent_command` as a tri-state string: the empty
/// "inherit from persona" sentinel (clear the pin), or a concrete command
/// (pin). Resolution:
///
/// - EMPTY / whitespace → the inherit sentinel: always `None` regardless of
/// `harness_override`, so toggling "Inherit runtime from persona" clears the
/// pin.
/// - DELIBERATE OVERRIDE (`harness_override` true, persona linked): the user
/// explicitly picked a runtime/Custom command in the dialog. This is a real
/// pin and is preserved VERBATIM — even when the picked command maps to, or
/// is byte-identical to, the persona's own runtime command. Selecting "Custom
/// command" and saving e.g. `goose` for a goose persona is a deliberate act
/// to freeze the harness against future persona runtime edits; dropping it
/// back to inherit (as [`divergent_agent_command_override`] would) defeats
/// that intent. Unlike the create-time path, there is no byte-identical
/// exception here: at create the command is machine-derived from the persona,
/// so equality means "no user divergence"; at update an equal command reached
/// the force branch only because the user picked Custom, which IS the
/// divergence.
/// - NO OVERRIDE INTENT (`harness_override` false) or NO PERSONA: defer to
/// [`divergent_agent_command_override`], which keeps the persona authoritative
/// and treats a same-runtime restatement as inherit.
pub fn update_time_agent_command_override(
persona_id: Option<&str>,
personas: &[crate::managed_agents::types::PersonaRecord],
picked_command: Option<&str>,
harness_override: bool,
) -> Option<String> {
let picked = picked_command
.map(str::trim)
.filter(|value| !value.is_empty())?;

if persona_id.is_some() && harness_override {
return Some(picked.to_string());
}

divergent_agent_command_override(persona_id, personas, Some(picked))
}

/// Decide the `agent_command_override` to persist at AGENT CREATE time.
///
/// A persona-backed create receives its harness command from
Expand Down Expand Up @@ -729,8 +770,8 @@ mod tests {
use super::{
classify_runtime, create_time_agent_command_override, default_agent_command,
divergent_agent_command_override, effective_agent_command, find_via_login_shell,
managed_agent_avatar_url, normalize_agent_args, BUZZ_AGENT_AVATAR_URL,
CLAUDE_CODE_AVATAR_URL, CODEX_AVATAR_URL, GOOSE_AVATAR_URL,
managed_agent_avatar_url, normalize_agent_args, update_time_agent_command_override,
BUZZ_AGENT_AVATAR_URL, CLAUDE_CODE_AVATAR_URL, CODEX_AVATAR_URL, GOOSE_AVATAR_URL,
};
use crate::managed_agents::AcpAvailabilityStatus;

Expand Down Expand Up @@ -1120,4 +1161,98 @@ mod tests {
Some("codex-acp".to_string())
);
}

#[test]
fn update_time_override_preserves_same_runtime_pin_when_overriding() {
// The bug this fixes: the user picks "Custom command" in the edit
// dialog and saves `goose` verbatim for a goose persona. That is a
// deliberate pin (harness_override true) — it must be kept so future
// persona runtime edits stop propagating, even though it maps to the
// persona's own runtime. `divergent_agent_command_override` alone would
// wrongly drop it to `None`.
let personas = vec![persona_with_runtime("p1", Some("goose"))];
assert_eq!(
update_time_agent_command_override(Some("p1"), &personas, Some("goose"), true),
Some("goose".to_string())
);
}

#[test]
fn update_time_override_preserves_exact_persona_command_when_overriding() {
// Even when the pick is byte-identical to the persona's own command, an
// explicit Custom selection (harness_override true) is a deliberate pin
// and is preserved. This is the core divergence from the create-time
// contract: at update, equality reached the force branch only because
// the user picked Custom.
let personas = vec![persona_with_runtime("p1", Some("claude"))];
assert_eq!(
update_time_agent_command_override(
Some("p1"),
&personas,
Some("claude-agent-acp"),
true
),
Some("claude-agent-acp".to_string())
);
}

#[test]
fn update_time_override_preserves_alias_pin_when_overriding() {
// A `claude` persona with an installed `claude-code-acp` alias: picking
// it as a Custom pin is a deliberate divergence from the primary
// command and must be preserved when overriding.
let personas = vec![persona_with_runtime("p1", Some("claude"))];
assert_eq!(
update_time_agent_command_override(
Some("p1"),
&personas,
Some("claude-code-acp"),
true
),
Some("claude-code-acp".to_string())
);
}

#[test]
fn update_time_override_defers_to_divergent_when_not_overriding() {
// Without the explicit intent bit (e.g. a name-only edit that still
// echoes the command), the persona stays authoritative: a same-runtime
// command inherits, a different runtime pins.
let personas = vec![persona_with_runtime("p1", Some("goose"))];
assert_eq!(
update_time_agent_command_override(Some("p1"), &personas, Some("goose"), false),
None
);
assert_eq!(
update_time_agent_command_override(Some("p1"), &personas, Some("codex-acp"), false),
Some("codex-acp".to_string())
);
}

#[test]
fn update_time_override_clears_pin_for_inherit_sentinel() {
// The empty "Inherit from persona" sentinel always clears the pin,
// regardless of the override flag.
let personas = vec![persona_with_runtime("p1", Some("goose"))];
assert_eq!(
update_time_agent_command_override(Some("p1"), &personas, Some(" "), true),
None
);
assert_eq!(
update_time_agent_command_override(Some("p1"), &personas, None, true),
None
);
}

#[test]
fn update_time_override_preserves_pin_for_persona_less_agent() {
// A persona-less agent has no runtime to inherit, so any picked command
// is a real pin — preserved even without the override flag (mirrors the
// create-time persona-less contract).
let personas = vec![persona_with_runtime("p1", Some("goose"))];
assert_eq!(
update_time_agent_command_override(None, &personas, Some("codex-acp"), false),
Some("codex-acp".to_string())
);
}
}
8 changes: 8 additions & 0 deletions desktop/src-tauri/src/managed_agents/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,14 @@ pub struct UpdateManagedAgentRequest {
pub acp_command: Option<String>,
#[serde(default)]
pub agent_command: Option<String>,
/// True when the accompanying `agent_command` is a runtime/Custom command
/// the user deliberately picked for a linked persona (i.e. the dialog is
/// not inheriting). Distinguishes a real pin — including one that maps to
/// the persona's own runtime — from a persona-authoritative restatement,
/// so a same-runtime pick is preserved instead of being dropped back to
/// inherit. Ignored when `agent_command` is absent or the inherit sentinel.
#[serde(default)]
pub harness_override: bool,
#[serde(default)]
pub agent_args: Option<Vec<String>>,
#[serde(default)]
Expand Down
Loading
Loading