From 9822985ea649b93f0deaa8a8a34928159795c55e Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Wed, 4 Feb 2026 09:21:41 -0600 Subject: [PATCH] fix(web ui): agent model selection --- CHANGELOG.md | 1 + ui/src/styles/components.css | 7 ++ ui/src/ui/app-render.ts | 135 +++++++++++++++++++++++++++-------- ui/src/ui/views/agents.ts | 16 +++-- 4 files changed, 126 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a862fd82..ea93b9e38 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo. +- Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman. - Web UI: resolve header logo path when `gateway.controlUi.basePath` is set. (#7178) Thanks @Yeom-JinHo. - Web UI: apply button styling to the new-messages indicator. - Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin. diff --git a/ui/src/styles/components.css b/ui/src/styles/components.css index 2ef6185c7..5ab1858b8 100644 --- a/ui/src/styles/components.css +++ b/ui/src/styles/components.css @@ -1659,6 +1659,13 @@ .agent-kv { display: grid; gap: 6px; + min-width: 0; +} + +.agent-kv > div { + min-width: 0; + overflow-wrap: anywhere; + word-break: break-word; } .agent-kv-sub { diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 3b5e69547..44d83c775 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -107,6 +107,27 @@ export function renderApp(state: AppViewState) { state.agentsList?.defaultId ?? state.agentsList?.agents?.[0]?.id ?? null; + const ensureAgentListEntry = (agentId: string) => { + const snapshot = (state.configForm ?? + (state.configSnapshot?.config as Record | null)) as { + agents?: { list?: unknown[] }; + } | null; + const listRaw = snapshot?.agents?.list; + const list = Array.isArray(listRaw) ? listRaw : []; + let index = list.findIndex( + (entry) => + entry && + typeof entry === "object" && + "id" in entry && + (entry as { id?: string }).id === agentId, + ); + if (index < 0) { + const nextList = [...list, { id: agentId }]; + updateConfigFormValue(state as unknown as ConfigState, ["agents", "list"], nextList); + index = nextList.length - 1; + } + return index; + }; return html`
@@ -637,26 +658,48 @@ export function renderApp(state: AppViewState) { if (!configValue) { return; } - const list = (configValue as { agents?: { list?: unknown[] } }).agents?.list; - if (!Array.isArray(list)) { - return; - } - const index = list.findIndex( - (entry) => - entry && - typeof entry === "object" && - "id" in entry && - (entry as { id?: string }).id === agentId, - ); - if (index < 0) { + const defaultId = state.agentsList?.defaultId ?? null; + if (defaultId && agentId === defaultId) { + const basePath = ["agents", "defaults", "model"]; + const defaults = + (configValue as { agents?: { defaults?: { model?: unknown } } }).agents + ?.defaults ?? {}; + const existing = defaults.model; + if (!modelId) { + removeConfigFormValue(state as unknown as ConfigState, basePath); + return; + } + if (existing && typeof existing === "object" && !Array.isArray(existing)) { + const fallbacks = (existing as { fallbacks?: unknown }).fallbacks; + const next = { + primary: modelId, + ...(Array.isArray(fallbacks) ? { fallbacks } : {}), + }; + updateConfigFormValue(state as unknown as ConfigState, basePath, next); + } else { + updateConfigFormValue(state as unknown as ConfigState, basePath, { + primary: modelId, + }); + } return; } + + const index = ensureAgentListEntry(agentId); const basePath = ["agents", "list", index, "model"]; if (!modelId) { removeConfigFormValue(state as unknown as ConfigState, basePath); return; } - const entry = list[index] as { model?: unknown }; + const list = ( + (state.configForm ?? + (state.configSnapshot?.config as Record | null)) as { + agents?: { list?: unknown[] }; + } + )?.agents?.list; + const entry = + Array.isArray(list) && list[index] + ? (list[index] as { model?: unknown }) + : null; const existing = entry?.model; if (existing && typeof existing === "object" && !Array.isArray(existing)) { const fallbacks = (existing as { fallbacks?: unknown }).fallbacks; @@ -673,23 +716,57 @@ export function renderApp(state: AppViewState) { if (!configValue) { return; } - const list = (configValue as { agents?: { list?: unknown[] } }).agents?.list; - if (!Array.isArray(list)) { - return; - } - const index = list.findIndex( - (entry) => - entry && - typeof entry === "object" && - "id" in entry && - (entry as { id?: string }).id === agentId, - ); - if (index < 0) { - return; - } - const basePath = ["agents", "list", index, "model"]; - const entry = list[index] as { model?: unknown }; const normalized = fallbacks.map((name) => name.trim()).filter(Boolean); + const defaultId = state.agentsList?.defaultId ?? null; + if (defaultId && agentId === defaultId) { + const basePath = ["agents", "defaults", "model"]; + const defaults = + (configValue as { agents?: { defaults?: { model?: unknown } } }).agents + ?.defaults ?? {}; + const existing = defaults.model; + const resolvePrimary = () => { + if (typeof existing === "string") { + return existing.trim() || null; + } + if (existing && typeof existing === "object" && !Array.isArray(existing)) { + const primary = (existing as { primary?: unknown }).primary; + if (typeof primary === "string") { + const trimmed = primary.trim(); + return trimmed || null; + } + } + return null; + }; + const primary = resolvePrimary(); + if (normalized.length === 0) { + if (primary) { + updateConfigFormValue(state as unknown as ConfigState, basePath, { + primary, + }); + } else { + removeConfigFormValue(state as unknown as ConfigState, basePath); + } + return; + } + const next = primary + ? { primary, fallbacks: normalized } + : { fallbacks: normalized }; + updateConfigFormValue(state as unknown as ConfigState, basePath, next); + return; + } + + const index = ensureAgentListEntry(agentId); + const basePath = ["agents", "list", index, "model"]; + const list = ( + (state.configForm ?? + (state.configSnapshot?.config as Record | null)) as { + agents?: { list?: unknown[] }; + } + )?.agents?.list; + const entry = + Array.isArray(list) && list[index] + ? (list[index] as { model?: unknown }) + : null; const existing = entry.model; const resolvePrimary = () => { if (typeof existing === "string") { diff --git a/ui/src/ui/views/agents.ts b/ui/src/ui/views/agents.ts index 318e59368..1cc352d35 100644 --- a/ui/src/ui/views/agents.ts +++ b/ui/src/ui/views/agents.ts @@ -875,16 +875,24 @@ function renderAgentOverview(params: {
Model Selection