mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Filter hidden harnesses from remaining pickers
Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
nextDisabledAcpRuntimeIds,
|
||||
parseDisabledAcpRuntimeIds,
|
||||
readDisabledAcpRuntimeIds,
|
||||
runtimesForAcpConfigurationPicker,
|
||||
runtimesForImplicitAcpSelection,
|
||||
visibleAcpRuntimeSeedForCreate,
|
||||
} from "./runtimeVisibilityPreference.ts";
|
||||
@@ -56,6 +57,27 @@ test("disabled runtimes are removed from selectable catalog entries", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("configuration pickers hide new choices but preserve the current runtime", () => {
|
||||
const runtimes = [
|
||||
{ id: "buzz-agent", label: "Buzz Agent" },
|
||||
{ id: "goose", label: "Goose" },
|
||||
{ id: "codex", label: "Codex" },
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
runtimesForAcpConfigurationPicker(
|
||||
runtimes,
|
||||
["buzz-agent", "goose"],
|
||||
"goose",
|
||||
),
|
||||
[runtimes[2], runtimes[1]],
|
||||
);
|
||||
assert.deepEqual(
|
||||
runtimesForAcpConfigurationPicker(runtimes, ["buzz-agent", "goose"], null),
|
||||
[runtimes[2]],
|
||||
);
|
||||
});
|
||||
|
||||
test("create seeds replace hidden runtimes but preserve selectable ones", () => {
|
||||
const runtimes = [{ id: "buzz-agent" }, { id: "goose" }];
|
||||
assert.equal(
|
||||
|
||||
@@ -95,6 +95,38 @@ export function filterEnabledAcpRuntimes<T extends { id: string }>(
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter new configuration choices while preserving one explicitly saved
|
||||
* runtime as a current-only option.
|
||||
*/
|
||||
export function runtimesForAcpConfigurationPicker<T extends { id: string }>(
|
||||
runtimes: readonly T[],
|
||||
disabledRuntimeIds: readonly string[],
|
||||
currentRuntimeId?: string | null,
|
||||
): T[] {
|
||||
const selectableRuntimes = filterEnabledAcpRuntimes(
|
||||
runtimes,
|
||||
disabledRuntimeIds,
|
||||
);
|
||||
const normalizedCurrentRuntimeId = normalizeRuntimeId(currentRuntimeId ?? "");
|
||||
if (
|
||||
!normalizedCurrentRuntimeId ||
|
||||
selectableRuntimes.some(
|
||||
(runtime) =>
|
||||
normalizeRuntimeId(runtime.id) === normalizedCurrentRuntimeId,
|
||||
)
|
||||
) {
|
||||
return selectableRuntimes;
|
||||
}
|
||||
|
||||
const currentRuntime = runtimes.find(
|
||||
(runtime) => normalizeRuntimeId(runtime.id) === normalizedCurrentRuntimeId,
|
||||
);
|
||||
return currentRuntime
|
||||
? [...selectableRuntimes, currentRuntime]
|
||||
: selectableRuntimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply visibility only when the app is choosing a runtime implicitly.
|
||||
* Existing definitions pinned to a runtime remain runnable.
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
} from "@/features/agents/ui/AgentConfigFields";
|
||||
import { resetConfigForHarnessChange } from "@/features/agents/ui/agentConfigOptions";
|
||||
import { AgentDropdownSelect } from "@/features/agents/ui/agentConfigControls";
|
||||
import { useDisabledAcpRuntimeIds } from "@/features/agents/lib/runtimeVisibilityPreference";
|
||||
import { createSaveCoalescer } from "./saveCoalescer";
|
||||
import { getBakedBuildEnv, type BakedEnvEntry } from "@/shared/api/tauri";
|
||||
import {
|
||||
@@ -127,14 +128,16 @@ function AgentDefaultsSection({
|
||||
() => new Set(effectiveReadyRuntimeIds),
|
||||
[effectiveReadyRuntimeIds],
|
||||
);
|
||||
const disabledRuntimeIds = useDisabledAcpRuntimeIds();
|
||||
// Setup already confirmed readiness. Re-filter only for onboarding
|
||||
// visibility here; a transient auth recheck must not invalidate that handoff.
|
||||
const readyRuntimes = React.useMemo(
|
||||
() =>
|
||||
getVisibleOnboardingRuntimes(runtimesQuery.data ?? []).filter((runtime) =>
|
||||
readyRuntimeIdSet.has(runtime.id),
|
||||
),
|
||||
[readyRuntimeIdSet, runtimesQuery.data],
|
||||
getVisibleOnboardingRuntimes(
|
||||
runtimesQuery.data ?? [],
|
||||
disabledRuntimeIds,
|
||||
).filter((runtime) => readyRuntimeIdSet.has(runtime.id)),
|
||||
[disabledRuntimeIds, readyRuntimeIdSet, runtimesQuery.data],
|
||||
);
|
||||
const selectedRuntime = React.useMemo(
|
||||
() =>
|
||||
|
||||
@@ -34,6 +34,18 @@ test("visible onboarding runtimes use the product order", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("onboarding defaults exclude device-disabled harnesses", () => {
|
||||
const runtimes = [
|
||||
runtime("codex", "available", "logged_in"),
|
||||
runtime("claude", "available", "logged_in"),
|
||||
];
|
||||
|
||||
assert.deepEqual(
|
||||
getVisibleOnboardingRuntimes(runtimes, ["claude"]).map(({ id }) => id),
|
||||
["codex"],
|
||||
);
|
||||
});
|
||||
|
||||
test("readiness requires an available and authenticated runtime", () => {
|
||||
assert.equal(
|
||||
runtimeIsReadyForOnboarding(runtime("claude", "available", "logged_in")),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AcpRuntimeCatalogEntry } from "@/shared/api/types";
|
||||
import { runtimesForAcpConfigurationPicker } from "@/features/agents/lib/runtimeVisibilityPreference";
|
||||
|
||||
export const ONBOARDING_RUNTIME_ORDER = [
|
||||
"claude",
|
||||
@@ -25,8 +26,9 @@ export function runtimeIsReadyForOnboarding(runtime: AcpRuntimeCatalogEntry) {
|
||||
|
||||
export function getVisibleOnboardingRuntimes(
|
||||
runtimes: readonly AcpRuntimeCatalogEntry[],
|
||||
disabledRuntimeIds: readonly string[] = [],
|
||||
) {
|
||||
return runtimes
|
||||
return runtimesForAcpConfigurationPicker(runtimes, disabledRuntimeIds)
|
||||
.filter((runtime) => runtimeIsVisibleInOnboarding(runtime.id))
|
||||
.sort(
|
||||
(left, right) =>
|
||||
|
||||
@@ -16,6 +16,10 @@ import {
|
||||
usePersonasQuery,
|
||||
useTeamsQuery,
|
||||
} from "@/features/agents/hooks";
|
||||
import {
|
||||
runtimesForAcpConfigurationPicker,
|
||||
useDisabledAcpRuntimeIds,
|
||||
} from "@/features/agents/lib/runtimeVisibilityPreference";
|
||||
import {
|
||||
useChannelTemplatesQuery,
|
||||
useCreateChannelTemplateMutation,
|
||||
@@ -292,6 +296,7 @@ function TemplateFormDialog({
|
||||
const teamsQuery = useTeamsQuery();
|
||||
const providersQuery = useAvailableAcpRuntimes();
|
||||
const runtimes = providersQuery.data ?? [];
|
||||
const disabledRuntimeIds = useDisabledAcpRuntimeIds();
|
||||
|
||||
const [name, setName] = React.useState("");
|
||||
const [description, setDescription] = React.useState("");
|
||||
@@ -559,6 +564,7 @@ function TemplateFormDialog({
|
||||
personaRuntimes={personaRuntimes}
|
||||
providers={runtimes}
|
||||
providersLoading={providersQuery.isLoading}
|
||||
disabledRuntimeIds={disabledRuntimeIds}
|
||||
selectedPersonaIds={selectedPersonaIds}
|
||||
selectedTeamIds={selectedTeamIds}
|
||||
teamRuntimes={teamRuntimes}
|
||||
@@ -638,6 +644,7 @@ function TemplateTeamSelector({
|
||||
}
|
||||
|
||||
function RuntimeAssignments({
|
||||
disabledRuntimeIds,
|
||||
isPending,
|
||||
onPersonaRuntimeChange,
|
||||
onTeamRuntimeChange,
|
||||
@@ -650,6 +657,7 @@ function RuntimeAssignments({
|
||||
teamRuntimes,
|
||||
teams,
|
||||
}: {
|
||||
disabledRuntimeIds: readonly string[];
|
||||
isPending: boolean;
|
||||
onPersonaRuntimeChange: (personaId: string, runtimeId: string) => void;
|
||||
onTeamRuntimeChange: (teamId: string, runtimeId: string) => void;
|
||||
@@ -697,6 +705,7 @@ function RuntimeAssignments({
|
||||
onChange={(runtimeId) =>
|
||||
onPersonaRuntimeChange(persona.id, runtimeId)
|
||||
}
|
||||
disabledRuntimeIds={disabledRuntimeIds}
|
||||
providers={providers}
|
||||
value={personaRuntimes[persona.id] ?? ""}
|
||||
/>
|
||||
@@ -708,6 +717,7 @@ function RuntimeAssignments({
|
||||
icon="team"
|
||||
label={team.name}
|
||||
onChange={(runtimeId) => onTeamRuntimeChange(team.id, runtimeId)}
|
||||
disabledRuntimeIds={disabledRuntimeIds}
|
||||
providers={providers}
|
||||
value={teamRuntimes[team.id] ?? ""}
|
||||
/>
|
||||
@@ -721,6 +731,7 @@ function RuntimeAssignments({
|
||||
function RuntimeRow({
|
||||
avatarUrl,
|
||||
disabled,
|
||||
disabledRuntimeIds,
|
||||
icon,
|
||||
label,
|
||||
onChange,
|
||||
@@ -729,12 +740,24 @@ function RuntimeRow({
|
||||
}: {
|
||||
avatarUrl?: string | null | undefined;
|
||||
disabled: boolean;
|
||||
disabledRuntimeIds: readonly string[];
|
||||
icon?: "team";
|
||||
label: string;
|
||||
onChange: (runtimeId: string) => void;
|
||||
providers: AcpRuntime[];
|
||||
value: string;
|
||||
}) {
|
||||
const runtimeOptions = runtimesForAcpConfigurationPicker(
|
||||
providers,
|
||||
disabledRuntimeIds,
|
||||
value,
|
||||
);
|
||||
const selectableRuntimeIds = new Set(
|
||||
runtimesForAcpConfigurationPicker(providers, disabledRuntimeIds).map(
|
||||
(runtime) => runtime.id,
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2">
|
||||
@@ -756,9 +779,12 @@ function RuntimeRow({
|
||||
value={value}
|
||||
>
|
||||
<option value="">Default</option>
|
||||
{providers.map((runtime) => (
|
||||
{runtimeOptions.map((runtime) => (
|
||||
<option key={runtime.id} value={runtime.id}>
|
||||
{runtime.label}
|
||||
{value === runtime.id && !selectableRuntimeIds.has(runtime.id)
|
||||
? " (current)"
|
||||
: ""}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { ACP_RUNTIME_VISIBILITY_STORAGE_KEY } from "@/features/agents/lib/runtimeVisibilityPreference";
|
||||
import { installMockBridge } from "../helpers/bridge";
|
||||
import { passThroughBackupStep } from "../helpers/onboarding";
|
||||
|
||||
@@ -581,6 +582,42 @@ test("defaults auto-selects the only ready visible harness", async ({
|
||||
await expect.poll(() => readSavedRuntime(page)).toBe("claude");
|
||||
});
|
||||
|
||||
test("defaults exclude device-disabled ready harnesses", async ({ page }) => {
|
||||
await page.addInitScript((storageKey) => {
|
||||
window.localStorage.setItem(storageKey, JSON.stringify(["claude"]));
|
||||
}, ACP_RUNTIME_VISIBILITY_STORAGE_KEY);
|
||||
await installMockBridge(
|
||||
page,
|
||||
{
|
||||
acpRuntimesCatalog: [
|
||||
runtime("claude", "available", { status: "logged_in" }),
|
||||
runtime("codex", "available", { status: "logged_in" }),
|
||||
],
|
||||
globalAgentConfig: {
|
||||
env_vars: {},
|
||||
provider: null,
|
||||
model: null,
|
||||
preferred_runtime: null,
|
||||
},
|
||||
},
|
||||
{ skipCommunitySeed: true, skipOnboardingSeed: true },
|
||||
);
|
||||
await page.goto("/");
|
||||
await navigateToSetupPage(page);
|
||||
await page.getByTestId("onboarding-setup-next").click();
|
||||
await expect(page.getByTestId("onboarding-page-config")).toBeVisible();
|
||||
|
||||
const harness = page.getByTestId("global-agent-default-harness");
|
||||
await expect(harness).toHaveText("Codex");
|
||||
await harness.click();
|
||||
await expect(
|
||||
page.getByTestId("global-agent-default-harness-option-claude"),
|
||||
).toHaveCount(0);
|
||||
await expect(
|
||||
page.getByTestId("global-agent-default-harness-option-codex"),
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("Finish waits for the latest rapid harness choice to persist", async ({
|
||||
page,
|
||||
}) => {
|
||||
|
||||
Reference in New Issue
Block a user