chore: Fix all TypeScript errors in `ui`.

main
cpojer 2026-02-03 22:35:55 +09:00
parent be4f7ef361
commit 27677dd8bd
No known key found for this signature in database
GPG Key ID: C29F94A3201118AF
25 changed files with 226 additions and 149 deletions

View File

@ -10,7 +10,7 @@ import { loadSessions } from "./controllers/sessions";
import { normalizeBasePath } from "./navigation"; import { normalizeBasePath } from "./navigation";
import { generateUUID } from "./uuid"; import { generateUUID } from "./uuid";
type ChatHost = { export type ChatHost = {
connected: boolean; connected: boolean;
chatMessage: string; chatMessage: string;
chatAttachments: ChatAttachment[]; chatAttachments: ChatAttachment[];

View File

@ -4,9 +4,10 @@ import type { AppViewState } from "./app-view-state";
import type { ThemeMode } from "./theme"; import type { ThemeMode } from "./theme";
import type { ThemeTransitionContext } from "./theme-transition"; import type { ThemeTransitionContext } from "./theme-transition";
import type { SessionsListResult } from "./types"; import type { SessionsListResult } from "./types";
import { OpenClawApp } from "./app";
import { refreshChat } from "./app-chat"; import { refreshChat } from "./app-chat";
import { syncUrlWithSessionKey } from "./app-settings"; import { syncUrlWithSessionKey } from "./app-settings";
import { loadChatHistory } from "./controllers/chat"; import { ChatState, loadChatHistory } from "./controllers/chat";
import { icons } from "./icons"; import { icons } from "./icons";
import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation"; import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation";
@ -94,18 +95,18 @@ export function renderChatControls(state: AppViewState) {
state.sessionKey = next; state.sessionKey = next;
state.chatMessage = ""; state.chatMessage = "";
state.chatStream = null; state.chatStream = null;
state.chatStreamStartedAt = null; (state as unknown as OpenClawApp).chatStreamStartedAt = null;
state.chatRunId = null; state.chatRunId = null;
state.resetToolStream(); (state as unknown as OpenClawApp).resetToolStream();
state.resetChatScroll(); (state as unknown as OpenClawApp).resetChatScroll();
state.applySettings({ state.applySettings({
...state.settings, ...state.settings,
sessionKey: next, sessionKey: next,
lastActiveSessionKey: next, lastActiveSessionKey: next,
}); });
void state.loadAssistantIdentity(); void state.loadAssistantIdentity();
syncUrlWithSessionKey(state, next, true); syncUrlWithSessionKey(next, true);
void loadChatHistory(state); void loadChatHistory(state as unknown as ChatState);
}} }}
> >
${repeat( ${repeat(
@ -122,7 +123,7 @@ export function renderChatControls(state: AppViewState) {
class="btn btn--sm btn--icon" class="btn btn--sm btn--icon"
?disabled=${state.chatLoading || !state.connected} ?disabled=${state.chatLoading || !state.connected}
@click=${() => { @click=${() => {
state.resetToolStream(); (state as unknown as OpenClawApp).resetToolStream();
void refreshChat(state as unknown as Parameters<typeof refreshChat>[0]); void refreshChat(state as unknown as Parameters<typeof refreshChat>[0]);
}} }}
title="Refresh chat data" title="Refresh chat data"
@ -228,7 +229,7 @@ function resolveSessionOptions(
seen.add(mainSessionKey); seen.add(mainSessionKey);
options.push({ options.push({
key: mainSessionKey, key: mainSessionKey,
displayName: resolveSessionDisplayName(mainSessionKey, resolvedMain), displayName: resolveSessionDisplayName(mainSessionKey, resolvedMain || undefined),
}); });
} }

View File

@ -1,16 +1,18 @@
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import type { AppViewState } from "./app-view-state"; import type { AppViewState } from "./app-view-state";
import { parseAgentSessionKey } from "../../../src/routing/session-key.js"; 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 { renderChatControls, renderTab, renderThemeToggle } from "./app-render.helpers";
import { loadAgentFileContent, loadAgentFiles, saveAgentFile } from "./controllers/agent-files"; import { loadAgentFileContent, loadAgentFiles, saveAgentFile } from "./controllers/agent-files";
import { loadAgentIdentities, loadAgentIdentity } from "./controllers/agent-identity"; import { loadAgentIdentities, loadAgentIdentity } from "./controllers/agent-identity";
import { loadAgentSkills } from "./controllers/agent-skills"; import { loadAgentSkills } from "./controllers/agent-skills";
import { loadAgents } from "./controllers/agents"; import { loadAgents } from "./controllers/agents";
import { loadChannels } from "./controllers/channels"; import { loadChannels } from "./controllers/channels";
import { loadChatHistory } from "./controllers/chat"; import { ChatState, loadChatHistory } from "./controllers/chat";
import { import {
applyConfig, applyConfig,
ConfigState,
loadConfig, loadConfig,
runUpdate, runUpdate,
saveConfig, saveConfig,
@ -38,7 +40,7 @@ import {
saveExecApprovals, saveExecApprovals,
updateExecApprovalsFormValue, updateExecApprovalsFormValue,
} from "./controllers/exec-approvals"; } from "./controllers/exec-approvals";
import { loadLogs } from "./controllers/logs"; import { loadLogs, LogsState } from "./controllers/logs";
import { loadNodes } from "./controllers/nodes"; import { loadNodes } from "./controllers/nodes";
import { loadPresence } from "./controllers/presence"; import { loadPresence } from "./controllers/presence";
import { deleteSession, loadSessions, patchSession } from "./controllers/sessions"; import { deleteSession, loadSessions, patchSession } from "./controllers/sessions";
@ -51,6 +53,7 @@ import {
} from "./controllers/skills"; } from "./controllers/skills";
import { icons } from "./icons"; import { icons } from "./icons";
import { TAB_GROUPS, subtitleForTab, titleForTab } from "./navigation"; import { TAB_GROUPS, subtitleForTab, titleForTab } from "./navigation";
import { ConfigUiHints } from "./types";
import { renderAgents } from "./views/agents"; import { renderAgents } from "./views/agents";
import { renderChannels } from "./views/channels"; import { renderChannels } from "./views/channels";
import { renderChat } from "./views/chat"; import { renderChat } from "./views/chat";
@ -213,7 +216,7 @@ export function renderApp(state: AppViewState) {
onSessionKeyChange: (next) => { onSessionKeyChange: (next) => {
state.sessionKey = next; state.sessionKey = next;
state.chatMessage = ""; state.chatMessage = "";
state.resetToolStream(); (state as unknown as OpenClawApp).resetToolStream();
state.applySettings({ state.applySettings({
...state.settings, ...state.settings,
sessionKey: next, sessionKey: next,
@ -242,7 +245,7 @@ export function renderApp(state: AppViewState) {
configSchema: state.configSchema, configSchema: state.configSchema,
configSchemaLoading: state.configSchemaLoading, configSchemaLoading: state.configSchemaLoading,
configForm: state.configForm, configForm: state.configForm,
configUiHints: state.configUiHints, configUiHints: state.configUiHints as ConfigUiHints,
configSaving: state.configSaving, configSaving: state.configSaving,
configFormDirty: state.configFormDirty, configFormDirty: state.configFormDirty,
nostrProfileFormState: state.nostrProfileFormState, nostrProfileFormState: state.nostrProfileFormState,
@ -251,7 +254,8 @@ export function renderApp(state: AppViewState) {
onWhatsAppStart: (force) => state.handleWhatsAppStart(force), onWhatsAppStart: (force) => state.handleWhatsAppStart(force),
onWhatsAppWait: () => state.handleWhatsAppWait(), onWhatsAppWait: () => state.handleWhatsAppWait(),
onWhatsAppLogout: () => state.handleWhatsAppLogout(), onWhatsAppLogout: () => state.handleWhatsAppLogout(),
onConfigPatch: (path, value) => updateConfigFormValue(state, path, value), onConfigPatch: (path, value) =>
updateConfigFormValue(state as unknown as ConfigState, path, value),
onConfigSave: () => state.handleChannelConfigSave(), onConfigSave: () => state.handleChannelConfigSave(),
onConfigReload: () => state.handleChannelConfigReload(), onConfigReload: () => state.handleChannelConfigReload(),
onNostrProfileEdit: (accountId, profile) => onNostrProfileEdit: (accountId, profile) =>
@ -460,12 +464,19 @@ export function renderApp(state: AppViewState) {
} }
const basePath = ["agents", "list", index, "tools"]; const basePath = ["agents", "list", index, "tools"];
if (profile) { if (profile) {
updateConfigFormValue(state, [...basePath, "profile"], profile); updateConfigFormValue(
state as unknown as ConfigState,
[...basePath, "profile"],
profile,
);
} else { } else {
removeConfigFormValue(state, [...basePath, "profile"]); removeConfigFormValue(state as unknown as ConfigState, [
...basePath,
"profile",
]);
} }
if (clearAllow) { if (clearAllow) {
removeConfigFormValue(state, [...basePath, "allow"]); removeConfigFormValue(state as unknown as ConfigState, [...basePath, "allow"]);
} }
}, },
onToolsOverridesChange: (agentId, alsoAllow, deny) => { onToolsOverridesChange: (agentId, alsoAllow, deny) => {
@ -488,18 +499,29 @@ export function renderApp(state: AppViewState) {
} }
const basePath = ["agents", "list", index, "tools"]; const basePath = ["agents", "list", index, "tools"];
if (alsoAllow.length > 0) { if (alsoAllow.length > 0) {
updateConfigFormValue(state, [...basePath, "alsoAllow"], alsoAllow); updateConfigFormValue(
state as unknown as ConfigState,
[...basePath, "alsoAllow"],
alsoAllow,
);
} else { } else {
removeConfigFormValue(state, [...basePath, "alsoAllow"]); removeConfigFormValue(state as unknown as ConfigState, [
...basePath,
"alsoAllow",
]);
} }
if (deny.length > 0) { if (deny.length > 0) {
updateConfigFormValue(state, [...basePath, "deny"], deny); updateConfigFormValue(
state as unknown as ConfigState,
[...basePath, "deny"],
deny,
);
} else { } else {
removeConfigFormValue(state, [...basePath, "deny"]); removeConfigFormValue(state as unknown as ConfigState, [...basePath, "deny"]);
} }
}, },
onConfigReload: () => loadConfig(state), onConfigReload: () => loadConfig(state as unknown as ConfigState),
onConfigSave: () => saveConfig(state), onConfigSave: () => saveConfig(state as unknown as ConfigState),
onChannelsRefresh: () => loadChannels(state, false), onChannelsRefresh: () => loadChannels(state, false),
onCronRefresh: () => state.loadCron(), onCronRefresh: () => state.loadCron(),
onSkillsFilterChange: (next) => (state.skillsFilter = next), onSkillsFilterChange: (next) => (state.skillsFilter = next),
@ -544,7 +566,11 @@ export function renderApp(state: AppViewState) {
} else { } else {
next.delete(normalizedSkill); next.delete(normalizedSkill);
} }
updateConfigFormValue(state, ["agents", "list", index, "skills"], [...next]); updateConfigFormValue(
state as unknown as ConfigState,
["agents", "list", index, "skills"],
[...next],
);
}, },
onAgentSkillsClear: (agentId) => { onAgentSkillsClear: (agentId) => {
if (!configValue) { if (!configValue) {
@ -564,7 +590,12 @@ export function renderApp(state: AppViewState) {
if (index < 0) { if (index < 0) {
return; return;
} }
removeConfigFormValue(state, ["agents", "list", index, "skills"]); removeConfigFormValue(state as unknown as ConfigState, [
"agents",
"list",
index,
"skills",
]);
}, },
onAgentSkillsDisableAll: (agentId) => { onAgentSkillsDisableAll: (agentId) => {
if (!configValue) { if (!configValue) {
@ -584,7 +615,11 @@ export function renderApp(state: AppViewState) {
if (index < 0) { if (index < 0) {
return; return;
} }
updateConfigFormValue(state, ["agents", "list", index, "skills"], []); updateConfigFormValue(
state as unknown as ConfigState,
["agents", "list", index, "skills"],
[],
);
}, },
onModelChange: (agentId, modelId) => { onModelChange: (agentId, modelId) => {
if (!configValue) { if (!configValue) {
@ -606,7 +641,7 @@ export function renderApp(state: AppViewState) {
} }
const basePath = ["agents", "list", index, "model"]; const basePath = ["agents", "list", index, "model"];
if (!modelId) { if (!modelId) {
removeConfigFormValue(state, basePath); removeConfigFormValue(state as unknown as ConfigState, basePath);
return; return;
} }
const entry = list[index] as { model?: unknown }; const entry = list[index] as { model?: unknown };
@ -617,9 +652,9 @@ export function renderApp(state: AppViewState) {
primary: modelId, primary: modelId,
...(Array.isArray(fallbacks) ? { fallbacks } : {}), ...(Array.isArray(fallbacks) ? { fallbacks } : {}),
}; };
updateConfigFormValue(state, basePath, next); updateConfigFormValue(state as unknown as ConfigState, basePath, next);
} else { } else {
updateConfigFormValue(state, basePath, modelId); updateConfigFormValue(state as unknown as ConfigState, basePath, modelId);
} }
}, },
onModelFallbacksChange: (agentId, fallbacks) => { onModelFallbacksChange: (agentId, fallbacks) => {
@ -660,16 +695,16 @@ export function renderApp(state: AppViewState) {
const primary = resolvePrimary(); const primary = resolvePrimary();
if (normalized.length === 0) { if (normalized.length === 0) {
if (primary) { if (primary) {
updateConfigFormValue(state, basePath, primary); updateConfigFormValue(state as unknown as ConfigState, basePath, primary);
} else { } else {
removeConfigFormValue(state, basePath); removeConfigFormValue(state as unknown as ConfigState, basePath);
} }
return; return;
} }
const next = primary const next = primary
? { primary, fallbacks: normalized } ? { primary, fallbacks: normalized }
: { fallbacks: normalized }; : { fallbacks: normalized };
updateConfigFormValue(state, basePath, next); updateConfigFormValue(state as unknown as ConfigState, basePath, next);
}, },
}) })
: nothing : nothing
@ -726,7 +761,7 @@ export function renderApp(state: AppViewState) {
onDeviceRotate: (deviceId, role, scopes) => onDeviceRotate: (deviceId, role, scopes) =>
rotateDeviceToken(state, { deviceId, role, scopes }), rotateDeviceToken(state, { deviceId, role, scopes }),
onDeviceRevoke: (deviceId, role) => revokeDeviceToken(state, { deviceId, role }), onDeviceRevoke: (deviceId, role) => revokeDeviceToken(state, { deviceId, role }),
onLoadConfig: () => loadConfig(state), onLoadConfig: () => loadConfig(state as unknown as ConfigState),
onLoadExecApprovals: () => { onLoadExecApprovals: () => {
const target = const target =
state.execApprovalsTarget === "node" && state.execApprovalsTargetNodeId state.execApprovalsTarget === "node" && state.execApprovalsTargetNodeId
@ -736,20 +771,28 @@ export function renderApp(state: AppViewState) {
}, },
onBindDefault: (nodeId) => { onBindDefault: (nodeId) => {
if (nodeId) { if (nodeId) {
updateConfigFormValue(state, ["tools", "exec", "node"], nodeId); updateConfigFormValue(
state as unknown as ConfigState,
["tools", "exec", "node"],
nodeId,
);
} else { } else {
removeConfigFormValue(state, ["tools", "exec", "node"]); removeConfigFormValue(state as unknown as ConfigState, [
"tools",
"exec",
"node",
]);
} }
}, },
onBindAgent: (agentIndex, nodeId) => { onBindAgent: (agentIndex, nodeId) => {
const basePath = ["agents", "list", agentIndex, "tools", "exec", "node"]; const basePath = ["agents", "list", agentIndex, "tools", "exec", "node"];
if (nodeId) { if (nodeId) {
updateConfigFormValue(state, basePath, nodeId); updateConfigFormValue(state as unknown as ConfigState, basePath, nodeId);
} else { } else {
removeConfigFormValue(state, basePath); removeConfigFormValue(state as unknown as ConfigState, basePath);
} }
}, },
onSaveBindings: () => saveConfig(state), onSaveBindings: () => saveConfig(state as unknown as ConfigState),
onExecApprovalsTargetChange: (kind, nodeId) => { onExecApprovalsTargetChange: (kind, nodeId) => {
state.execApprovalsTarget = kind; state.execApprovalsTarget = kind;
state.execApprovalsTargetNodeId = nodeId; state.execApprovalsTargetNodeId = nodeId;
@ -784,30 +827,29 @@ export function renderApp(state: AppViewState) {
state.chatMessage = ""; state.chatMessage = "";
state.chatAttachments = []; state.chatAttachments = [];
state.chatStream = null; state.chatStream = null;
state.chatStreamStartedAt = null;
state.chatRunId = null; state.chatRunId = null;
(state as unknown as OpenClawApp).chatStreamStartedAt = null;
state.chatQueue = []; state.chatQueue = [];
state.resetToolStream(); (state as unknown as OpenClawApp).resetToolStream();
state.resetChatScroll(); (state as unknown as OpenClawApp).resetChatScroll();
state.applySettings({ state.applySettings({
...state.settings, ...state.settings,
sessionKey: next, sessionKey: next,
lastActiveSessionKey: next, lastActiveSessionKey: next,
}); });
void state.loadAssistantIdentity(); void state.loadAssistantIdentity();
void loadChatHistory(state); void loadChatHistory(state as unknown as ChatState);
void refreshChatAvatar(state); void refreshChatAvatar(state as unknown as ChatHost);
}, },
thinkingLevel: state.chatThinkingLevel, thinkingLevel: state.chatThinkingLevel,
showThinking, showThinking,
loading: state.chatLoading, loading: state.chatLoading,
sending: state.chatSending, sending: state.chatSending,
compactionStatus: state.compactionStatus,
assistantAvatarUrl: chatAvatarUrl, assistantAvatarUrl: chatAvatarUrl,
messages: state.chatMessages, messages: state.chatMessages,
toolMessages: state.chatToolMessages, toolMessages: state.chatToolMessages,
stream: state.chatStream, stream: state.chatStream,
streamStartedAt: state.chatStreamStartedAt, streamStartedAt: null,
draft: state.chatMessage, draft: state.chatMessage,
queue: state.chatQueue, queue: state.chatQueue,
connected: state.connected, connected: state.connected,
@ -817,8 +859,10 @@ export function renderApp(state: AppViewState) {
sessions: state.sessionsResult, sessions: state.sessionsResult,
focusMode: chatFocus, focusMode: chatFocus,
onRefresh: () => { onRefresh: () => {
state.resetToolStream(); return Promise.all([
return Promise.all([loadChatHistory(state), refreshChatAvatar(state)]); loadChatHistory(state as unknown as ChatState),
refreshChatAvatar(state as unknown as ChatHost),
]);
}, },
onToggleFocusMode: () => { onToggleFocusMode: () => {
if (state.onboarding) { if (state.onboarding) {
@ -829,25 +873,28 @@ export function renderApp(state: AppViewState) {
chatFocusMode: !state.settings.chatFocusMode, chatFocusMode: !state.settings.chatFocusMode,
}); });
}, },
onChatScroll: (event) => state.handleChatScroll(event), onChatScroll: (event) => (state as unknown as OpenClawApp).handleChatScroll(event),
onDraftChange: (next) => (state.chatMessage = next), onDraftChange: (next) => (state.chatMessage = next),
attachments: state.chatAttachments, attachments: state.chatAttachments,
onAttachmentsChange: (next) => (state.chatAttachments = next), onAttachmentsChange: (next) => (state.chatAttachments = next),
onSend: () => state.handleSendChat(), onSend: () => (state as unknown as OpenClawApp).handleSendChat(),
canAbort: Boolean(state.chatRunId), canAbort: Boolean(state.chatRunId),
onAbort: () => void state.handleAbortChat(), onAbort: () => void (state as unknown as OpenClawApp).handleAbortChat(),
onQueueRemove: (id) => state.removeQueuedMessage(id), onQueueRemove: (id) => (state as unknown as OpenClawApp).removeQueuedMessage(id),
onNewSession: () => state.handleSendChat("/new", { restoreDraft: true }), onNewSession: () =>
(state as unknown as OpenClawApp).handleSendChat("/new", { restoreDraft: true }),
showNewMessages: state.chatNewMessagesBelow, showNewMessages: state.chatNewMessagesBelow,
onScrollToBottom: () => state.scrollToBottom(), onScrollToBottom: () => state.scrollToBottom(),
// Sidebar props for tool output viewing // Sidebar props for tool output viewing
sidebarOpen: state.sidebarOpen, sidebarOpen: (state as unknown as OpenClawApp).sidebarOpen,
sidebarContent: state.sidebarContent, sidebarContent: (state as unknown as OpenClawApp).sidebarContent,
sidebarError: state.sidebarError, sidebarError: (state as unknown as OpenClawApp).sidebarError,
splitRatio: state.splitRatio, splitRatio: (state as unknown as OpenClawApp).splitRatio,
onOpenSidebar: (content: string) => state.handleOpenSidebar(content), onOpenSidebar: (content: string) =>
onCloseSidebar: () => state.handleCloseSidebar(), (state as unknown as OpenClawApp).handleOpenSidebar(content),
onSplitRatioChange: (ratio: number) => state.handleSplitRatioChange(ratio), onCloseSidebar: () => (state as unknown as OpenClawApp).handleCloseSidebar(),
onSplitRatioChange: (ratio: number) =>
(state as unknown as OpenClawApp).handleSplitRatioChange(ratio),
assistantName: state.assistantName, assistantName: state.assistantName,
assistantAvatar: state.assistantAvatar, assistantAvatar: state.assistantAvatar,
}) })
@ -868,28 +915,31 @@ export function renderApp(state: AppViewState) {
connected: state.connected, connected: state.connected,
schema: state.configSchema, schema: state.configSchema,
schemaLoading: state.configSchemaLoading, schemaLoading: state.configSchemaLoading,
uiHints: state.configUiHints, uiHints: state.configUiHints as ConfigUiHints,
formMode: state.configFormMode, formMode: state.configFormMode,
formValue: state.configForm, formValue: state.configForm,
originalValue: state.configFormOriginal, originalValue: state.configFormOriginal,
searchQuery: state.configSearchQuery, searchQuery: (state as unknown as OpenClawApp).configSearchQuery,
activeSection: state.configActiveSection, activeSection: (state as unknown as OpenClawApp).configActiveSection,
activeSubsection: state.configActiveSubsection, activeSubsection: (state as unknown as OpenClawApp).configActiveSubsection,
onRawChange: (next) => { onRawChange: (next) => {
state.configRaw = next; state.configRaw = next;
}, },
onFormModeChange: (mode) => (state.configFormMode = mode), onFormModeChange: (mode) => (state.configFormMode = mode),
onFormPatch: (path, value) => updateConfigFormValue(state, path, value), onFormPatch: (path, value) =>
onSearchChange: (query) => (state.configSearchQuery = query), updateConfigFormValue(state as unknown as OpenClawApp, path, value),
onSearchChange: (query) =>
((state as unknown as OpenClawApp).configSearchQuery = query),
onSectionChange: (section) => { onSectionChange: (section) => {
state.configActiveSection = section; (state as unknown as OpenClawApp).configActiveSection = section;
state.configActiveSubsection = null; (state as unknown as OpenClawApp).configActiveSubsection = null;
}, },
onSubsectionChange: (section) => (state.configActiveSubsection = section), onSubsectionChange: (section) =>
onReload: () => loadConfig(state), ((state as unknown as OpenClawApp).configActiveSubsection = section),
onSave: () => saveConfig(state), onReload: () => loadConfig(state as unknown as OpenClawApp),
onApply: () => applyConfig(state), onSave: () => saveConfig(state as unknown as OpenClawApp),
onUpdate: () => runUpdate(state), onApply: () => applyConfig(state as unknown as OpenClawApp),
onUpdate: () => runUpdate(state as unknown as OpenClawApp),
}) })
: nothing : nothing
} }
@ -931,9 +981,10 @@ export function renderApp(state: AppViewState) {
state.logsLevelFilters = { ...state.logsLevelFilters, [level]: enabled }; state.logsLevelFilters = { ...state.logsLevelFilters, [level]: enabled };
}, },
onToggleAutoFollow: (next) => (state.logsAutoFollow = next), onToggleAutoFollow: (next) => (state.logsAutoFollow = next),
onRefresh: () => loadLogs(state, { reset: true }), onRefresh: () => loadLogs(state as unknown as LogsState, { reset: true }),
onExport: (lines, label) => state.exportLogs(lines, label), onExport: (lines, label) =>
onScroll: (event) => state.handleLogsScroll(event), (state as unknown as OpenClawApp).exportLogs(lines, label),
onScroll: (event) => (state as unknown as OpenClawApp).handleLogsScroll(event),
}) })
: nothing : nothing
} }

