Filter implicit runtime fallbacks centrally

Signed-off-by: kenny lopez <klopez4212@gmail.com>
This commit is contained in:
kenny lopez
2026-07-27 13:28:06 +01:00
parent a392ee1f65
commit 2864db1ca7
3 changed files with 99 additions and 15 deletions
+8 -6
View File
@@ -96,12 +96,14 @@ with a TypeScript lookup table or an id comparison in a component.
editor persists its visible fallback on the next save. Its dependent
provider/model defaults are also ignored for new implicit fallback agents,
without changing the persisted configuration used by existing agents.
`resolveStartRuntimeForDefinition` is the shared boundary that filters every
runtime-less start through the device's visible-runtime set; call it instead
of duplicating default selection in a start surface. Team deploys use the
same visible-runtime set only for members that need an implicit fallback, so
a fully pinned team remains deployable even when every installed runtime is
hidden. Definitions already pinned to a hidden runtime remain runnable.
`resolvePersonaRuntime` is the shared visibility boundary for every
runtime-less deployment or provisioning path. Definition-to-instance starts
use the stricter `resolveStartRuntimeForDefinition` wrapper; call one of
these shared resolvers instead of duplicating default selection in a start
surface. Team deploys use the same visible-runtime set only for members that
need an implicit fallback, so a fully pinned team remains deployable even
when every installed runtime is hidden. Definitions already pinned to a
hidden runtime remain runnable.
10. **The defaults modal is progressively disclosed.** An unset global config
starts on the Buzz Agent-first deployment fallback and carries that visible
harness into the next saved edit. The `progressive-defaults` disclosure
@@ -33,6 +33,46 @@ test("resolvePersonaRuntime — undefined personaRuntimeId also returns defaultR
});
});
test("resolvePersonaRuntime — hidden defaults are skipped for runtime-less personas", () => {
const result = resolvePersonaRuntime(null, runtimes, goose, false, ["goose"]);
assert.deepEqual(result, {
runtime: claude,
warnings: [],
isOverridden: false,
});
});
test("resolvePersonaRuntime — explicitly pinned hidden runtimes remain available", () => {
const result = resolvePersonaRuntime("goose", runtimes, claude, false, [
"goose",
]);
assert.deepEqual(result, {
runtime: goose,
warnings: [],
isOverridden: false,
});
});
test("resolvePersonaRuntime — hidden fallback is replaced when a pinned runtime is unavailable", () => {
const result = resolvePersonaRuntime("unknown-rt", runtimes, goose, false, [
"goose",
]);
assert.equal(result.runtime, claude);
assert.equal(result.warnings.length, 1);
assert.match(result.warnings[0], /Claude/);
assert.equal(result.isOverridden, true);
});
test("resolvePersonaRuntime — no implicit fallback remains when every runtime is hidden", () => {
const result = resolvePersonaRuntime(null, runtimes, goose, false, [
"goose",
"claude",
]);
assert.equal(result.runtime, null);
assert.equal(result.warnings.length, 1);
assert.equal(result.isOverridden, false);
});
test("resolvePersonaRuntime — no personaRuntimeId and no defaultRuntime returns null with warning", () => {
const result = resolvePersonaRuntime(null, runtimes, null);
assert.equal(result.runtime, null);
@@ -1,4 +1,8 @@
import type { AcpRuntime, AcpRuntimeCatalogEntry } from "@/shared/api/types";
import {
filterEnabledAcpRuntimes,
getDisabledAcpRuntimeIdsSnapshot,
} from "./runtimeVisibilityPreference";
/**
* Select the best default runtime from a catalog, using the same preference
@@ -55,18 +59,31 @@ export type ResolvePersonaRuntimeResult = {
* fall back to `defaultRuntime` and emit a warning.
* 4. If there is no `defaultRuntime` either → return `null` with an error
* warning so the UI can block deployment.
*
* Hidden runtimes remain eligible when explicitly pinned by a persona. They
* are removed only from the implicit fallback set, at this shared boundary,
* so every provisioning surface observes the device visibility preference.
*/
export function resolvePersonaRuntime(
personaRuntimeId: string | undefined | null,
runtimes: readonly AcpRuntime[],
defaultRuntime: AcpRuntime | null,
forceOverride?: boolean,
disabledRuntimeIds: readonly string[] = getDisabledAcpRuntimeIdsSnapshot(),
): ResolvePersonaRuntimeResult {
const implicitDefaultRuntime = forceOverride
? defaultRuntime
: resolveVisibleDefaultRuntime(
runtimes,
defaultRuntime,
disabledRuntimeIds,
);
// Case 1: Persona has no runtime preference — use the default.
if (!personaRuntimeId) {
return {
runtime: defaultRuntime,
warnings: defaultRuntime
runtime: implicitDefaultRuntime,
warnings: implicitDefaultRuntime
? []
: [
"No agent runtimes are available. Install a runtime (e.g. Goose) to deploy agents.",
@@ -78,28 +95,35 @@ export function resolvePersonaRuntime(
// Case 2: Persona's preferred runtime is available.
const matched = runtimes.find((p) => p.id === personaRuntimeId);
if (matched) {
if (forceOverride && defaultRuntime && matched.id !== defaultRuntime.id) {
if (
forceOverride &&
implicitDefaultRuntime &&
matched.id !== implicitDefaultRuntime.id
) {
return {
runtime: defaultRuntime,
runtime: implicitDefaultRuntime,
warnings: [
`Runtime override: using ${defaultRuntime.label} instead of ${matched.label}.`,
`Runtime override: using ${implicitDefaultRuntime.label} instead of ${matched.label}.`,
],
isOverridden: true,
};
}
return {
runtime: forceOverride && defaultRuntime ? defaultRuntime : matched,
runtime:
forceOverride && implicitDefaultRuntime
? implicitDefaultRuntime
: matched,
warnings: [],
isOverridden: false,
};
}
// Case 3 & 4: Persona's runtime is not available — fall back.
if (defaultRuntime) {
if (implicitDefaultRuntime) {
return {
runtime: defaultRuntime,
runtime: implicitDefaultRuntime,
warnings: [
`This agent is configured for runtime "${personaRuntimeId}" but it is not available. Using ${defaultRuntime.label} instead.`,
`This agent is configured for runtime "${personaRuntimeId}" but it is not available. Using ${implicitDefaultRuntime.label} instead.`,
],
isOverridden: true,
};
@@ -114,6 +138,24 @@ export function resolvePersonaRuntime(
};
}
function resolveVisibleDefaultRuntime(
runtimes: readonly AcpRuntime[],
defaultRuntime: AcpRuntime | null,
disabledRuntimeIds: readonly string[],
): AcpRuntime | null {
if (!defaultRuntime) return null;
const visibleRuntimes = filterEnabledAcpRuntimes(
runtimes,
disabledRuntimeIds,
);
return (
visibleRuntimes.find((runtime) => runtime.id === defaultRuntime.id) ??
visibleRuntimes[0] ??
null
);
}
/**
* Collect runtime-resolution warnings for a list of personas.
*