Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
454d4c7
feat: replace secondary-model experiment with [subagent.models] pool
7Sageer Aug 5, 2026
fad5ca8
fix(agent-core-v2): harden subagent model-pool validation and error/p…
7Sageer Aug 5, 2026
d215e94
feat: re-add /secondary_model and accept a lone subagent default_model
7Sageer Aug 6, 2026
dc1eb7c
docs: use public model identifiers in the subagent model pool examples
7Sageer Aug 6, 2026
a89601c
refactor: rename /secondary_model to /secondary-model
7Sageer Aug 6, 2026
98a4a6d
test: cover the /secondary-model command name resolution
7Sageer Aug 6, 2026
c620153
Revert "test: cover the /secondary-model command name resolution"
7Sageer Aug 6, 2026
dc5f510
Merge branch 'main' into feat/subagent-model-pool
7Sageer Aug 11, 2026
b581ec9
feat(agent-core-v2): move the subagent model pool to [secondary_model]
7Sageer Aug 11, 2026
6c98b73
docs: correct default engine guidance
7Sageer Aug 11, 2026
c43c69f
feat(agent-core-v2): pin subagents to default_model with [secondary_m…
7Sageer Aug 11, 2026
add3738
docs: use real managed model aliases in the secondary_model examples
7Sageer Aug 11, 2026
b38912a
chore: simplify the subagent model pool changeset
7Sageer Aug 12, 2026
a80b9a6
feat(agent-core-v2): honor the legacy [secondary_model] model key as …
7Sageer Aug 12, 2026
4cf4621
Merge remote-tracking branch 'origin/main' into feat/subagent-model-pool
7Sageer Aug 12, 2026
8aeb6ed
refactor(node-sdk): export the reserved model-alias constants from th…
7Sageer Aug 12, 2026
dee665f
feat(agent-core-v2): keep the subagent model pool behind the secondar…
7Sageer Aug 12, 2026
be160c8
fix(node-sdk): cascade provider removal into the subagent model pool
7Sageer Aug 12, 2026
7240d1a
fix(agent-core-v2): close two subagent model pool validation gaps
7Sageer Aug 12, 2026
310b470
test(agent-core-v2): stub the model/provider registries in handler-ch…
7Sageer Aug 12, 2026
dc320ab
fix(kap-server): cascade REST provider deletion into the subagent mod…
7Sageer Aug 12, 2026
7f94519
fix: keep the subagent pool consistent on provider replace and preser…
7Sageer Aug 12, 2026
cda3cc1
fix(agent-core-v2): cascade the subagent pool on catalog refresh writes
7Sageer Aug 13, 2026
e16ca0e
fix: cover the last two model-table write paths for the subagent pool
7Sageer Aug 13, 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
2 changes: 1 addition & 1 deletion .agents/skills/agent-core-dev/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ pass `ConfigTarget.Memory` for a per-run override that is never written to disk.
- `src/kosong/model/thinking.ts` (owner domain, not `config`) — the `resolveThinkingEffort` helper and the authoritative `ThinkingConfig` type (the `thinking` section itself registers from `src/app/kosongConfig/configSection.ts`).
- `src/app/config/configPure.ts` — `isPlainObject`, `deepMerge`, `omitUndefined`, `describeUnknownError`.

A domain that owns a section keeps the schema in its own `configSection.ts` (e.g. `src/app/flag/flag.ts` for `experimental`, `src/agent/loop/configSection.ts` for `loopControl`). Exception: kosong-owned sections (`providers`, `models`, `thinking`) — kosong is a pure, persistence-free abstraction layer that defines only the types (`src/kosong/{provider,model}`); the section constants, the zod schemas (re-derived from those types and compile-time pinned via `AssertExact<Equal<z.infer<typeof Schema>, Type>>`, see `_base/utils/typeEquality.ts`), the registrations, env bindings, and TOML transforms all live in the persistence wrapper `src/app/kosongConfig/configSection.ts`. (`modelCatalog` and `secondaryModel` have no kosong-side type at all — their sections are fully self-contained in `app/kosongConfig`, types derived from the schemas.) A cross-section env overlay (e.g. the `KIMI_MODEL_*` synthesis) lives in the wrapper too (`src/app/kosongConfig/envOverlay.ts`; the `[secondary_model]` derived-entry synthesis in `secondaryModelOverlay.ts`) and is registered via module-level `registerConfigOverlay`. The two-way sync between config sections and kosong's in-memory registries is owned by `IKosongConfigService` (`src/app/kosongConfig/kosongConfigService.ts`).
A domain that owns a section keeps the schema in its own `configSection.ts` (e.g. `src/app/flag/flag.ts` for `experimental`, `src/agent/loop/configSection.ts` for `loopControl`). Exception: kosong-owned sections (`providers`, `models`, `thinking`) — kosong is a pure, persistence-free abstraction layer that defines only the types (`src/kosong/{provider,model}`); the section constants, the zod schemas (re-derived from those types and compile-time pinned via `AssertExact<Equal<z.infer<typeof Schema>, Type>>`, see `_base/utils/typeEquality.ts`), the registrations, env bindings, and TOML transforms all live in the persistence wrapper `src/app/kosongConfig/configSection.ts`. (`modelCatalog` has no kosong-side type at all — its section is fully self-contained in `app/kosongConfig`, types derived from the schema.) A cross-section env overlay (e.g. the `KIMI_MODEL_*` synthesis in `src/app/kosongConfig/envOverlay.ts`) lives in the wrapper too and is registered via module-level `registerConfigOverlay`. The session subagent domain owns two sections in `src/session/subagent/configSection.ts`: `[subagent]` (`timeout_ms` on disk) and `[secondary_model]` (`default_model` plus the `[secondary_model.models]` pool, with a lone legacy v1 `model` key honored as a fallback default below `default_model`), with the legacy `[subagent]` pool keys declared as deprecations; neither carries a cross-section overlay. Cross-field pool validation (default present / in-pool / every key resolvable) runs at session creation in `subagentModelsValidationService.ts`, not in the schema. The two-way sync between config sections and kosong's in-memory registries is owned by `IKosongConfigService` (`src/app/kosongConfig/kosongConfigService.ts`).

## Scope

Expand Down
5 changes: 5 additions & 0 deletions .changeset/remove-secondary-model-sdk.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code-sdk": minor
Comment on lines +1 to +2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the removed SDK method in this minor release

The changeset declares a minor SDK bump while the same commit deletes the public Session.applyPersistedSecondaryModel() method and its RPC surface. Existing JavaScript consumers will receive undefined at runtime, and TypeScript consumers can no longer compile after an otherwise compatible-range upgrade; either retain a deprecated compatibility shim or release the removal as a major change.

AGENTS.md reference: AGENTS.md:L59-L61

Useful? React with 👍 / 👎.

---

Remove the secondary-model session API `Session.applyPersistedSecondaryModel`; subagent model selection is configured via `[secondary_model]` in config.toml instead. The `SECONDARY_DERIVED_MODEL_ALIAS` export stays (the v1 engine still synthesizes the entry at runtime, so hosts keep filtering it out of model pickers), and the SDK now also exports `PRIMARY_SUBAGENT_MODEL_CHOICE`, the v2 subagent model pool's reserved `primary` key.
5 changes: 5 additions & 0 deletions .changeset/subagent-model-pool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Add a configurable model pool for spawned subagents behind the `secondary-model` experiment (`KIMI_CODE_EXPERIMENTAL_SECONDARY_MODEL=1`, or the master flag): with the experiment on, the `/secondary-model` command or the `[secondary_model]` section in config.toml sets a default model or a small named pool that the main agent picks from per spawn. A lone legacy `model` key in the same section keeps working as the fallback default.
101 changes: 41 additions & 60 deletions apps/kimi-code/src/tui/commands/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {
effectiveModelAlias,
PRIMARY_SUBAGENT_MODEL_CHOICE,
SECONDARY_DERIVED_MODEL_ALIAS,
type ExperimentalFeatureState,
type KimiConfig,
type ModelAlias,
type PermissionMode,
type Session,
Expand Down Expand Up @@ -269,6 +269,15 @@ export async function handleSecondaryModelCommand(host: SlashCommandHost, args:
const alias = args.trim();
await refreshModelsForPicker(host);
const models = pickerModelsForHost(host);
// The pool reserves `primary` as the symbolic "caller's own model" choice —
// a user alias with that name can never be the subagent default.
delete models[PRIMARY_SUBAGENT_MODEL_CHOICE];
if (alias === PRIMARY_SUBAGENT_MODEL_CHOICE) {
host.showError(
`"${PRIMARY_SUBAGENT_MODEL_CHOICE}" is reserved by the subagent model pool (it always binds the caller's own model) — rename the [models] alias to use it here.`,
);
return;
}
if (Object.keys(models).length === 0) {
host.showNotice(
'No models configured',
Expand All @@ -281,7 +290,10 @@ export async function handleSecondaryModelCommand(host: SlashCommandHost, args:
return;
}
const secondary = (await host.harness.getConfig()).secondaryModel;
showSecondaryModelPicker(host, models, secondary?.model ?? '', secondary?.defaultEffort, alias);
// The v2 engine honors a lone legacy `model` key as the fallback pool
// default — reflect it as the picker's current value.
const current = secondary?.defaultModel ?? secondary?.model ?? '';
showSecondaryModelPicker(host, models, current, alias.length > 0 ? alias : undefined);
}

export async function handleEffortCommand(host: SlashCommandHost, args: string): Promise<void> {
Expand Down Expand Up @@ -427,8 +439,8 @@ async function applyEditorChoice(host: SlashCommandHost, value: string): Promise
/**
* The models a picker may offer: the user's configured aliases with
* host-effective provider resolution applied, minus the synthesized
* `__secondary__` derived entry — a runtime artifact of the `[secondary_model]`
* recipe that must never be selectable as a primary or secondary model.
* `__secondary__` derived entry — a runtime artifact of the v1 engine's
* `[secondary_model]` recipe that must never be selectable as a model.
*/
function pickerModelsForHost(host: SlashCommandHost): Record<string, ModelAlias> {
return Object.fromEntries(
Expand Down Expand Up @@ -604,26 +616,28 @@ async function persistModelSelection(
}

// ---------------------------------------------------------------------------
// Secondary model (`/secondary_model`)
// Secondary model (`/secondary-model`) — persists `[secondary_model] default_model`
// ---------------------------------------------------------------------------

function showSecondaryModelPicker(
host: SlashCommandHost,
models: Record<string, ModelAlias>,
currentValue: string,
currentEffort: string | undefined,
selectedValue?: string,
): void {
host.mountEditorReplacement(
new TabbedModelSelectorComponent({
models,
currentValue,
selectedValue,
currentThinkingEffort: currentEffort ?? 'off',
currentThinkingEffort: 'off',
// Subagent pool bindings carry no explicit thinking level, so the picker
// hides the Thinking footer instead of offering a no-op choice.
thinkingControl: false,
title: ' Select a secondary model (subagents)',
onSelect: ({ alias, thinking }) => {
onSelect: ({ alias }) => {
host.restoreEditor();
void performSecondaryModelSwitch(host, alias, thinking);
void performSecondaryModelSave(host, alias);
},
onCancel: () => {
host.restoreEditor();
Expand All @@ -633,65 +647,32 @@ function showSecondaryModelPicker(
}

/**
* Persist-first, then live-apply: the synthesized derived entry only exists in
* the core config after a reload. No session-only variant — a session-local
* recipe with patch fields would bind a derived alias the core config cannot
* resolve.
* Persists `[secondary_model] default_model`. When a
* `[secondary_model.models]` pool exists and does not list the alias yet, the
* alias is added with an empty description — the engine requires the default
* to be a pool key. Without a pool the default alone forms an implicit
* single-entry pool, so nothing else is written. No live-apply step: the
* engine resolves the pool per spawn, so the next subagent dispatch picks the
* new value up on its own.
*/
async function performSecondaryModelSwitch(
host: SlashCommandHost,
alias: string,
effort: ThinkingEffort,
): Promise<void> {
async function performSecondaryModelSave(host: SlashCommandHost, alias: string): Promise<void> {
const displayName = modelDisplayName(alias, host.state.appState.availableModels[alias]);
let updatedConfig: KimiConfig;
try {
updatedConfig = await host.harness.setConfig({
secondaryModel: { model: alias, defaultEffort: effort },
});
const config = await host.harness.getConfig({ reload: true });
const existing = config.secondaryModel?.models;
const patch: { defaultModel: string; models?: Record<string, string> } = {
defaultModel: alias,
Comment on lines +663 to +664

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep writing the legacy recipe in legacy mode

When the CLI is started with KIMI_CODE_LEGACY_FLAG=1, this command remains available through the legacy engine's secondary-model flag, but it now writes only secondaryModel.defaultModel. The legacy binding path reads secondaryModel.model and snapshots it into live sessions, so the selection is ignored even though the TUI reports success; existing sessions also no longer receive the former live-apply call. Branch on host.engineV2 to retain the model recipe and live application for legacy sessions, or hide the command in legacy mode.

Useful? React with 👍 / 👎.

};
if (existing !== undefined) {
patch.models = { ...existing, [alias]: existing[alias] ?? '' };
}
await host.harness.setConfig({ secondaryModel: patch });
} catch (error) {
host.showError(`Failed to save secondary model: ${formatErrorMessage(error)}`);
return;
}
if (host.session !== undefined) {
try {
await host.session.applyPersistedSecondaryModel();
} catch (error) {
host.showError(
`Saved ${displayName} as the secondary model, but failed to apply it to this session: ${formatErrorMessage(error)}`,
);
return;
}
}
host.setAppState({ availableModels: updatedConfig.models ?? {} });
// Report the effective binding from the reloaded config, not the picked
// value: KIMI_SECONDARY_MODEL / KIMI_SECONDARY_EFFORT override the recipe at
// runtime, and the session binds the overlaid snapshot (mirrors how
// /model displays the effective alias read back from the session).
const effective = updatedConfig.secondaryModel;
const envOverrides: string[] = [];
if (effective?.model !== undefined && effective.model !== alias) {
envOverrides.push(`KIMI_SECONDARY_MODEL=${effective.model}`);
}
if (effective?.defaultEffort !== undefined && effective.defaultEffort !== effort) {
envOverrides.push(`KIMI_SECONDARY_EFFORT=${effective.defaultEffort}`);
}
if (envOverrides.length > 0 && effective?.model !== undefined) {
const effectiveName = modelDisplayName(
effective.model,
updatedConfig.models?.[effective.model],
);
host.showStatus(
`Saved ${displayName} as the secondary model, but ${envOverrides.join(' and ')} ` +
`overrides it at runtime — subagents bind ${effectiveName} until the env var is unset.`,
'warning',
);
return;
}
host.showStatus(
host.session === undefined
? `Secondary model set to ${displayName} with thinking ${effort}; applies to new sessions.`
: `Secondary model set to ${displayName} with thinking ${effort}.`,
`Secondary model set to ${displayName}. Newly spawned subagents will use it by default.`,
'success',
);
}
Expand Down
2 changes: 1 addition & 1 deletion apps/kimi-code/src/tui/commands/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ async function handleBuiltInSlashCommand(
case 'model':
await handleModelCommand(host, args);
return;
case 'secondary_model':
case 'secondary-model':
await handleSecondaryModelCommand(host, args);
return;
case 'effort':
Expand Down
25 changes: 23 additions & 2 deletions apps/kimi-code/src/tui/commands/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
} from '@moonshot-ai/kimi-code-oauth';
import {
applyCatalogProvider,
cascadeSubagentModelPool,
catalogProviderModels,
CatalogFetchError,
DEFAULT_CATALOG_URL,
resolveCatalogImport,
SECONDARY_DERIVED_MODEL_ALIAS,
type Catalog,
type ThinkingEffort,
} from '@moonshot-ai/kimi-code-sdk';
Expand Down Expand Up @@ -231,6 +233,10 @@ async function handleCatalogProviderAdd(host: SlashCommandHost): Promise<void> {
// entered. The model selector that follows is just a convenience to pick the
// default model; ESC leaves the provider in place without a default selection.
const existingConfig = await host.harness.getConfig();
const poolSnapshot =
existingConfig.providers[providerId] !== undefined
? existingConfig.secondaryModel
: undefined;
if (existingConfig.providers[providerId] !== undefined) {
await host.harness.removeProvider(providerId);
}
Expand All @@ -251,6 +257,16 @@ async function handleCatalogProviderAdd(host: SlashCommandHost): Promise<void> {
models: config.models,
});

// removeProvider cascaded the subagent pool against a model table where
// every `${providerId}/...` alias was absent; restore the entries that
// survived the re-add (aliases the catalog genuinely dropped stay dropped).
if (poolSnapshot !== undefined) {
const restored = cascadeSubagentModelPool(poolSnapshot, config.models ?? {});
if (restored !== null) {
await host.harness.setConfig({ secondaryModel: restored ?? poolSnapshot });
}
}

await host.authFlow.refreshConfigAfterLogin();
host.track('connect', { provider: providerId, method: 'catalog' });
host.showStatus(`Provider added: ${entry.name ?? providerId}`);
Expand All @@ -263,8 +279,11 @@ async function handleCatalogProviderAdd(host: SlashCommandHost): Promise<void> {
// Build a merged model dictionary that includes existing models plus the
// newly-persisted provider's models, so the tabbed selector shows every
// provider's tab (the new provider's tab starts active via initialTabId).
// The v1 runtime may carry the synthesized `__secondary__` derived entry —
// never selectable in a picker.
const stateModels = await host.harness.getConfig().then((c) => c.models ?? {});
const mergedModels = { ...stateModels };
delete mergedModels[SECONDARY_DERIVED_MODEL_ALIAS];

const selector = new TabbedModelSelectorComponent({
models: mergedModels,
Expand Down Expand Up @@ -356,8 +375,10 @@ async function handleCustomRegistryAddViaDialog(host: SlashCommandHost): Promise
);

// Offer the model selector so the user can pick a default, just like the
// catalog (known-provider) flow.
const stateModels = await host.harness.getConfig().then((c) => c.models ?? {});
// catalog (known-provider) flow. Copy without the v1-synthesized
// `__secondary__` derived entry — never selectable in a picker.
const stateModels = { ...(await host.harness.getConfig().then((c) => c.models ?? {})) };
delete stateModels[SECONDARY_DERIVED_MODEL_ALIAS];
const firstNewAlias = Object.keys(stateModels).find((a) =>
addedProviderIds.some((pid) => a.startsWith(`${pid}/`)),
);
Expand Down
4 changes: 2 additions & 2 deletions apps/kimi-code/src/tui/commands/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,8 @@ export const BUILTIN_SLASH_COMMANDS = [
availability: 'always',
},
{
name: 'secondary_model',
aliases: [],
name: 'secondary-model',
aliases: ['subagent-model'],
Comment on lines +188 to +189

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain the old slash-command name as an alias

With the secondary-model experiment enabled, existing users or automation invoking /secondary_model now get an unknown-command error because the renamed entry retains only subagent-model as an alias. Since the CLI changeset is minor, keep secondary_model as a compatibility alias while making secondary-model canonical, or treat the rename as a major change.

AGENTS.md reference: AGENTS.md:L59-L61

Useful? React with 👍 / 👎.

description: 'Configure the secondary model for subagents',
priority: 90,
availability: 'always',
Expand Down
12 changes: 9 additions & 3 deletions apps/kimi-code/src/tui/components/dialogs/model-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ export interface ModelSelectorOptions {
* line; wraps instead of truncating when it exceeds the width (e.g. the
* mid-conversation switch cost notice). */
readonly warning?: string;
/** Set to false to hide the Thinking footer and disable ←/→ effort
* switching — for pickers whose selection carries no thinking level. */
readonly thinkingControl?: boolean;
readonly onSelect: (selection: ModelSelection) => void;
/** When provided, Alt+S invokes this instead of onSelect — used to apply the
* choice to the current session only, without persisting it as the default. */
Expand Down Expand Up @@ -225,7 +228,10 @@ export class ModelSelectorComponent extends Container implements Focusable {
}

// Left/Right move the active thinking effort within the model's segments.
if (matchesKey(data, Key.left) || matchesKey(data, Key.right)) {
if (
this.opts.thinkingControl !== false &&
(matchesKey(data, Key.left) || matchesKey(data, Key.right))
) {
const selected = this.selectedChoice();
if (selected !== undefined) {
const segments = segmentsFor(selected.model);
Expand Down Expand Up @@ -352,13 +358,13 @@ export class ModelSelectorComponent extends Container implements Focusable {

lines.push('');
const selected = this.selectedChoice();
if (selected !== undefined) {
if (selected !== undefined && this.opts.thinkingControl !== false) {
const canSwitch = segmentsFor(selected.model).length > 1;
const thinkingHeader = canSwitch ? ' Thinking (←→ to switch)' : ' Thinking';
lines.push(currentTheme.fg('textMuted', thinkingHeader));
lines.push(this.renderThinkingControl(selected));
lines.push('');
}
lines.push('');
lines.push(currentTheme.fg('primary', '─'.repeat(width)));
return lines.map((line) => truncateToWidth(line, width));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,17 @@ export interface TabbedModelSelectorOptions {
readonly selectedValue?: string;
readonly currentThinkingEffort: string;
/** Forwarded to each inner selector; overrides the default ' Select a model'
* title line (e.g. the secondary-model picker). */
* title line. */
readonly title?: string;
/** When set, the tab for this provider id is initially active instead of the
* tab derived from `currentValue`. */
readonly initialTabId?: string;
/** Forwarded to each inner selector; when set, warning-colored lines are
* rendered directly below the key-hint line, wrapping as needed (e.g. the
* mid-conversation switch cost notice). */
/** When set, warning-colored lines are rendered directly below the key-hint
* line, wrapping as needed (e.g. the mid-conversation switch cost notice). */
readonly warning?: string;
/** Forwarded to each inner selector; set to false to hide the Thinking
* footer and disable ←/→ effort switching. */
readonly thinkingControl?: boolean;
readonly onSelect: (selection: ModelSelection) => void;
/** Forwarded to each inner selector; when set, Alt+S applies the choice to
* the current session only without persisting it as the default. */
Expand Down Expand Up @@ -187,6 +189,7 @@ function makeSelector(
searchable: true,
providerSwitchHint: true,
warning: opts.warning,
thinkingControl: opts.thinkingControl,
onSelect: opts.onSelect,
onSessionOnlySelect: opts.onSessionOnlySelect,
onCancel: opts.onCancel,
Expand Down
6 changes: 2 additions & 4 deletions apps/kimi-code/src/tui/controllers/subagent-event-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,7 @@ export class SubAgentEventHandler {
usage: totalUsage,
// The bound model alias rides every child status update (emitted right
// after spawn); surface it on the subagent card. `modelDisplayName`
// falls back to the alias itself when the entry is unknown (e.g. the
// synthesized `__secondary__` derived entry is missing).
// falls back to the alias itself when the entry is unknown.
modelDisplay:
event.model === undefined
? undefined
Expand Down Expand Up @@ -589,8 +588,7 @@ export class SubAgentEventHandler {
// The bound model alias rides every child status update (emitted right
// after spawn). Swarm members share one binding, so the panel shows it
// once in the header instead of per cell. `modelDisplayName` falls back
// to the alias itself when the entry is unknown (e.g. the synthesized
// `__secondary__` derived entry is missing).
// to the alias itself when the entry is unknown.
progress.setModelDisplay(
modelDisplayName(event.model, this.host.state.appState.availableModels[event.model]),
);
Expand Down
Loading
Loading