mirror of
https://github.com/block/buzz.git
synced 2026-11-02 23:41:08 +00:00
Add preferred runtime onboarding (#2040)
Signed-off-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Pinky <44b8e82baa6e0e254e0208d68f335c283c94e7b78dd1fa10d5a49d3f13dd0435@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
@@ -120,6 +120,23 @@ test("row 3: plain avatar URLs pass through; base64 data URIs upload via the inj
|
||||
assert.equal(uploads.length, 1, "upload must go through the injected fn");
|
||||
});
|
||||
|
||||
test("row 3: failed persona avatar upload never substitutes the runtime avatar", async () => {
|
||||
const input = await buildInstanceInputForDefinition(
|
||||
persona({
|
||||
id: "builtin:fizz",
|
||||
displayName: "Fizz",
|
||||
avatarUrl: "data:image/png;base64,aGk=",
|
||||
}),
|
||||
claudeRuntime,
|
||||
async () => {
|
||||
throw new Error("upload failed");
|
||||
},
|
||||
);
|
||||
|
||||
assert.equal(input.avatarUrl, undefined);
|
||||
assert.notEqual(input.avatarUrl, claudeRuntime.avatarUrl);
|
||||
});
|
||||
|
||||
test("mapping carries the runtime and definition fields", async () => {
|
||||
const input = await buildInstanceInputForDefinition(persona(), gooseRuntime);
|
||||
assert.equal(input.name, "Test Agent");
|
||||
|
||||
@@ -45,11 +45,12 @@ export async function availableRuntimesForStart(
|
||||
export function resolveStartRuntimeForDefinition(
|
||||
persona: AgentPersona,
|
||||
runtimes: readonly AcpRuntime[],
|
||||
preferredRuntimeId?: string | null,
|
||||
): { runtime: AcpRuntime; warnings: string[] } {
|
||||
// Use the buzz-agent-first preference (buzz-agent → goose → first available)
|
||||
// so a freshly installed goose never beats the bundled buzz-agent sidecar
|
||||
// for runtime-less personas (item 13 regression guard).
|
||||
const defaultRuntime = getDefaultPersonaRuntime(runtimes);
|
||||
const defaultRuntime = getDefaultPersonaRuntime(runtimes, preferredRuntimeId);
|
||||
const { runtime, warnings, isOverridden }: ResolvePersonaRuntimeResult =
|
||||
resolvePersonaRuntime(persona.runtime, runtimes, defaultRuntime);
|
||||
|
||||
@@ -115,7 +116,6 @@ export async function buildInstanceInputForDefinition(
|
||||
const avatarUrl = await resolveManagedAgentAvatarUrl(
|
||||
persona.avatarUrl,
|
||||
upload,
|
||||
runtime.avatarUrl,
|
||||
);
|
||||
|
||||
const base = {
|
||||
|
||||
@@ -12,11 +12,13 @@ import type { AcpRuntime, AcpRuntimeCatalogEntry } from "@/shared/api/types";
|
||||
*/
|
||||
export function getDefaultPersonaRuntime<T extends AcpRuntimeCatalogEntry>(
|
||||
runtimes: readonly T[],
|
||||
preferredRuntimeId?: string | null,
|
||||
): T | null {
|
||||
const available = runtimes.filter(
|
||||
(runtime) => runtime.availability === "available",
|
||||
);
|
||||
return (
|
||||
available.find((runtime) => runtime.id === preferredRuntimeId) ??
|
||||
available.find((runtime) => runtime.id === "buzz-agent") ??
|
||||
available.find((runtime) => runtime.id === "goose") ??
|
||||
available[0] ??
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
useAvailableAcpRuntimes,
|
||||
useCreateChannelManagedAgentsMutation,
|
||||
} from "@/features/agents/hooks";
|
||||
import { useGlobalAgentConfig } from "@/features/agents/useGlobalAgentConfig";
|
||||
import type { CreateChannelManagedAgentsResult } from "@/features/agents/channelAgents";
|
||||
import {
|
||||
emptyResolvedTeamPersonas,
|
||||
@@ -50,6 +51,7 @@ export function AddTeamToChannelDialog({
|
||||
onOpenChange,
|
||||
onDeployed,
|
||||
}: AddTeamToChannelDialogProps) {
|
||||
const { globalConfig } = useGlobalAgentConfig();
|
||||
const channelsQuery = useChannelsQuery();
|
||||
const providersQuery = useAvailableAcpRuntimes();
|
||||
const [channelId, setChannelId] = React.useState("");
|
||||
@@ -69,7 +71,10 @@ export function AddTeamToChannelDialog({
|
||||
const runtimes = providersQuery.data ?? [];
|
||||
// Use the buzz-agent-first preference so the team-deploy fallback mirrors the
|
||||
// single-agent start path (buzz-agent → goose → first available).
|
||||
const defaultProvider = getDefaultPersonaRuntime(runtimes);
|
||||
const defaultProvider = getDefaultPersonaRuntime(
|
||||
runtimes,
|
||||
globalConfig.preferred_runtime,
|
||||
);
|
||||
|
||||
const teamPersonaResolution = React.useMemo(
|
||||
() =>
|
||||
|
||||
@@ -156,9 +156,17 @@ export function AgentDefinitionDialog({
|
||||
const [showAdvancedFields, setShowAdvancedFields] = React.useState(false);
|
||||
const [isAvatarUploadPending, setIsAvatarUploadPending] =
|
||||
React.useState(false);
|
||||
const {
|
||||
globalConfig,
|
||||
inheritedDefaults: {
|
||||
provider: inheritedProviderDefault,
|
||||
model: inheritedModelDefault,
|
||||
},
|
||||
inheritedEnvVars: inheritedEnvVarsForAdvanced,
|
||||
} = useAgentDialogDefaults({ open });
|
||||
const defaultRuntime = React.useMemo(
|
||||
() => getDefaultPersonaRuntime(runtimes),
|
||||
[runtimes],
|
||||
() => getDefaultPersonaRuntime(runtimes, globalConfig.preferred_runtime),
|
||||
[globalConfig.preferred_runtime, runtimes],
|
||||
);
|
||||
const shouldReduceMotion = useReducedMotion();
|
||||
const initialModelProviderEditableWithoutRuntime = Boolean(
|
||||
@@ -331,14 +339,6 @@ export function AgentDefinitionDialog({
|
||||
// Used to silence requirements already satisfied there.
|
||||
const { data: runtimeFileConfig, isLoading: fileConfigLoading } =
|
||||
useRuntimeFileConfigQuery(runtime, { enabled: open });
|
||||
const {
|
||||
globalConfig,
|
||||
inheritedDefaults: {
|
||||
provider: inheritedProviderDefault,
|
||||
model: inheritedModelDefault,
|
||||
},
|
||||
inheritedEnvVars: inheritedEnvVarsForAdvanced,
|
||||
} = useAgentDialogDefaults({ open });
|
||||
function handleAiConfigurationModeChange(nextMode: AgentAiConfigurationMode) {
|
||||
setAiConfigurationMode(nextMode);
|
||||
setIsCustomProviderEditing(false);
|
||||
|
||||
@@ -45,6 +45,7 @@ export const EMPTY_GLOBAL_CONFIG: GlobalAgentConfig = {
|
||||
env_vars: {},
|
||||
provider: null,
|
||||
model: null,
|
||||
preferred_runtime: null,
|
||||
};
|
||||
|
||||
/** Baked env keys that route to structured controls, not the generic env editor. */
|
||||
|
||||
@@ -75,6 +75,23 @@ test("getPersonaProviderOptions appends (current) tail for an unknown saved prov
|
||||
|
||||
// ── getDefaultPersonaRuntime — buzz-agent first ───────────────────────────────
|
||||
|
||||
test("getDefaultPersonaRuntime honors an available global preference", () => {
|
||||
const runtimes = [
|
||||
makeRuntime("buzz-agent"),
|
||||
makeRuntime("goose"),
|
||||
makeRuntime("claude"),
|
||||
];
|
||||
assert.equal(getDefaultPersonaRuntime(runtimes, "claude")?.id, "claude");
|
||||
});
|
||||
|
||||
test("getDefaultPersonaRuntime ignores an unavailable global preference", () => {
|
||||
const runtimes = [
|
||||
makeRuntime("buzz-agent"),
|
||||
makeRuntime("claude", "not_installed"),
|
||||
];
|
||||
assert.equal(getDefaultPersonaRuntime(runtimes, "claude")?.id, "buzz-agent");
|
||||
});
|
||||
|
||||
test("getDefaultPersonaRuntime returns buzz-agent over goose when both are available", () => {
|
||||
const runtimes = [
|
||||
makeRuntime("goose"),
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
useStopManagedAgentMutation,
|
||||
useDeleteManagedAgentMutation,
|
||||
} from "@/features/agents/hooks";
|
||||
import { useGlobalAgentConfig } from "@/features/agents/useGlobalAgentConfig";
|
||||
import { useChannelsQuery } from "@/features/channels/hooks";
|
||||
import { usePresenceQuery } from "@/features/presence/hooks";
|
||||
import type {
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
} from "../lib/instanceInputForDefinition";
|
||||
|
||||
export function useManagedAgentActions() {
|
||||
const { globalConfig } = useGlobalAgentConfig();
|
||||
const relayAgentsQuery = useRelayAgentsQuery();
|
||||
const managedAgentsQuery = useManagedAgentsQuery();
|
||||
const [shouldLoadChannels, setShouldLoadChannels] = React.useState(false);
|
||||
@@ -193,6 +195,7 @@ export function useManagedAgentActions() {
|
||||
const { runtime, warnings } = resolveStartRuntimeForDefinition(
|
||||
persona,
|
||||
runtimes,
|
||||
globalConfig.preferred_runtime,
|
||||
);
|
||||
const input = await buildInstanceInputForDefinition(persona, runtime);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ const EMPTY_CONFIG: GlobalAgentConfig = {
|
||||
env_vars: {},
|
||||
provider: null,
|
||||
model: null,
|
||||
preferred_runtime: null,
|
||||
};
|
||||
|
||||
export const globalAgentConfigQueryKey = ["globalAgentConfig"] as const;
|
||||
|
||||
Reference in New Issue
Block a user