chore: Fix all TypeScript errors in ui.

This commit is contained in:
cpojer
2026-02-03 22:35:55 +09:00
parent be4f7ef361
commit 27677dd8bd
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 { generateUUID } from "./uuid";
type ChatHost = {
export type ChatHost = {
connected: boolean;
chatMessage: string;
chatAttachments: ChatAttachment[];

View File

@@ -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<typeof refreshChat>[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),
});
}

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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<AgentsListResult>("agents.list", {});
if (res) {
state.agentsList = res;
const selected = state.agentsSelectedId;

View File

@@ -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<ChannelsStatusSnapshot | null>("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) {

View File

@@ -3,18 +3,19 @@ import { handleChatEvent, type ChatEventPayload, type ChatState } from "./chat";
function createState(overrides: Partial<ChatState> = {}): 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,
};
}

View File

@@ -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<unknown>; thinkingLevel?: string }>(
"chat.history",
{
sessionKey: state.sessionKey,
limit: 200,
},
);
state.chatMessages = Array.isArray(res.messages) ? res.messages : [];
state.chatThinkingLevel = res.thinkingLevel ?? null;
} catch (err) {

View File

@@ -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,
};
}

View File

@@ -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<ConfigSnapshot>("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<ConfigSchemaResponse>("config.schema", {});
applyConfigSchema(state, res);
} catch (err) {
state.lastError = String(err);

View File

@@ -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<CronStatus>("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<CronJob> }>("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<CronRunLogEntry> }>("cron.runs", {
id: jobId,
limit: 50,
});

View File

@@ -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<PendingDevice>;
paired?: Array<PendingDevice>;
}>("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<string>;
}>("device.token.rotate", params);
if (res?.token) {
const identity = await loadOrCreateDeviceIdentity();
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.";
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);
} catch (err) {
state.lastError = String(err);

View File

@@ -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<string, unknown> }>("node.list", {});
state.nodes = Array.isArray(res.nodes) ? res.nodes : [];
} catch (err) {
if (!opts?.quiet) {

View File

@@ -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<SessionsListResult | undefined>("sessions.list", params);
if (res) {
state.sessionsResult = res;
}

View File

@@ -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<SkillStatusReport | undefined>("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,

View File

@@ -42,7 +42,7 @@ function bytesToHex(bytes: Uint8Array): 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));
}

View File

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

View File

@@ -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;

View File

@@ -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<string | null> | null;
scopes?: Array<string | null> | null;
text?: string | null;
ts?: number | null;
version?: string | null;
};
export type GatewaySessionsDefaults = {

View File

@@ -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":

View File

@@ -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");
});

View File

@@ -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"],