View File

@ -99,7 +99,7 @@ export function applySettingsFromUrl(host: SettingsHost) {
if (passwordRaw != null) { if (passwordRaw != null) {
const password = passwordRaw.trim(); const password = passwordRaw.trim();
if (password) { if (password) {
(host as { password: string }).password = password; (host as unknown as { password: string }).password = password;
} }
params.delete("password"); params.delete("password");
shouldCleanUrl = true; shouldCleanUrl = true;
@ -189,23 +189,24 @@ export async function refreshActiveTab(host: SettingsHost) {
await loadSkills(host as unknown as OpenClawApp); await loadSkills(host as unknown as OpenClawApp);
} }
if (host.tab === "agents") { if (host.tab === "agents") {
await loadAgents(host as unknown as OpenClawApp); const app = host as unknown as OpenClawApp;
await loadConfig(host as unknown as OpenClawApp); await loadAgents(app);
const agentIds = host.agentsList?.agents?.map((entry) => entry.id) ?? []; await loadConfig(app);
const agentIds = app.agentsList?.agents?.map((entry) => entry.id) ?? [];
if (agentIds.length > 0) { if (agentIds.length > 0) {
void loadAgentIdentities(host as unknown as OpenClawApp, agentIds); void loadAgentIdentities(app, agentIds);
} }
const agentId = const agentId =
host.agentsSelectedId ?? host.agentsList?.defaultId ?? host.agentsList?.agents?.[0]?.id; app.agentsSelectedId ?? app.agentsList?.defaultId ?? app.agentsList?.agents?.[0]?.id;
if (agentId) { if (agentId) {
void loadAgentIdentity(host as unknown as OpenClawApp, agentId); void loadAgentIdentity(app, agentId);
if (host.agentsPanel === "skills") { if (app.agentsPanel === "skills") {
void loadAgentSkills(host as unknown as OpenClawApp, agentId); void loadAgentSkills(app, agentId);
} }
if (host.agentsPanel === "channels") { if (app.agentsPanel === "channels") {
void loadChannels(host as unknown as OpenClawApp, false); void loadChannels(app, false);
} }
if (host.agentsPanel === "cron") { if (app.agentsPanel === "cron") {
void loadCron(host); 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") { if (typeof window === "undefined") {
return; return;
} }

View File

@ -257,7 +257,7 @@ export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPaylo
sessionKey, sessionKey,
name, name,
args, args,
output, output: output || undefined,
startedAt: typeof payload.ts === "number" ? payload.ts : now, startedAt: typeof payload.ts === "number" ? payload.ts : now,
updatedAt: now, updatedAt: now,
message: {}, message: {},
@ -270,7 +270,7 @@ export function handleAgentEvent(host: ToolStreamHost, payload?: AgentEventPaylo
entry.args = args; entry.args = args;
} }
if (output !== undefined) { if (output !== undefined) {
entry.output = output; entry.output = output || undefined;
} }
entry.updatedAt = now; entry.updatedAt = now;
} }

View File

@ -51,7 +51,7 @@ describe("config form renderer", () => {
container, container,
); );
const tokenInput = container.querySelector("input[type='password']"); const tokenInput: HTMLInputElement | null = container.querySelector("input[type='password']");
expect(tokenInput).not.toBeNull(); expect(tokenInput).not.toBeNull();
if (!tokenInput) { if (!tokenInput) {
return; return;
@ -67,7 +67,7 @@ describe("config form renderer", () => {
tokenButton?.dispatchEvent(new MouseEvent("click", { bubbles: true })); tokenButton?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
expect(onPatch).toHaveBeenCalledWith(["mode"], "token"); 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(); expect(checkbox).not.toBeNull();
if (!checkbox) { if (!checkbox) {
return; return;

View File

@ -20,7 +20,7 @@ export async function loadAgents(state: AgentsState) {
state.agentsLoading = true; state.agentsLoading = true;
state.agentsError = null; state.agentsError = null;
try { try {
const res = await state.client.request("agents.list", {}); const res = await state.client.request<AgentsListResult>("agents.list", {});
if (res) { if (res) {
state.agentsList = res; state.agentsList = res;
const selected = state.agentsSelectedId; const selected = state.agentsSelectedId;

View File

@ -1,4 +1,5 @@
import type { ChannelsState } from "./channels.types"; import type { ChannelsState } from "./channels.types";
import { ChannelsStatusSnapshot } from "../types";
export type { ChannelsState }; export type { ChannelsState };
@ -12,7 +13,7 @@ export async function loadChannels(state: ChannelsState, probe: boolean) {
state.channelsLoading = true; state.channelsLoading = true;
state.channelsError = null; state.channelsError = null;
try { try {
const res = await state.client.request("channels.status", { const res = await state.client.request<ChannelsStatusSnapshot | null>("channels.status", {
probe, probe,
timeoutMs: 8000, timeoutMs: 8000,
}); });
@ -31,10 +32,13 @@ export async function startWhatsAppLogin(state: ChannelsState, force: boolean) {
} }
state.whatsappBusy = true; state.whatsappBusy = true;
try { try {
const res = await state.client.request("web.login.start", { const res = await state.client.request<{ message?: string; qrDataUrl?: string }>(
force, "web.login.start",
timeoutMs: 30000, {
}); force,
timeoutMs: 30000,
},
);
state.whatsappLoginMessage = res.message ?? null; state.whatsappLoginMessage = res.message ?? null;
state.whatsappLoginQrDataUrl = res.qrDataUrl ?? null; state.whatsappLoginQrDataUrl = res.qrDataUrl ?? null;
state.whatsappLoginConnected = null; state.whatsappLoginConnected = null;
@ -53,9 +57,12 @@ export async function waitWhatsAppLogin(state: ChannelsState) {
} }
state.whatsappBusy = true; state.whatsappBusy = true;
try { try {
const res = await state.client.request("web.login.wait", { const res = await state.client.request<{ message?: string; connected?: boolean }>(
timeoutMs: 120000, "web.login.wait",
}); {
timeoutMs: 120000,
},
);
state.whatsappLoginMessage = res.message ?? null; state.whatsappLoginMessage = res.message ?? null;
state.whatsappLoginConnected = res.connected ?? null; state.whatsappLoginConnected = res.connected ?? null;
if (res.connected) { if (res.connected) {

View File

@ -3,18 +3,19 @@ import { handleChatEvent, type ChatEventPayload, type ChatState } from "./chat";
function createState(overrides: Partial<ChatState> = {}): ChatState { function createState(overrides: Partial<ChatState> = {}): ChatState {
return { return {
client: null, chatAttachments: [],
connected: true,
sessionKey: "main",
chatLoading: false, chatLoading: false,
chatMessages: [],
chatThinkingLevel: null,
chatSending: false,
chatMessage: "", chatMessage: "",
chatMessages: [],
chatRunId: null, chatRunId: null,
chatSending: false,
chatStream: null, chatStream: null,
chatStreamStartedAt: null, chatStreamStartedAt: null,
chatThinkingLevel: null,
client: null,
connected: true,
lastError: null, lastError: null,
sessionKey: "main",
...overrides, ...overrides,
}; };
} }

View File

@ -34,10 +34,13 @@ export async function loadChatHistory(state: ChatState) {
state.chatLoading = true; state.chatLoading = true;
state.lastError = null; state.lastError = null;
try { try {
const res = await state.client.request("chat.history", { const res = await state.client.request<{ messages?: Array<unknown>; thinkingLevel?: string }>(
sessionKey: state.sessionKey, "chat.history",
limit: 200, {
}); sessionKey: state.sessionKey,
limit: 200,
},
);
state.chatMessages = Array.isArray(res.messages) ? res.messages : []; state.chatMessages = Array.isArray(res.messages) ? res.messages : [];
state.chatThinkingLevel = res.thinkingLevel ?? null; state.chatThinkingLevel = res.thinkingLevel ?? null;
} catch (err) { } catch (err) {

View File

@ -9,27 +9,30 @@ import {
function createState(): ConfigState { function createState(): ConfigState {
return { return {
client: null,
connected: false,
applySessionKey: "main", applySessionKey: "main",
client: null,
configActiveSection: null,
configActiveSubsection: null,
configApplying: false,
configForm: null,
configFormDirty: false,
configFormMode: "form",
configFormOriginal: null,
configIssues: [],
configLoading: false, configLoading: false,
configRaw: "", configRaw: "",
configRawOriginal: "", configRawOriginal: "",
configValid: null,
configIssues: [],
configSaving: false, configSaving: false,
configApplying: false,
updateRunning: false,
configSnapshot: null,
configSchema: null, configSchema: null,
configSchemaVersion: null,
configSchemaLoading: false, configSchemaLoading: false,
configSchemaVersion: null,
configSearchQuery: "",
configSnapshot: null,
configUiHints: {}, configUiHints: {},
configForm: null, configValid: null,
configFormOriginal: null, connected: false,
configFormDirty: false,
configFormMode: "form",
lastError: null, lastError: null,
updateRunning: false,
}; };
} }

View File

@ -41,7 +41,7 @@ export async function loadConfig(state: ConfigState) {
state.configLoading = true; state.configLoading = true;
state.lastError = null; state.lastError = null;
try { try {
const res = await state.client.request("config.get", {}); const res = await state.client.request<ConfigSnapshot>("config.get", {});
applyConfigSnapshot(state, res); applyConfigSnapshot(state, res);
} catch (err) { } catch (err) {
state.lastError = String(err); state.lastError = String(err);
@ -59,7 +59,7 @@ export async function loadConfigSchema(state: ConfigState) {
} }
state.configSchemaLoading = true; state.configSchemaLoading = true;
try { try {
const res = await state.client.request("config.schema", {}); const res = await state.client.request<ConfigSchemaResponse>("config.schema", {});
applyConfigSchema(state, res); applyConfigSchema(state, res);
} catch (err) { } catch (err) {
state.lastError = String(err); state.lastError = String(err);

View File

@ -21,7 +21,7 @@ export async function loadCronStatus(state: CronState) {
return; return;
} }
try { try {
const res = await state.client.request("cron.status", {}); const res = await state.client.request<CronStatus>("cron.status", {});
state.cronStatus = res; state.cronStatus = res;
} catch (err) { } catch (err) {
state.cronError = String(err); state.cronError = String(err);
@ -38,7 +38,7 @@ export async function loadCronJobs(state: CronState) {
state.cronLoading = true; state.cronLoading = true;
state.cronError = null; state.cronError = null;
try { try {
const res = await state.client.request("cron.list", { const res = await state.client.request<{ jobs?: Array<CronJob> }>("cron.list", {
includeDisabled: true, includeDisabled: true,
}); });
state.cronJobs = Array.isArray(res.jobs) ? res.jobs : []; state.cronJobs = Array.isArray(res.jobs) ? res.jobs : [];
@ -211,7 +211,7 @@ export async function loadCronRuns(state: CronState, jobId: string) {
return; return;
} }
try { try {
const res = await state.client.request("cron.runs", { const res = await state.client.request<{ entries?: Array<CronRunLogEntry> }>("cron.runs", {
id: jobId, id: jobId,
limit: 50, limit: 50,
}); });

View File

@ -57,7 +57,10 @@ export async function loadDevices(state: DevicesState, opts?: { quiet?: boolean
state.devicesError = null; state.devicesError = null;
} }
try { try {
const res = await state.client.request("device.pair.list", {}); const res = await state.client.request<{
pending?: Array<PendingDevice>;
paired?: Array<PendingDevice>;
}>("device.pair.list", {});
state.devicesList = { state.devicesList = {
pending: Array.isArray(res?.pending) ? res.pending : [], pending: Array.isArray(res?.pending) ? res.pending : [],
paired: Array.isArray(res?.paired) ? res.paired : [], paired: Array.isArray(res?.paired) ? res.paired : [],
@ -107,7 +110,12 @@ export async function rotateDeviceToken(
return; return;
} }
try { 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<string>;
}>("device.token.rotate", params);
if (res?.token) { if (res?.token) {
const identity = await loadOrCreateDeviceIdentity(); const identity = await loadOrCreateDeviceIdentity();
const role = res.role ?? params.role; const role = res.role ?? params.role;

View File

@ -94,7 +94,7 @@ export async function loadExecApprovals(
state.lastError = "Select a node before loading exec approvals."; state.lastError = "Select a node before loading exec approvals.";
return; return;
} }
const res = await state.client.request(rpc.method, rpc.params); const res = await state.client.request<ExecApprovalsSnapshot>(rpc.method, rpc.params);
applyExecApprovalsSnapshot(state, res); applyExecApprovalsSnapshot(state, res);
} catch (err) { } catch (err) {
state.lastError = String(err); state.lastError = String(err);

View File

@ -20,7 +20,7 @@ export async function loadNodes(state: NodesState, opts?: { quiet?: boolean }) {
state.lastError = null; state.lastError = null;
} }
try { try {
const res = await state.client.request("node.list", {}); const res = await state.client.request<{ nodes?: Record<string, unknown> }>("node.list", {});
state.nodes = Array.isArray(res.nodes) ? res.nodes : []; state.nodes = Array.isArray(res.nodes) ? res.nodes : [];
} catch (err) { } catch (err) {
if (!opts?.quiet) { if (!opts?.quiet) {

View File

@ -46,7 +46,7 @@ export async function loadSessions(
if (limit > 0) { if (limit > 0) {
params.limit = limit; params.limit = limit;
} }
const res = await state.client.request("sessions.list", params); const res = await state.client.request<SessionsListResult | undefined>("sessions.list", params);
if (res) { if (res) {
state.sessionsResult = res; state.sessionsResult = res;
} }

View File

@ -56,7 +56,7 @@ export async function loadSkills(state: SkillsState, options?: LoadSkillsOptions
state.skillsLoading = true; state.skillsLoading = true;
state.skillsError = null; state.skillsError = null;
try { try {
const res = await state.client.request("skills.status", {}); const res = await state.client.request<SkillStatusReport | undefined>("skills.status", {});
if (res) { if (res) {
state.skillsReport = res; state.skillsReport = res;
} }
@ -134,7 +134,7 @@ export async function installSkill(
state.skillsBusyKey = skillKey; state.skillsBusyKey = skillKey;
state.skillsError = null; state.skillsError = null;
try { try {
const result = await state.client.request("skills.install", { const result = await state.client.request<{ message?: string }>("skills.install", {
name, name,
installId, installId,
timeoutMs: 120000, timeoutMs: 120000,

View File

@ -42,7 +42,7 @@ function bytesToHex(bytes: Uint8Array): string {
} }
async function fingerprintPublicKey(publicKey: Uint8Array): Promise<string> { async function fingerprintPublicKey(publicKey: Uint8Array): Promise<string> {
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)); return bytesToHex(new Uint8Array(hash));
} }

View File

@ -5,7 +5,6 @@ import { truncateText } from "./format";
marked.setOptions({ marked.setOptions({
gfm: true, gfm: true,
breaks: true, breaks: true,
mangle: false,
}); });
const allowedTags = [ const allowedTags = [

View File

@ -116,7 +116,7 @@ describe("control UI routing", () => {
const app = mountApp("/chat"); const app = mountApp("/chat");
await app.updateComplete; await app.updateComplete;
const initialContainer = app.querySelector(".chat-thread"); const initialContainer: HTMLElement | null = app.querySelector(".chat-thread");
expect(initialContainer).not.toBeNull(); expect(initialContainer).not.toBeNull();
if (!initialContainer) { if (!initialContainer) {
return; return;

View File

@ -302,18 +302,20 @@ export type ConfigSchemaResponse = {
}; };
export type PresenceEntry = { export type PresenceEntry = {
instanceId?: string | null;
host?: string | null;
ip?: string | null;
version?: string | null;
platform?: string | null;
deviceFamily?: string | null; deviceFamily?: string | null;
modelIdentifier?: string | null; host?: string | null;
mode?: string | null; instanceId?: string | null;
ip?: string | null;
lastInputSeconds?: number | null; lastInputSeconds?: number | null;
mode?: string | null;
modelIdentifier?: string | null;
platform?: string | null;
reason?: string | null; reason?: string | null;
roles?: Array<string | null> | null;
scopes?: Array<string | null> | null;
text?: string | null; text?: string | null;
ts?: number | null; ts?: number | null;
version?: string | null;
}; };
export type GatewaySessionsDefaults = { export type GatewaySessionsDefaults = {

View File

@ -124,7 +124,7 @@ function renderChannel(key: ChannelKey, props: ChannelsProps, data: ChannelsChan
case "googlechat": case "googlechat":
return renderGoogleChatCard({ return renderGoogleChatCard({
props, props,
googlechat: data.googlechat, googleChat: data.googlechat,
accountCountLabel, accountCountLabel,
}); });
case "slack": case "slack":

View File

@ -194,7 +194,7 @@ describe("config view", () => {
if (!input) { if (!input) {
return; return;
} }
input.value = "gateway"; (input as HTMLInputElement).value = "gateway";
input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("input", { bubbles: true }));
expect(onSearchChange).toHaveBeenCalledWith("gateway"); expect(onSearchChange).toHaveBeenCalledWith("gateway");
}); });

View File

@ -3,8 +3,9 @@
"target": "ES2022", "target": "ES2022",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"], "lib": ["ES2023", "DOM", "DOM.Iterable"],
"strict": true, "strict": true,
"noEmit": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true, "skipLibCheck": true,
"types": ["vite/client"], "types": ["vite/client"],