Preserve harness merge resolutions after DCO rebase

Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
kenny lopez
2026-07-27 13:33:11 +01:00
parent cf55e5ab24
commit b8178d5cba
20 changed files with 164 additions and 120 deletions
+5 -7
View File
@@ -759,13 +759,11 @@ pub async fn create_managed_agent(
// Load personas once for harness/pack/avatar resolution below.
let personas = load_personas(&app).unwrap_or_default();
// The persona runtime is authoritative. Persist an override only when
// the selected runtime must remain pinned (`harness_override`). This
// includes explicit picks and the visible fallback for a runtime-less
// persona. `implicit_harness_fallback` distinguishes those intents so
// the pin cannot adopt defaults owned by another harness. A divergence
// without `harness_override` remains inherited so it can recover when
// its configured runtime is installed. Persona-less creates always pin.
// The persona runtime is authoritative. Persist an override only for a
// pinned selection, including the visible runtime-less fallback.
// `implicit_harness_fallback` distinguishes that automatic fallback so
// it cannot adopt defaults owned by another harness. Other divergence
// stays inherited; persona-less creates always pin.
let (agent_command_override, agent_command_override_is_implicit) =
crate::managed_agents::create_time_agent_command_override_state(
requested_persona_id.as_deref(),
@@ -17,7 +17,7 @@ fn bare_agent_record(
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command: "buzz-agent".to_string(),
agent_command_override: None,
agent_command_override_is_implicit: false,
agent_args: vec![],
@@ -219,7 +219,7 @@ fn deploy_resolver_returns_none_for_orphaned_instance() {
}
#[test]
fn deploy_resolver_ignores_defaults_from_a_different_implicit_runtime() {
fn deploy_resolver_keeps_defaults_for_an_explicit_harness_override() {
let mut record = bare_agent_record(Some("p1"), None, None);
record.agent_command_override = Some("goose".to_string());
let personas = vec![persona_record("p1", None, None)];
@@ -232,7 +232,7 @@ fn deploy_resolver_ignores_defaults_from_a_different_implicit_runtime() {
assert_eq!(
resolve_deploy_model_provider(&record, &personas, &global),
(None, None)
(Some("auto".to_string()), Some("relay-mesh".to_string()))
);
}
@@ -362,8 +362,6 @@ pub fn effective_agent_command(
}
mod overrides;
#[cfg(test)]
pub use overrides::create_time_agent_command_override;
pub use overrides::{apply_agent_command_update, create_time_agent_command_override_state};
/// Prefix of the typed dangling-harness error produced by
@@ -1,14 +1,17 @@
use std::path::PathBuf;
use super::overrides::{divergent_agent_command_override, update_time_agent_command_override};
use super::overrides::{
create_time_agent_command_override, divergent_agent_command_override,
update_time_agent_command_override,
};
use super::{
apply_agent_command_update, classify_runtime, codex_adapter_availability,
codex_adapter_is_outdated, create_time_agent_command_override, default_agent_command,
effective_agent_command, find_nvm_default_bin, find_via_login_shell,
is_login_shell_path_uninit, is_safe_nvm_tag, managed_agent_avatar_url, normalize_agent_args,
parse_semver_tag, preset_catalog_entry, probe_codex_acp_major_version, record_agent_command,
refresh_login_shell_path, try_record_agent_command, PresetHarness, BUZZ_AGENT_AVATAR_URL,
CLAUDE_CODE_AVATAR_URL, CODEX_AVATAR_URL, GOOSE_AVATAR_URL,
codex_adapter_is_outdated, default_agent_command, effective_agent_command,
find_nvm_default_bin, find_via_login_shell, is_login_shell_path_uninit, is_safe_nvm_tag,
managed_agent_avatar_url, normalize_agent_args, parse_semver_tag, preset_catalog_entry,
probe_codex_acp_major_version, record_agent_command, refresh_login_shell_path,
try_record_agent_command, PresetHarness, BUZZ_AGENT_AVATAR_URL, CLAUDE_CODE_AVATAR_URL,
CODEX_AVATAR_URL, GOOSE_AVATAR_URL,
};
use crate::managed_agents::AcpAvailabilityStatus;
@@ -72,16 +72,22 @@ fn non_blank(v: Option<&str>) -> Option<&str> {
}
fn resolve_linked(
record: &ManagedAgentRecord,
definition: &AgentDefinition,
definitions: &[AgentDefinition],
global: &GlobalAgentConfig,
) -> EffectiveAgentConfig {
let global_defaults_apply =
super::global_config::global_harness_defaults_apply_to_record(record, definitions, global);
let model = match non_blank(definition.model.as_deref()) {
Some(m) => ResolvedField {
value: Some(m.to_owned()),
source: ConfigSource::Definition,
},
None => ResolvedField {
value: global.model.clone(),
value: global_defaults_apply
.then(|| global.model.clone())
.flatten(),
source: ConfigSource::Global,
},
};
@@ -92,7 +98,9 @@ fn resolve_linked(
source: ConfigSource::Definition,
},
None => ResolvedField {
value: global.provider.clone(),
value: global_defaults_apply
.then(|| global.provider.clone())
.flatten(),
source: ConfigSource::Global,
},
};
@@ -251,7 +259,9 @@ pub fn resolve_effective_config(
) -> EffectiveConfigResult {
match &record.persona_id {
Some(pid) => match definitions.iter().find(|d| d.id == *pid) {
Some(def) => EffectiveConfigResult::Resolved(resolve_linked(def, global)),
Some(def) => {
EffectiveConfigResult::Resolved(resolve_linked(record, def, definitions, global))
}
None => EffectiveConfigResult::OrphanedInstance {
record_pubkey: record.pubkey.clone(),
missing_persona_id: pid.clone(),
@@ -45,8 +45,9 @@ fn record(
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command: "buzz-agent".to_string(),
agent_command_override: None,
agent_command_override_is_implicit: false,
agent_args: vec![],
mcp_command: "".to_string(),
turn_timeout_seconds: 300,
@@ -223,7 +223,7 @@ pub fn save_global_agent_config(app: &AppHandle, config: &GlobalAgentConfig) ->
/// harness. Explicitly configured definitions and standalone agents keep
/// normal global inheritance. Configs written before `preferred_runtime`
/// existed implicitly belong to Buzz Agent.
fn global_harness_defaults_apply_to_record(
pub(crate) fn global_harness_defaults_apply_to_record(
record: &ManagedAgentRecord,
personas: &[AgentDefinition],
global: &GlobalAgentConfig,
@@ -255,27 +255,18 @@ fn global_harness_defaults_apply_to_record(
let preferred_runtime = global
.preferred_runtime
.as_deref()
.and_then(crate::managed_agents::known_acp_runtime)
.or_else(|| crate::managed_agents::known_acp_runtime("buzz-agent"));
let selected_command = crate::managed_agents::record_agent_command(record, personas);
let selected_runtime = crate::managed_agents::known_acp_runtime(&selected_command);
.filter(|runtime| !runtime.trim().is_empty())
.unwrap_or("buzz-agent");
let preferred_command = crate::managed_agents::known_acp_runtime_exact(preferred_runtime)
.and_then(|runtime| runtime.commands.first().copied())
.map(str::to_string)
.or_else(|| {
crate::managed_agents::custom_harnesses::lookup_loaded_harness_by_id(preferred_runtime)
.map(|harness| harness.command.clone())
})
.unwrap_or_else(|| preferred_runtime.to_string());
selected_runtime.is_some_and(|selected| {
preferred_runtime.is_some_and(|preferred| preferred.id == selected.id)
})
}
/// Return the global provider/model values that are safe for this record.
pub(crate) fn global_model_provider_for_record<'a>(
record: &ManagedAgentRecord,
personas: &[AgentDefinition],
global: &'a GlobalAgentConfig,
) -> (Option<&'a str>, Option<&'a str>) {
if global_harness_defaults_apply_to_record(record, personas, global) {
(global.model.as_deref(), global.provider.as_deref())
} else {
(None, None)
}
record.agent_command.trim() == preferred_command.trim()
}
/// Return global env vars with harness-dependent defaults masked when an
@@ -307,7 +307,7 @@ fn bare_record() -> ManagedAgentRecord {
relay_url: "ws://localhost:3000".to_string(),
avatar_url: None,
acp_command: "buzz-acp".to_string(),
agent_command: "goose".to_string(),
agent_command: "buzz-agent".to_string(),
agent_command_override: None,
agent_command_override_is_implicit: false,
agent_args: vec![],
@@ -494,10 +494,6 @@ fn explicit_harness_override_keeps_defaults_from_a_different_preferred_runtime()
global_env_vars_for_record(&record, &personas, &global),
global.env_vars
);
assert_eq!(
global_env_vars_for_record(&record, &personas, &global),
global.env_vars
);
}
#[test]
@@ -547,6 +543,26 @@ fn explicit_harness_override_keeps_legacy_global_defaults() {
);
}
#[test]
fn runtime_fallback_does_not_inherit_defaults_from_a_different_preferred_runtime() {
let mut record = bare_record();
record.persona_id = Some("p1".to_string());
record.agent_command = "goose".to_string();
record.agent_command_override = None;
let personas = vec![persona("p1", None, None)];
let global = GlobalAgentConfig {
model: Some("auto".to_string()),
provider: Some("relay-mesh".to_string()),
preferred_runtime: Some("buzz-agent".to_string()),
..Default::default()
};
assert_eq!(
resolve_effective_model_provider(&record, &personas, &global),
(None, None)
);
}
#[test]
fn default_command_fallback_does_not_inherit_defaults_from_another_preferred_runtime() {
let mut record = bare_record();
@@ -571,7 +587,8 @@ fn default_command_fallback_does_not_inherit_defaults_from_another_preferred_run
fn runtime_fallback_inherits_defaults_when_it_matches_the_preferred_runtime() {
let mut record = bare_record();
record.persona_id = Some("p1".to_string());
record.agent_command_override = Some("goose".to_string());
record.agent_command = "goose".to_string();
record.agent_command_override = None;
let personas = vec![persona("p1", None, None)];
let global = GlobalAgentConfig {
model: Some("global-model".to_string()),
@@ -582,7 +599,10 @@ fn runtime_fallback_inherits_defaults_when_it_matches_the_preferred_runtime() {
assert_eq!(
resolve_effective_model_provider(&record, &personas, &global),
(Some("global-model"), Some("global-provider"))
(
Some("global-model".to_string()),
Some("global-provider".to_string())
)
);
}
@@ -601,7 +621,10 @@ fn explicit_runtime_keeps_global_defaults_when_another_runtime_is_preferred() {
assert_eq!(
resolve_effective_model_provider(&record, &personas, &global),
(Some("global-model"), Some("global-provider"))
(
Some("global-model".to_string()),
Some("global-provider".to_string())
)
);
}
+2 -3
View File
@@ -52,9 +52,8 @@ pub use env_vars::*;
pub(crate) use git_bash::git_bash_available;
pub(crate) use git_bash::{discover_git_bash, GitBashPrerequisite};
pub(crate) use global_config::{
global_env_vars_for_record, global_model_provider_for_record, load_global_agent_config,
resolve_effective_model_provider, save_global_agent_config, validate_global_config,
GlobalAgentConfig,
global_env_vars_for_record, load_global_agent_config, resolve_effective_model_provider,
save_global_agent_config, validate_global_config, GlobalAgentConfig,
};
pub(crate) use managed_node_paths::*;
pub use nest::*;
@@ -15,6 +15,7 @@ fn sample_record() -> ManagedAgentRecord {
acp_command: "buzz-acp".into(),
agent_command: "goose".into(),
agent_command_override: None,
agent_command_override_is_implicit: false,
agent_args: vec![],
mcp_command: String::new(),
turn_timeout_seconds: 320,
@@ -252,8 +252,7 @@ fn resolve_effective_agent_env_with_def(
}
}
// Layer 3a: global env vars — the lowest user-settable layer.
// Injected before persona/agent so per-agent values win on collision.
// Layer 3a: global env vars, injected before persona/agent overrides.
// `merged_user_env` with an empty "lower" map applies reserved/malformed-key
// filtering to the global map for free.
let applicable_global_env =
@@ -477,6 +477,7 @@ fn global_model_change_trips_hash_without_model_env_var() {
let mut rec = record();
rec.persona_id = Some("p1".into());
rec.agent_command = "some-harness-without-model-env".into();
rec.agent_command_override = Some(rec.agent_command.clone());
let personas = vec![{
let mut p = persona("p1", None, "prompt");
@@ -2,11 +2,6 @@ import * as React from "react";
import { ChevronDown } from "lucide-react";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import type {
AcpRuntimeCatalogEntry,
CreatePersonaInput,
UpdatePersonaInput,
} from "@/shared/api/types";
import { cn } from "@/shared/lib/cn";
import { Button } from "@/shared/ui/button";
import { ChooserDialogContent } from "@/shared/ui/chooser-dialog-content";
@@ -89,31 +84,8 @@ import {
useSelectableAcpRuntimes,
visibleAcpRuntimeSeedForCreate,
} from "../lib/runtimeVisibilityPreference";
type AgentDefinitionDialogProps = {
open: boolean;
title: string;
description: string;
submitLabel: string;
initialValues: CreatePersonaInput | UpdatePersonaInput | null;
error: Error | null;
isPending: boolean;
runtimes: AcpRuntimeCatalogEntry[];
runtimesLoading?: boolean;
onOpenChange: (open: boolean) => void;
onSubmit: (
input: CreatePersonaInput | UpdatePersonaInput,
) => Promise<unknown>;
/** Rendered below the form fields in create mode only ("Where to run"). */
createRunSection?: React.ReactNode;
/** Extra create-mode submit gate (e.g. incomplete provider config). */
createSubmitBlocked?: boolean;
};
const ADVANCED_FIELDS_MOTION_TRANSITION = {
duration: 0.18,
ease: [0.23, 1, 0.32, 1],
} as const;
import type { AgentDefinitionDialogProps } from "./AgentDefinitionDialog.types";
import { ADVANCED_FIELDS_MOTION_TRANSITION } from "./agentAdvancedFieldsMotion";
export function AgentDefinitionDialog({
open,
@@ -400,11 +372,7 @@ export function AgentDefinitionDialog({
(runtime.trim().length > 0 && runtimeCanChooseLlmProvider) ||
blankRuntimeModelProviderEditable;
const trimmedProvider = provider.trim();
// Required credential env keys for this runtime + provider combination.
// Used to show required markers on the LLM provider label and amber
// locked rows in the env vars editor.
// File-layer config for the selected runtime (e.g. goose config.yaml).
// Used to silence requirements already satisfied there.
// File config satisfies credentials before the readiness gate renders them.
const { data: runtimeFileConfig } = useRuntimeFileConfigQuery(runtime, {
enabled: open,
});
@@ -0,0 +1,27 @@
import type { ReactNode } from "react";
import type {
AcpRuntimeCatalogEntry,
CreatePersonaInput,
UpdatePersonaInput,
} from "@/shared/api/types";
export type AgentDefinitionDialogProps = {
open: boolean;
title: string;
description: string;
submitLabel: string;
initialValues: CreatePersonaInput | UpdatePersonaInput | null;
error: Error | null;
isPending: boolean;
runtimes: AcpRuntimeCatalogEntry[];
runtimesLoading?: boolean;
onOpenChange: (open: boolean) => void;
onSubmit: (
input: CreatePersonaInput | UpdatePersonaInput,
) => Promise<unknown>;
/** Rendered below the form fields in create mode only ("Where to run"). */
createRunSection?: ReactNode;
/** Extra create-mode submit gate (e.g. incomplete provider config). */
createSubmitBlocked?: boolean;
};
@@ -13,11 +13,9 @@ import {
} from "@/features/agents/hooks";
import { isManagedAgentActive } from "@/features/agents/lib/managedAgentControlActions";
import type {
ManagedAgent,
RespondToMode,
UpdateManagedAgentInput,
} from "@/shared/api/types";
import type { EditAgentFocusTarget } from "@/features/agents/openEditAgentEvent";
import { cn } from "@/shared/lib/cn";
import { Button } from "@/shared/ui/button";
import { ChooserDialogContent } from "@/shared/ui/chooser-dialog-content";
@@ -85,13 +83,10 @@ import { useProviderApiKeyFieldState } from "./providerApiKeyFieldState";
import { useSelectableAcpRuntimes } from "../lib/runtimeVisibilityPreference";
import { resolveModelFieldStatusMessage } from "./agentConfigControls";
import { AdvancedRequiredBadge } from "./AdvancedRequiredBadge";
import { ADVANCED_FIELDS_MOTION_TRANSITION } from "./agentAdvancedFieldsMotion";
import type { AgentInstanceEditDialogProps } from "./AgentInstanceEditDialog.types";
import { showAgentProfileSyncWarning } from "./agentProfileSyncWarning";
const ADVANCED_FIELDS_MOTION_TRANSITION = {
duration: 0.18,
ease: [0.23, 1, 0.32, 1],
} as const;
export function AgentInstanceEditDialog({
agent,
initialFocus,
@@ -99,16 +94,7 @@ export function AgentInstanceEditDialog({
onEditLinkedPersona,
onOpenChange,
onUpdated,
}: {
agent: ManagedAgent;
/** Optional field to scroll/focus when the dialog opens from a card deep-link. */
initialFocus?: EditAgentFocusTarget;
open: boolean;
/** Present only when the linked definition is editable (non-built-in, resolved). Caller closes this dialog and enters definition-edit. */
onEditLinkedPersona?: () => void;
onOpenChange: (open: boolean) => void;
onUpdated?: (agent: ManagedAgent) => void;
}) {
}: AgentInstanceEditDialogProps) {
const updateMutation = useUpdateManagedAgentMutation();
const startMutation = useStartManagedAgentMutation();
const runtimesQuery = useAcpRuntimesQuery({ enabled: open });
@@ -0,0 +1,14 @@
import type { EditAgentFocusTarget } from "@/features/agents/openEditAgentEvent";
import type { ManagedAgent } from "@/shared/api/types";
export type AgentInstanceEditDialogProps = {
agent: ManagedAgent;
/** Optional field to scroll/focus when the dialog opens from a card deep-link. */
initialFocus?: EditAgentFocusTarget;
open: boolean;
/** Present only when the linked definition is editable (non-built-in,
* resolved). Caller closes this dialog and enters definition-edit. */
onEditLinkedPersona?: () => void;
onOpenChange: (open: boolean) => void;
onUpdated?: (agent: ManagedAgent) => void;
};
@@ -0,0 +1,4 @@
export const ADVANCED_FIELDS_MOTION_TRANSITION = {
duration: 0.18,
ease: [0.23, 1, 0.32, 1],
} as const;
@@ -91,6 +91,34 @@ test("welcome readiness evaluates the runtime selected by the visible fallback",
);
});
test("welcome readiness keeps legacy Buzz Agent provider defaults", () => {
const runtimes = [
{
id: "goose",
label: "Goose",
availability: "available",
authStatus: { status: "not_applicable" },
},
{
id: "buzz-agent",
label: "Buzz Agent",
availability: "available",
authStatus: { status: "not_applicable" },
},
];
const globalConfig = {
env_vars: { OPENAI_COMPAT_API_KEY: "e2e-placeholder" },
provider: "openai",
model: "gpt-5.5",
preferred_runtime: null,
};
assert.deepEqual(resolveWelcomeAgentReadiness(runtimes, globalConfig, []), {
ready: true,
reason: "buzz-agent",
});
});
test("welcome provisioning requires an enabled available runtime", () => {
const runtimes = [
{
+3 -10
View File
@@ -449,17 +449,10 @@ export type CreateManagedAgentInput = {
relayUrl?: string;
acpCommand?: string;
agentCommand?: string;
/**
* True when `agentCommand` is a runtime command the caller deliberately wants
* to preserve instead of inheriting the linked persona command. This covers
* deploy-dialog runtime selections and discovered or installed aliases for the
* same persona runtime id, while still ignoring missing-runtime fallbacks.
*/
/** True when `agentCommand` must remain pinned instead of inheriting the persona.
* Covers explicit choices and installed aliases, but not missing-runtime fallbacks. */
harnessOverride?: boolean;
/**
* True when `harnessOverride` pins an automatically selected runtime for a
* runtime-less persona rather than a runtime the user explicitly selected.
*/
/** Whether the pinned runtime is an automatic fallback for a runtime-less persona. */
implicitHarnessFallback?: boolean;
agentArgs?: string[];
mcpCommand?: string;
+1 -1
View File
@@ -2681,7 +2681,7 @@ test("first-run onboarding posts the live Fizz kickoff", async ({ page }) => {
page,
{
globalAgentConfig: {
env_vars: { OPENAI_API_KEY: "e2e-placeholder" },
env_vars: { OPENAI_COMPAT_API_KEY: "e2e-placeholder" },
provider: "openai",
model: "gpt-5.5",
},