From 27677dd8bd37c31cbf04e0ce9802b050ed59a2de Mon Sep 17 00:00:00 2001 From: cpojer Date: Tue, 3 Feb 2026 22:35:55 +0900 Subject: [PATCH] chore: Fix all TypeScript errors in `ui`. --- ui/src/ui/app-chat.ts | 2 +- ui/src/ui/app-render.helpers.ts | 17 ++- ui/src/ui/app-render.ts | 185 +++++++++++++++--------- ui/src/ui/app-settings.ts | 27 ++-- ui/src/ui/app-tool-stream.ts | 4 +- ui/src/ui/config-form.browser.test.ts | 4 +- ui/src/ui/controllers/agents.ts | 2 +- ui/src/ui/controllers/channels.ts | 23 ++- ui/src/ui/controllers/chat.test.ts | 13 +- ui/src/ui/controllers/chat.ts | 11 +- ui/src/ui/controllers/config.test.ts | 27 ++-- ui/src/ui/controllers/config.ts | 4 +- ui/src/ui/controllers/cron.ts | 6 +- ui/src/ui/controllers/devices.ts | 12 +- ui/src/ui/controllers/exec-approvals.ts | 2 +- ui/src/ui/controllers/nodes.ts | 2 +- ui/src/ui/controllers/sessions.ts | 2 +- ui/src/ui/controllers/skills.ts | 4 +- ui/src/ui/device-identity.ts | 2 +- ui/src/ui/markdown.ts | 1 - ui/src/ui/navigation.browser.test.ts | 2 +- ui/src/ui/types.ts | 16 +- ui/src/ui/views/channels.ts | 2 +- ui/src/ui/views/config.browser.test.ts | 2 +- ui/tsconfig.json | 3 +- 25 files changed, 226 insertions(+), 149 deletions(-) diff --git a/ui/src/ui/app-chat.ts b/ui/src/ui/app-chat.ts index f1176d8b1d..7c20820c22 100644 --- a/ui/src/ui/app-chat.ts +++ b/ui/src/ui/app-chat.ts @@ -10,7 +10,7 @@ import { loadSessions } from "./controllers/sessions"; import { normalizeBasePath } from "./navigation"; import { generateUUID } from "./uuid"; -type ChatHost = { +export type ChatHost = { connected: boolean; chatMessage: string; chatAttachments: ChatAttachment[]; diff --git a/ui/src/ui/app-render.helpers.ts b/ui/src/ui/app-render.helpers.ts index ac6b3d8ef0..1993a1b55f 100644 --- a/ui/src/ui/app-render.helpers.ts +++ b/ui/src/ui/app-render.helpers.ts @@ -4,9 +4,10 @@ import type { AppViewState } from "./app-view-state"; import type { ThemeMode } from "./theme"; import type { ThemeTransitionContext } from "./theme-transition"; import type { SessionsListResult } from "./types"; +import { OpenClawApp } from "./app"; import { refreshChat } from "./app-chat"; import { syncUrlWithSessionKey } from "./app-settings"; -import { loadChatHistory } from "./controllers/chat"; +import { ChatState, loadChatHistory } from "./controllers/chat"; import { icons } from "./icons"; import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation"; @@ -94,18 +95,18 @@ export function renderChatControls(state: AppViewState) { state.sessionKey = next; state.chatMessage = ""; state.chatStream = null; - state.chatStreamStartedAt = null; + (state as unknown as OpenClawApp).chatStreamStartedAt = null; state.chatRunId = null; - state.resetToolStream(); - state.resetChatScroll(); + (state as unknown as OpenClawApp).resetToolStream(); + (state as unknown as OpenClawApp).resetChatScroll(); state.applySettings({ ...state.settings, sessionKey: next, lastActiveSessionKey: next, }); void state.loadAssistantIdentity(); - syncUrlWithSessionKey(state, next, true); - void loadChatHistory(state); + syncUrlWithSessionKey(next, true); + void loadChatHistory(state as unknown as ChatState); }} > ${repeat( @@ -122,7 +123,7 @@ export function renderChatControls(state: AppViewState) { class="btn btn--sm btn--icon" ?disabled=${state.chatLoading || !state.connected} @click=${() => { - state.resetToolStream(); + (state as unknown as OpenClawApp).resetToolStream(); void refreshChat(state as unknown as Parameters[0]); }} title="Refresh chat data" @@ -228,7 +229,7 @@ function resolveSessionOptions( seen.add(mainSessionKey); options.push({ key: mainSessionKey, - displayName: resolveSessionDisplayName(mainSessionKey, resolvedMain), + displayName: resolveSessionDisplayName(mainSessionKey, resolvedMain || undefined), }); } diff --git a/ui/src/ui/app-render.ts b/ui/src/ui/app-render.ts index 92db25f9f7..b34c67c7ea 100644 --- a/ui/src/ui/app-render.ts +++ b/ui/src/ui/app-render.ts @@ -1,16 +1,18 @@ import { html, nothing } from "lit"; import type { AppViewState } from "./app-view-state"; import { parseAgentSessionKey } from "../../../src/routing/session-key.js"; -import { refreshChatAvatar } from "./app-chat"; +import { OpenClawApp } from "./app"; +import { ChatHost, refreshChatAvatar } from "./app-chat"; import { renderChatControls, renderTab, renderThemeToggle } from "./app-render.helpers"; import { loadAgentFileContent, loadAgentFiles, saveAgentFile } from "./controllers/agent-files"; import { loadAgentIdentities, loadAgentIdentity } from "./controllers/agent-identity"; import { loadAgentSkills } from "./controllers/agent-skills"; import { loadAgents } from "./controllers/agents"; import { loadChannels } from "./controllers/channels"; -import { loadChatHistory } from "./controllers/chat"; +import { ChatState, loadChatHistory } from "./controllers/chat"; import { applyConfig, + ConfigState, loadConfig, runUpdate, saveConfig, @@ -38,7 +40,7 @@ import { saveExecApprovals, updateExecApprovalsFormValue, } from "./controllers/exec-approvals"; -import { loadLogs } from "./controllers/logs"; +import { loadLogs, LogsState } from "./controllers/logs"; import { loadNodes } from "./controllers/nodes"; import { loadPresence } from "./controllers/presence"; import { deleteSession, loadSessions, patchSession } from "./controllers/sessions"; @@ -51,6 +53,7 @@ import { } from "./controllers/skills"; import { icons } from "./icons"; import { TAB_GROUPS, subtitleForTab, titleForTab } from "./navigation"; +import { ConfigUiHints } from "./types"; import { renderAgents } from "./views/agents"; import { renderChannels } from "./views/channels"; import { renderChat } from "./views/chat"; @@ -213,7 +216,7 @@ export function renderApp(state: AppViewState) { onSessionKeyChange: (next) => { state.sessionKey = next; state.chatMessage = ""; - state.resetToolStream(); + (state as unknown as OpenClawApp).resetToolStream(); state.applySettings({ ...state.settings, sessionKey: next, @@ -242,7 +245,7 @@ export function renderApp(state: AppViewState) { configSchema: state.configSchema, configSchemaLoading: state.configSchemaLoading, configForm: state.configForm, - configUiHints: state.configUiHints, + configUiHints: state.configUiHints as ConfigUiHints, configSaving: state.configSaving, configFormDirty: state.configFormDirty, nostrProfileFormState: state.nostrProfileFormState, @@ -251,7 +254,8 @@ export function renderApp(state: AppViewState) { onWhatsAppStart: (force) => state.handleWhatsAppStart(force), onWhatsAppWait: () => state.handleWhatsAppWait(), onWhatsAppLogout: () => state.handleWhatsAppLogout(), - onConfigPatch: (path, value) => updateConfigFormValue(state, path, value), + onConfigPatch: (path, value) => + updateConfigFormValue(state as unknown as ConfigState, path, value), onConfigSave: () => state.handleChannelConfigSave(), onConfigReload: () => state.handleChannelConfigReload(), onNostrProfileEdit: (accountId, profile) => @@ -460,12 +464,19 @@ export function renderApp(state: AppViewState) { } const basePath = ["agents", "list", index, "tools"]; if (profile) { - updateConfigFormValue(state, [...basePath, "profile"], profile); + updateConfigFormValue( + state as unknown as ConfigState, + [...basePath, "profile"], + profile, + ); } else { - removeConfigFormValue(state, [...basePath, "profile"]); + removeConfigFormValue(state as unknown as ConfigState, [ + ...basePath, + "profile", + ]); } if (clearAllow) { - removeConfigFormValue(state, [...basePath, "allow"]); + removeConfigFormValue(state as unknown as ConfigState, [...basePath, "allow"]); } }, onToolsOverridesChange: (agentId, alsoAllow, deny) => { @@ -488,18 +499,29 @@ export function renderApp(state: AppViewState) { } const basePath = ["agents", "list", index, "tools"]; if (alsoAllow.length > 0) { - updateConfigFormValue(state, [...basePath, "alsoAllow"], alsoAllow); + updateConfigFormValue( + state as unknown as ConfigState, + [...basePath, "alsoAllow"], + alsoAllow, + ); } else { - removeConfigFormValue(state, [...basePath, "alsoAllow"]); + removeConfigFormValue(state as unknown as ConfigState, [ + ...basePath, + "alsoAllow", + ]); } if (deny.length > 0) { - updateConfigFormValue(state, [...basePath, "deny"], deny); + updateConfigFormValue( + state as unknown as ConfigState, + [...basePath, "deny"], + deny, + ); } else { - removeConfigFormValue(state, [...basePath, "deny"]); + removeConfigFormValue(state as unknown as ConfigState, [...basePath, "deny"]); } }, - onConfigReload: () => loadConfig(state), - onConfigSave: () => saveConfig(state), + onConfigReload: () => loadConfig(state as unknown as ConfigState), + onConfigSave: () => saveConfig(state as unknown as ConfigState), onChannelsRefresh: () => loadChannels(state, false), onCronRefresh: () => state.loadCron(), onSkillsFilterChange: (next) => (state.skillsFilter = next), @@ -544,7 +566,11 @@ export function renderApp(state: AppViewState) { } else { next.delete(normalizedSkill); } - updateConfigFormValue(state, ["agents", "list", index, "skills"], [...next]); + updateConfigFormValue( + state as unknown as ConfigState, + ["agents", "list", index, "skills"], + [...next], + ); }, onAgentSkillsClear: (agentId) => { if (!configValue) { @@ -564,7 +590,12 @@ export function renderApp(state: AppViewState) { if (index < 0) { return; } - removeConfigFormValue(state, ["agents", "list", index, "skills"]); + removeConfigFormValue(state as unknown as ConfigState, [ + "agents", + "list", + index, + "skills", + ]); }, onAgentSkillsDisableAll: (agentId) => { if (!configValue) { @@ -584,7 +615,11 @@ export function renderApp(state: AppViewState) { if (index < 0) { return; } - updateConfigFormValue(state, ["agents", "list", index, "skills"], []); + updateConfigFormValue( + state as unknown as ConfigState, + ["agents", "list", index, "skills"], + [], + ); }, onModelChange: (agentId, modelId) => { if (!configValue) { @@ -606,7 +641,7 @@ export function renderApp(state: AppViewState) { } const basePath = ["agents", "list", index, "model"]; if (!modelId) { - removeConfigFormValue(state, basePath); + removeConfigFormValue(state as unknown as ConfigState, basePath); return; } const entry = list[index] as { model?: unknown }; @@ -617,9 +652,9 @@ export function renderApp(state: AppViewState) { primary: modelId, ...(Array.isArray(fallbacks) ? { fallbacks } : {}), }; - updateConfigFormValue(state, basePath, next); + updateConfigFormValue(state as unknown as ConfigState, basePath, next); } else { - updateConfigFormValue(state, basePath, modelId); + updateConfigFormValue(state as unknown as ConfigState, basePath, modelId); } }, onModelFallbacksChange: (agentId, fallbacks) => { @@ -660,16 +695,16 @@ export function renderApp(state: AppViewState) { const primary = resolvePrimary(); if (normalized.length === 0) { if (primary) { - updateConfigFormValue(state, basePath, primary); + updateConfigFormValue(state as unknown as ConfigState, basePath, primary); } else { - removeConfigFormValue(state, basePath); + removeConfigFormValue(state as unknown as ConfigState, basePath); } return; } const next = primary ? { primary, fallbacks: normalized } : { fallbacks: normalized }; - updateConfigFormValue(state, basePath, next); + updateConfigFormValue(state as unknown as ConfigState, basePath, next); }, }) : nothing @@ -726,7 +761,7 @@ export function renderApp(state: AppViewState) { onDeviceRotate: (deviceId, role, scopes) => rotateDeviceToken(state, { deviceId, role, scopes }), onDeviceRevoke: (deviceId, role) => revokeDeviceToken(state, { deviceId, role }), - onLoadConfig: () => loadConfig(state), + onLoadConfig: () => loadConfig(state as unknown as ConfigState), onLoadExecApprovals: () => { const target = state.execApprovalsTarget === "node" && state.execApprovalsTargetNodeId @@ -736,20 +771,28 @@ export function renderApp(state: AppViewState) { }, onBindDefault: (nodeId) => { if (nodeId) { - updateConfigFormValue(state, ["tools", "exec", "node"], nodeId); + updateConfigFormValue( + state as unknown as ConfigState, + ["tools", "exec", "node"], + nodeId, + ); } else { - removeConfigFormValue(state, ["tools", "exec", "node"]); + removeConfigFormValue(state as unknown as ConfigState, [ + "tools", + "exec", + "node", + ]); } }, onBindAgent: (agentIndex, nodeId) => { const basePath = ["agents", "list", agentIndex, "tools", "exec", "node"]; if (nodeId) { - updateConfigFormValue(state, basePath, nodeId); + updateConfigFormValue(state as unknown as ConfigState, basePath, nodeId); } else { - removeConfigFormValue(state, basePath); + removeConfigFormValue(state as unknown as ConfigState, basePath); } }, - onSaveBindings: () => saveConfig(state), + onSaveBindings: () => saveConfig(state as unknown as ConfigState), onExecApprovalsTargetChange: (kind, nodeId) => { state.execApprovalsTarget = kind; state.execApprovalsTargetNodeId = nodeId; @@ -784,30 +827,29 @@ export function renderApp(state: AppViewState) { state.chatMessage = ""; state.chatAttachments = []; state.chatStream = null; - state.chatStreamStartedAt = null; state.chatRunId = null; + (state as unknown as OpenClawApp).chatStreamStartedAt = null; state.chatQueue = []; - state.resetToolStream(); - state.resetChatScroll(); + (state as unknown as OpenClawApp).resetToolStream(); + (state as unknown as OpenClawApp).resetChatScroll(); state.applySettings({ ...state.settings, sessionKey: next, lastActiveSessionKey: next, }); void state.loadAssistantIdentity(); - void loadChatHistory(state); - void refreshChatAvatar(state); + void loadChatHistory(state as unknown as ChatState); + void refreshChatAvatar(state as unknown as ChatHost); }, thinkingLevel: state.chatThinkingLevel, showThinking, loading: state.chatLoading, sending: state.chatSending, - compactionStatus: state.compactionStatus, assistantAvatarUrl: chatAvatarUrl, messages: state.chatMessages, toolMessages: state.chatToolMessages, stream: state.chatStream, - streamStartedAt: state.chatStreamStartedAt, + streamStartedAt: null, draft: state.chatMessage, queue: state.chatQueue, connected: state.connected, @@ -817,8 +859,10 @@ export function renderApp(state: AppViewState) { sessions: state.sessionsResult, focusMode: chatFocus, onRefresh: () => { - state.resetToolStream(); - return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]); + return Promise.all([ + loadChatHistory(state as unknown as ChatState), + refreshChatAvatar(state as unknown as ChatHost), + ]); }, onToggleFocusMode: () => { if (state.onboarding) { @@ -829,25 +873,28 @@ export function renderApp(state: AppViewState) { chatFocusMode: !state.settings.chatFocusMode, }); }, - onChatScroll: (event) => state.handleChatScroll(event), + onChatScroll: (event) => (state as unknown as OpenClawApp).handleChatScroll(event), onDraftChange: (next) => (state.chatMessage = next), attachments: state.chatAttachments, onAttachmentsChange: (next) => (state.chatAttachments = next), - onSend: () => state.handleSendChat(), + onSend: () => (state as unknown as OpenClawApp).handleSendChat(), canAbort: Boolean(state.chatRunId), - onAbort: () => void state.handleAbortChat(), - onQueueRemove: (id) => state.removeQueuedMessage(id), - onNewSession: () => state.handleSendChat("/new", { restoreDraft: true }), + onAbort: () => void (state as unknown as OpenClawApp).handleAbortChat(), + onQueueRemove: (id) => (state as unknown as OpenClawApp).removeQueuedMessage(id), + onNewSession: () => + (state as unknown as OpenClawApp).handleSendChat("/new", { restoreDraft: true }), showNewMessages: state.chatNewMessagesBelow, onScrollToBottom: () => state.scrollToBottom(), // Sidebar props for tool output viewing - sidebarOpen: state.sidebarOpen, - sidebarContent: state.sidebarContent, - sidebarError: state.sidebarError, - splitRatio: state.splitRatio, - onOpenSidebar: (content: string) => state.handleOpenSidebar(content), - onCloseSidebar: () => state.handleCloseSidebar(), - onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio), + sidebarOpen: (state as unknown as OpenClawApp).sidebarOpen, + sidebarContent: (state as unknown as OpenClawApp).sidebarContent, + sidebarError: (state as unknown as OpenClawApp).sidebarError, + splitRatio: (state as unknown as OpenClawApp).splitRatio, + onOpenSidebar: (content: string) => + (state as unknown as OpenClawApp).handleOpenSidebar(content), + onCloseSidebar: () => (state as unknown as OpenClawApp).handleCloseSidebar(), + onSplitRatioChange: (ratio: number) => + (state as unknown as OpenClawApp).handleSplitRatioChange(ratio), assistantName: state.assistantName, assistantAvatar: state.assistantAvatar, }) @@ -868,28 +915,31 @@ export function renderApp(state: AppViewState) { connected: state.connected, schema: state.configSchema, schemaLoading: state.configSchemaLoading, - uiHints: state.configUiHints, + uiHints: state.configUiHints as ConfigUiHints, formMode: state.configFormMode, formValue: state.configForm, originalValue: state.configFormOriginal, - searchQuery: state.configSearchQuery, - activeSection: state.configActiveSection, - activeSubsection: state.configActiveSubsection, + searchQuery: (state as unknown as OpenClawApp).configSearchQuery, + activeSection: (state as unknown as OpenClawApp).configActiveSection, + activeSubsection: (state as unknown as OpenClawApp).configActiveSubsection, onRawChange: (next) => { state.configRaw = next; }, onFormModeChange: (mode) => (state.configFormMode = mode), - onFormPatch: (path, value) => updateConfigFormValue(state, path, value), - onSearchChange: (query) => (state.configSearchQuery = query), + onFormPatch: (path, value) => + updateConfigFormValue(state as unknown as OpenClawApp, path, value), + onSearchChange: (query) => + ((state as unknown as OpenClawApp).configSearchQuery = query), onSectionChange: (section) => { - state.configActiveSection = section; - state.configActiveSubsection = null; + (state as unknown as OpenClawApp).configActiveSection = section; + (state as unknown as OpenClawApp).configActiveSubsection = null; }, - onSubsectionChange: (section) => (state.configActiveSubsection = section), - onReload: () => loadConfig(state), - onSave: () => saveConfig(state), - onApply: () => applyConfig(state), - onUpdate: () => runUpdate(state), + onSubsectionChange: (section) => + ((state as unknown as OpenClawApp).configActiveSubsection = section), + onReload: () => loadConfig(state as unknown as OpenClawApp), + onSave: () => saveConfig(state as unknown as OpenClawApp), + onApply: () => applyConfig(state as unknown as OpenClawApp), + onUpdate: () => runUpdate(state as unknown as OpenClawApp), }) : nothing } @@ -931,9 +981,10 @@ export function renderApp(state: AppViewState) { state.logsLevelFilters = { ...state.logsLevelFilters, [level]: enabled }; }, onToggleAutoFollow: (next) => (state.logsAutoFollow = next), - onRefresh: () => loadLogs(state, { reset: true }), - onExport: (lines, label) => state.exportLogs(lines, label), - onScroll: (event) => state.handleLogsScroll(event), + onRefresh: () => loadLogs(state as unknown as LogsState, { reset: true }), + onExport: (lines, label) => + (state as unknown as OpenClawApp).exportLogs(lines, label), + onScroll: (event) => (state as unknown as OpenClawApp).handleLogsScroll(event), }) : nothing } diff --git a/ui/src/ui/app-settings.ts b/ui/src/ui/app-settings.ts index 4b88b04849..843c7c4c98 100644 --- a/ui/src/ui/app-settings.ts +++ b/ui/src/ui/app-settings.ts @@ -99,7 +99,7 @@ export function applySettingsFromUrl(host: SettingsHost) { if (passwordRaw != null) { const password = passwordRaw.trim(); if (password) { - (host as { password: string }).password = password; + (host as unknown as { password: string }).password = password; } params.delete("password"); shouldCleanUrl = true; @@ -189,23 +189,24 @@ export async function refreshActiveTab(host: SettingsHost) { await loadSkills(host as unknown as OpenClawApp); } if (host.tab === "agents") { - await loadAgents(host as unknown as OpenClawApp); - await loadConfig(host as unknown as OpenClawApp); - const agentIds = host.agentsList?.agents?.map((entry) => entry.id) ?? []; + const app = host as unknown as OpenClawApp; + await loadAgents(app); + await loadConfig(app); + const agentIds = app.agentsList?.agents?.map((entry) => entry.id) ?? []; if (agentIds.length > 0) { - void loadAgentIdentities(host as unknown as OpenClawApp, agentIds); + void loadAgentIdentities(app, agentIds); } const agentId = - host.agentsSelectedId ?? host.agentsList?.defaultId ?? host.agentsList?.agents?.[0]?.id; + app.agentsSelectedId ?? app.agentsList?.defaultId ?? app.agentsList?.agents?.[0]?.id; if (agentId) { - void loadAgentIdentity(host as unknown as OpenClawApp, agentId); - if (host.agentsPanel === "skills") { - void loadAgentSkills(host as unknown as OpenClawApp, agentId); + void loadAgentIdentity(app, agentId); + if (app.agentsPanel === "skills") { + void loadAgentSkills(app, agentId); } - if (host.agentsPanel === "channels") { - void loadChannels(host as unknown as OpenClawApp, false); + if (app.agentsPanel === "channels") { + void loadChannels(app, false); } - if (host.agentsPanel === "cron") { + if (app.agentsPanel === "cron") { void loadCron(host); } } @@ -380,7 +381,7 @@ export function syncUrlWithTab(host: SettingsHost, tab: Tab, replace: boolean) { } } -export function syncUrlWithSessionKey(host: SettingsHost, sessionKey: string, replace: boolean) { +export function syncUrlWithSessionKey(sessionKey: string, replace: boolean) { if (typeof window === "undefined") { return; } diff --git a/ui/src/ui/app-tool-stream.ts b/ui/src/ui/app-tool-stream.ts index 3439b90ea0..9701c836de 100644 --- a/ui/src/ui/app-tool-stream.ts +++ b/ui/src/ui/app-tool-stream.ts @@ -257,7 +257,7 @@ export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPaylo sessionKey, name, args, - output, + output: output || undefined, startedAt: typeof payload.ts === "number" ? payload.ts : now, updatedAt: now, message: {}, @@ -270,7 +270,7 @@ export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPaylo entry.args = args; } if (output !== undefined) { - entry.output = output; + entry.output = output || undefined; } entry.updatedAt = now; } diff --git a/ui/src/ui/config-form.browser.test.ts b/ui/src/ui/config-form.browser.test.ts index 37c98258d7..745ff07d5c 100644 --- a/ui/src/ui/config-form.browser.test.ts +++ b/ui/src/ui/config-form.browser.test.ts @@ -51,7 +51,7 @@ describe("config form renderer", () => { container, ); - const tokenInput = container.querySelector("input[type='password']"); + const tokenInput: HTMLInputElement | null = container.querySelector("input[type='password']"); expect(tokenInput).not.toBeNull(); if (!tokenInput) { return; @@ -67,7 +67,7 @@ describe("config form renderer", () => { tokenButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); expect(onPatch).toHaveBeenCalledWith(["mode"], "token"); - const checkbox = container.querySelector("input[type='checkbox']"); + const checkbox: HTMLInputElement | null = container.querySelector("input[type='checkbox']"); expect(checkbox).not.toBeNull(); if (!checkbox) { return; diff --git a/ui/src/ui/controllers/agents.ts b/ui/src/ui/controllers/agents.ts index bbaf584ff2..b809f9b321 100644 --- a/ui/src/ui/controllers/agents.ts +++ b/ui/src/ui/controllers/agents.ts @@ -20,7 +20,7 @@ export async function loadAgents(state: AgentsState) { state.agentsLoading = true; state.agentsError = null; try { - const res = await state.client.request("agents.list", {}); + const res = await state.client.request("agents.list", {}); if (res) { state.agentsList = res; const selected = state.agentsSelectedId; diff --git a/ui/src/ui/controllers/channels.ts b/ui/src/ui/controllers/channels.ts index 2952faebc5..b7dc018ebd 100644 --- a/ui/src/ui/controllers/channels.ts +++ b/ui/src/ui/controllers/channels.ts @@ -1,4 +1,5 @@ import type { ChannelsState } from "./channels.types"; +import { ChannelsStatusSnapshot } from "../types"; export type { ChannelsState }; @@ -12,7 +13,7 @@ export async function loadChannels(state: ChannelsState, probe: boolean) { state.channelsLoading = true; state.channelsError = null; try { - const res = await state.client.request("channels.status", { + const res = await state.client.request("channels.status", { probe, timeoutMs: 8000, }); @@ -31,10 +32,13 @@ export async function startWhatsAppLogin(state: ChannelsState, force: boolean) { } state.whatsappBusy = true; try { - const res = await state.client.request("web.login.start", { - force, - timeoutMs: 30000, - }); + const res = await state.client.request<{ message?: string; qrDataUrl?: string }>( + "web.login.start", + { + force, + timeoutMs: 30000, + }, + ); state.whatsappLoginMessage = res.message ?? null; state.whatsappLoginQrDataUrl = res.qrDataUrl ?? null; state.whatsappLoginConnected = null; @@ -53,9 +57,12 @@ export async function waitWhatsAppLogin(state: ChannelsState) { } state.whatsappBusy = true; try { - const res = await state.client.request("web.login.wait", { - timeoutMs: 120000, - }); + const res = await state.client.request<{ message?: string; connected?: boolean }>( + "web.login.wait", + { + timeoutMs: 120000, + }, + ); state.whatsappLoginMessage = res.message ?? null; state.whatsappLoginConnected = res.connected ?? null; if (res.connected) { diff --git a/ui/src/ui/controllers/chat.test.ts b/ui/src/ui/controllers/chat.test.ts index 3bd3aeb7f7..a61643c95f 100644 --- a/ui/src/ui/controllers/chat.test.ts +++ b/ui/src/ui/controllers/chat.test.ts @@ -3,18 +3,19 @@ import { handleChatEvent, type ChatEventPayload, type ChatState } from "./chat"; function createState(overrides: Partial = {}): ChatState { return { - client: null, - connected: true, - sessionKey: "main", + chatAttachments: [], chatLoading: false, - chatMessages: [], - chatThinkingLevel: null, - chatSending: false, chatMessage: "", + chatMessages: [], chatRunId: null, + chatSending: false, chatStream: null, chatStreamStartedAt: null, + chatThinkingLevel: null, + client: null, + connected: true, lastError: null, + sessionKey: "main", ...overrides, }; } diff --git a/ui/src/ui/controllers/chat.ts b/ui/src/ui/controllers/chat.ts index 1d8437e1c7..2bd59e8d67 100644 --- a/ui/src/ui/controllers/chat.ts +++ b/ui/src/ui/controllers/chat.ts @@ -34,10 +34,13 @@ export async function loadChatHistory(state: ChatState) { state.chatLoading = true; state.lastError = null; try { - const res = await state.client.request("chat.history", { - sessionKey: state.sessionKey, - limit: 200, - }); + const res = await state.client.request<{ messages?: Array; thinkingLevel?: string }>( + "chat.history", + { + sessionKey: state.sessionKey, + limit: 200, + }, + ); state.chatMessages = Array.isArray(res.messages) ? res.messages : []; state.chatThinkingLevel = res.thinkingLevel ?? null; } catch (err) { diff --git a/ui/src/ui/controllers/config.test.ts b/ui/src/ui/controllers/config.test.ts index d3b120f610..5a92c0b829 100644 --- a/ui/src/ui/controllers/config.test.ts +++ b/ui/src/ui/controllers/config.test.ts @@ -9,27 +9,30 @@ import { function createState(): ConfigState { return { - client: null, - connected: false, applySessionKey: "main", + client: null, + configActiveSection: null, + configActiveSubsection: null, + configApplying: false, + configForm: null, + configFormDirty: false, + configFormMode: "form", + configFormOriginal: null, + configIssues: [], configLoading: false, configRaw: "", configRawOriginal: "", - configValid: null, - configIssues: [], configSaving: false, - configApplying: false, - updateRunning: false, - configSnapshot: null, configSchema: null, - configSchemaVersion: null, configSchemaLoading: false, + configSchemaVersion: null, + configSearchQuery: "", + configSnapshot: null, configUiHints: {}, - configForm: null, - configFormOriginal: null, - configFormDirty: false, - configFormMode: "form", + configValid: null, + connected: false, lastError: null, + updateRunning: false, }; } diff --git a/ui/src/ui/controllers/config.ts b/ui/src/ui/controllers/config.ts index da0cb19dc4..8e4df347ab 100644 --- a/ui/src/ui/controllers/config.ts +++ b/ui/src/ui/controllers/config.ts @@ -41,7 +41,7 @@ export async function loadConfig(state: ConfigState) { state.configLoading = true; state.lastError = null; try { - const res = await state.client.request("config.get", {}); + const res = await state.client.request("config.get", {}); applyConfigSnapshot(state, res); } catch (err) { state.lastError = String(err); @@ -59,7 +59,7 @@ export async function loadConfigSchema(state: ConfigState) { } state.configSchemaLoading = true; try { - const res = await state.client.request("config.schema", {}); + const res = await state.client.request("config.schema", {}); applyConfigSchema(state, res); } catch (err) { state.lastError = String(err); diff --git a/ui/src/ui/controllers/cron.ts b/ui/src/ui/controllers/cron.ts index 51e80fc642..cfb7938caf 100644 --- a/ui/src/ui/controllers/cron.ts +++ b/ui/src/ui/controllers/cron.ts @@ -21,7 +21,7 @@ export async function loadCronStatus(state: CronState) { return; } try { - const res = await state.client.request("cron.status", {}); + const res = await state.client.request("cron.status", {}); state.cronStatus = res; } catch (err) { state.cronError = String(err); @@ -38,7 +38,7 @@ export async function loadCronJobs(state: CronState) { state.cronLoading = true; state.cronError = null; try { - const res = await state.client.request("cron.list", { + const res = await state.client.request<{ jobs?: Array }>("cron.list", { includeDisabled: true, }); state.cronJobs = Array.isArray(res.jobs) ? res.jobs : []; @@ -211,7 +211,7 @@ export async function loadCronRuns(state: CronState, jobId: string) { return; } try { - const res = await state.client.request("cron.runs", { + const res = await state.client.request<{ entries?: Array }>("cron.runs", { id: jobId, limit: 50, }); diff --git a/ui/src/ui/controllers/devices.ts b/ui/src/ui/controllers/devices.ts index 23c07a6b01..d60a619578 100644 --- a/ui/src/ui/controllers/devices.ts +++ b/ui/src/ui/controllers/devices.ts @@ -57,7 +57,10 @@ export async function loadDevices(state: DevicesState, opts?: { quiet?: boolean state.devicesError = null; } try { - const res = await state.client.request("device.pair.list", {}); + const res = await state.client.request<{ + pending?: Array; + paired?: Array; + }>("device.pair.list", {}); state.devicesList = { pending: Array.isArray(res?.pending) ? res.pending : [], paired: Array.isArray(res?.paired) ? res.paired : [], @@ -107,7 +110,12 @@ export async function rotateDeviceToken( return; } try { - const res = await state.client.request("device.token.rotate", params); + const res = await state.client.request<{ + token: string; + role?: string; + deviceId?: string; + scopes?: Array; + }>("device.token.rotate", params); if (res?.token) { const identity = await loadOrCreateDeviceIdentity(); const role = res.role ?? params.role; diff --git a/ui/src/ui/controllers/exec-approvals.ts b/ui/src/ui/controllers/exec-approvals.ts index aa6ba27011..f1bc04a5cd 100644 --- a/ui/src/ui/controllers/exec-approvals.ts +++ b/ui/src/ui/controllers/exec-approvals.ts @@ -94,7 +94,7 @@ export async function loadExecApprovals( state.lastError = "Select a node before loading exec approvals."; return; } - const res = await state.client.request(rpc.method, rpc.params); + const res = await state.client.request(rpc.method, rpc.params); applyExecApprovalsSnapshot(state, res); } catch (err) { state.lastError = String(err); diff --git a/ui/src/ui/controllers/nodes.ts b/ui/src/ui/controllers/nodes.ts index 711a6034c1..1883bd3256 100644 --- a/ui/src/ui/controllers/nodes.ts +++ b/ui/src/ui/controllers/nodes.ts @@ -20,7 +20,7 @@ export async function loadNodes(state: NodesState, opts?: { quiet?: boolean }) { state.lastError = null; } try { - const res = await state.client.request("node.list", {}); + const res = await state.client.request<{ nodes?: Record }>("node.list", {}); state.nodes = Array.isArray(res.nodes) ? res.nodes : []; } catch (err) { if (!opts?.quiet) { diff --git a/ui/src/ui/controllers/sessions.ts b/ui/src/ui/controllers/sessions.ts index 112d990bd4..f585d01b28 100644 --- a/ui/src/ui/controllers/sessions.ts +++ b/ui/src/ui/controllers/sessions.ts @@ -46,7 +46,7 @@ export async function loadSessions( if (limit > 0) { params.limit = limit; } - const res = await state.client.request("sessions.list", params); + const res = await state.client.request("sessions.list", params); if (res) { state.sessionsResult = res; } diff --git a/ui/src/ui/controllers/skills.ts b/ui/src/ui/controllers/skills.ts index 568a296472..d74d68a3ee 100644 --- a/ui/src/ui/controllers/skills.ts +++ b/ui/src/ui/controllers/skills.ts @@ -56,7 +56,7 @@ export async function loadSkills(state: SkillsState, options?: LoadSkillsOptions state.skillsLoading = true; state.skillsError = null; try { - const res = await state.client.request("skills.status", {}); + const res = await state.client.request("skills.status", {}); if (res) { state.skillsReport = res; } @@ -134,7 +134,7 @@ export async function installSkill( state.skillsBusyKey = skillKey; state.skillsError = null; try { - const result = await state.client.request("skills.install", { + const result = await state.client.request<{ message?: string }>("skills.install", { name, installId, timeoutMs: 120000, diff --git a/ui/src/ui/device-identity.ts b/ui/src/ui/device-identity.ts index f6febf6d3d..947b818503 100644 --- a/ui/src/ui/device-identity.ts +++ b/ui/src/ui/device-identity.ts @@ -42,7 +42,7 @@ function bytesToHex(bytes: Uint8Array): string { } async function fingerprintPublicKey(publicKey: Uint8Array): Promise { - const hash = await crypto.subtle.digest("SHA-256", publicKey); + const hash = await crypto.subtle.digest("SHA-256", publicKey.slice().buffer); return bytesToHex(new Uint8Array(hash)); } diff --git a/ui/src/ui/markdown.ts b/ui/src/ui/markdown.ts index d78b7e3fd6..ef500112a7 100644 --- a/ui/src/ui/markdown.ts +++ b/ui/src/ui/markdown.ts @@ -5,7 +5,6 @@ import { truncateText } from "./format"; marked.setOptions({ gfm: true, breaks: true, - mangle: false, }); const allowedTags = [ diff --git a/ui/src/ui/navigation.browser.test.ts b/ui/src/ui/navigation.browser.test.ts index 84945e969f..bf020419fe 100644 --- a/ui/src/ui/navigation.browser.test.ts +++ b/ui/src/ui/navigation.browser.test.ts @@ -116,7 +116,7 @@ describe("control UI routing", () => { const app = mountApp("/chat"); await app.updateComplete; - const initialContainer = app.querySelector(".chat-thread"); + const initialContainer: HTMLElement | null = app.querySelector(".chat-thread"); expect(initialContainer).not.toBeNull(); if (!initialContainer) { return; diff --git a/ui/src/ui/types.ts b/ui/src/ui/types.ts index 7f00dd44c4..11ddc97a84 100644 --- a/ui/src/ui/types.ts +++ b/ui/src/ui/types.ts @@ -302,18 +302,20 @@ export type ConfigSchemaResponse = { }; export type PresenceEntry = { - instanceId?: string | null; - host?: string | null; - ip?: string | null; - version?: string | null; - platform?: string | null; deviceFamily?: string | null; - modelIdentifier?: string | null; - mode?: string | null; + host?: string | null; + instanceId?: string | null; + ip?: string | null; lastInputSeconds?: number | null; + mode?: string | null; + modelIdentifier?: string | null; + platform?: string | null; reason?: string | null; + roles?: Array | null; + scopes?: Array | null; text?: string | null; ts?: number | null; + version?: string | null; }; export type GatewaySessionsDefaults = { diff --git a/ui/src/ui/views/channels.ts b/ui/src/ui/views/channels.ts index 96bdbc115d..e4f35c76bf 100644 --- a/ui/src/ui/views/channels.ts +++ b/ui/src/ui/views/channels.ts @@ -124,7 +124,7 @@ function renderChannel(key: ChannelKey, props: ChannelsProps, data: ChannelsChan case "googlechat": return renderGoogleChatCard({ props, - googlechat: data.googlechat, + googleChat: data.googlechat, accountCountLabel, }); case "slack": diff --git a/ui/src/ui/views/config.browser.test.ts b/ui/src/ui/views/config.browser.test.ts index 481e163b44..fd87612b3f 100644 --- a/ui/src/ui/views/config.browser.test.ts +++ b/ui/src/ui/views/config.browser.test.ts @@ -194,7 +194,7 @@ describe("config view", () => { if (!input) { return; } - input.value = "gateway"; + (input as HTMLInputElement).value = "gateway"; input.dispatchEvent(new Event("input", { bubbles: true })); expect(onSearchChange).toHaveBeenCalledWith("gateway"); }); diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 85d70e937c..ce6c525cd2 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -3,8 +3,9 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", - "lib": ["ES2022", "DOM", "DOM.Iterable"], + "lib": ["ES2023", "DOM", "DOM.Iterable"], "strict": true, + "noEmit": true, "experimentalDecorators": true, "skipLibCheck": true, "types": ["vite/client"],