import {
GATEWAY_EVENT_UPDATE_AVAILABLE,
type GatewayUpdateAvailableEventPayload,
} from "../../../src/gateway/events.js"; import { ConnectErrorDetailCodes } from "../../../src/gateway/protocol/connect-error-details.js"; import {
CHAT_SESSIONS_ACTIVE_MINUTES,
clearPendingQueueItemsForRun,
flushChatQueueForEvent,
} from "./app-chat.ts"; import type { EventLogEntry } from "./app-events.ts"; import {
applySettings,
loadCron,
refreshActiveTab,
setLastActiveSessionKey,
} from "./app-settings.ts"; import { handleAgentEvent, resetToolStream, type AgentEventPayload } from "./app-tool-stream.ts"; import { shouldReloadHistoryForFinalEvent } from "./chat-event-reload.ts"; import { parseChatSideResult, type ChatSideResult } from "./chat/side-result.ts"; import { formatConnectError } from "./connect-error.ts"; import { loadAgents, type AgentsState } from "./controllers/agents.ts"; import {
loadAssistantIdentity,
type AssistantIdentityState,
} from "./controllers/assistant-identity.ts"; import {
loadChatHistory,
handleChatEvent,
type ChatEventPayload,
type ChatState,
} from "./controllers/chat.ts"; import { loadControlUiBootstrapConfig } from "./controllers/control-ui-bootstrap.ts"; import { loadDevices, type DevicesState } from "./controllers/devices.ts"; import type { ExecApprovalRequest } from "./controllers/exec-approval.ts"; import {
addExecApproval,
parseExecApprovalRequested,
parseExecApprovalResolved,
parsePluginApprovalRequested,
pruneExecApprovalQueue,
removeExecApproval,
} from "./controllers/exec-approval.ts"; import { loadHealthState, type HealthState } from "./controllers/health.ts"; import { loadNodes, type NodesState } from "./controllers/nodes.ts"; import {
applySessionsChangedEvent,
loadSessions,
subscribeSessions,
type SessionsState,
} from "./controllers/sessions.ts"; import {
resolveGatewayErrorDetailCode,
type GatewayEventFrame,
type GatewayHelloOk,
} from "./gateway.ts"; import { GatewayBrowserClient } from "./gateway.ts"; import type { Tab } from "./navigation.ts"; import type { UiSettings } from "./storage.ts"; import type {
AgentsListResult,
PresenceEntry,
HealthSummary,
StatusSummary,
UpdateAvailable,
} from "./types.ts";
function isGenericBrowserFetchFailure(message: string): boolean { return /^(?:typeerror:\s*)?(?:fetch failed|failed to fetch)$/i.test(message.trim());
}
function normalizeSessionKeyForDefaults(
value: string | undefined,
defaults: SessionDefaultsSnapshot,
): string { const raw = (value ?? "").trim(); const mainSessionKey = defaults.mainSessionKey?.trim(); if (!mainSessionKey) { return raw;
} if (!raw) { return mainSessionKey;
} const mainKey = defaults.mainKey?.trim() || "main"; const defaultAgentId = defaults.defaultAgentId?.trim(); const isAlias =
raw === "main" ||
raw === mainKey ||
(defaultAgentId &&
(raw === `agent:${defaultAgentId}:main` || raw === `agent:${defaultAgentId}:${mainKey}`)); return isAlias ? mainSessionKey : raw;
}
function applySessionDefaults(host: GatewayHost, defaults?: SessionDefaultsSnapshot) { if (!defaults?.mainSessionKey) { return;
}
// Detect if user has already selected a specific session (not an alias like "main"). // If normalization doesn't change the value, it's a user-selected session. const normalizedSessionKey = normalizeSessionKeyForDefaults(host.sessionKey, defaults); const isUserSelectedSession = normalizedSessionKey === host.sessionKey;
if (isUserSelectedSession) { // User has selected a specific session; preserve their choice // Only normalize lastActiveSessionKey, don't override current sessionKey const resolvedLastActiveSessionKey = normalizeSessionKeyForDefaults(
host.settings.lastActiveSessionKey,
defaults,
); if (resolvedLastActiveSessionKey !== host.settings.lastActiveSessionKey) {
applySettings(host as unknown as Parameters<typeof applySettings>[0], {
...host.settings,
lastActiveSessionKey: resolvedLastActiveSessionKey,
});
} return; // Keep user's session selection
} const resolvedSessionKey = normalizeSessionKeyForDefaults(host.sessionKey, defaults); const resolvedSettingsSessionKey = normalizeSessionKeyForDefaults(
host.settings.sessionKey,
defaults,
); const resolvedLastActiveSessionKey = normalizeSessionKeyForDefaults(
host.settings.lastActiveSessionKey,
defaults,
); const nextSessionKey = resolvedSessionKey || resolvedSettingsSessionKey || host.sessionKey; const nextSettings = {
...host.settings,
sessionKey: resolvedSettingsSessionKey || nextSessionKey,
lastActiveSessionKey: resolvedLastActiveSessionKey || nextSessionKey,
}; const shouldUpdateSettings =
nextSettings.sessionKey !== host.settings.sessionKey ||
nextSettings.lastActiveSessionKey !== host.settings.lastActiveSessionKey; if (nextSessionKey !== host.sessionKey) {
host.sessionKey = nextSessionKey;
} if (shouldUpdateSettings) {
applySettings(host as unknown as Parameters<typeof applySettings>[0], nextSettings);
}
}
function handleTerminalChatEvent(
host: GatewayHost,
payload: ChatEventPayload | undefined,
state: ReturnType<typeof handleChatEvent>,
): boolean { if (state !== "final" && state !== "error" && state !== "aborted") { returnfalse;
} // Check if tool events were seen before resetting (resetToolStream clears toolStreamOrder). const toolHost = host as unknown as Parameters<typeof resetToolStream>[0]; const hadToolEvents = toolHost.toolStreamOrder.length > 0; const flushQueue = () => void flushChatQueueForEvent(host as unknown as Parameters<typeof flushChatQueueForEvent>[0]);
clearPendingQueueItemsForRun(
host as unknown as Parameters<typeof clearPendingQueueItemsForRun>[0],
payload?.runId,
); const runId = payload?.runId; if (runId && host.refreshSessionsAfterChat.has(runId)) {
host.refreshSessionsAfterChat.delete(runId); if (state === "final") { void loadSessions(host as unknown as SessionsState, {
activeMinutes: CHAT_SESSIONS_ACTIVE_MINUTES,
});
}
} // Reload history when tools were used so the persisted tool results // replace the now-cleared streaming state. if (hadToolEvents && state === "final") { const completedRunId = runId ?? null; void loadChatHistory(host as unknown as ChatState).finally(() => { if (completedRunId && host.chatRunId && host.chatRunId !== completedRunId) { return;
}
resetToolStream(toolHost);
flushQueue();
}); returntrue;
}
resetToolStream(toolHost);
flushQueue(); returnfalse;
}
function handleChatGatewayEvent(host: GatewayHost, payload: ChatEventPayload | undefined) { if (payload?.sessionKey) {
setLastActiveSessionKey(
host as unknown as Parameters<typeof setLastActiveSessionKey>[0],
payload.sessionKey,
);
} const sideResultHost = host as GatewayHostWithSideResults; const isTrackedSideResultTerminalEvent =
isTerminalChatState(payload?.state) && typeof payload?.runId === "string" &&
sideResultHost.chatSideResultTerminalRuns?.has(payload.runId) === true; if (isTrackedSideResultTerminalEvent && payload?.runId) {
sideResultHost.chatSideResultTerminalRuns?.delete(payload.runId); return;
} const state = handleChatEvent(host as unknown as ChatState, payload); const historyReloaded = handleTerminalChatEvent(host, payload, state); const deferredReloadHost = host as GatewayHostWithDeferredSessionMessageReload; const deferredSessionKey = deferredReloadHost.pendingSessionMessageReloadSessionKey?.trim(); const payloadSessionKey = payload?.sessionKey?.trim(); const shouldReplayDeferredSessionMessageReload = Boolean(
deferredSessionKey &&
payloadSessionKey &&
deferredSessionKey === payloadSessionKey &&
isTerminalChatState(state) &&
payloadSessionKey === host.sessionKey &&
!host.chatRunId,
); if (deferredSessionKey && payloadSessionKey && deferredSessionKey === payloadSessionKey) {
deferredReloadHost.pendingSessionMessageReloadSessionKey = null;
} if (state === "final" && !historyReloaded && shouldReloadHistoryForFinalEvent(payload)) { void loadChatHistory(host as unknown as ChatState); return;
} if (shouldReplayDeferredSessionMessageReload && !historyReloaded) { void loadChatHistory(host as unknown as ChatState);
}
}
function handleSessionMessageGatewayEvent(
host: GatewayHost,
payload: { sessionKey?: string } | undefined,
) {
applySessionsChangedEvent(host as unknown as SessionsState, payload); const deferredReloadHost = host as GatewayHostWithDeferredSessionMessageReload; const sessionKey = payload?.sessionKey?.trim(); if (!sessionKey || sessionKey !== host.sessionKey) { return;
} // Skip history reload while a chat run is active. The chat event handler // manages streaming state and appends the final assistant message. Reloading // history mid-run races with the optimistic user-message update and resets // chatStream, which delays the user message card from appearing until the // first LLM delta arrives. if (host.chatRunId) {
deferredReloadHost.pendingSessionMessageReloadSessionKey = sessionKey; return;
}
deferredReloadHost.pendingSessionMessageReloadSessionKey = null; void loadChatHistory(host as unknown as ChatState);
}
if (evt.event === "agent") { if (host.onboarding) { return;
}
handleAgentEvent(
host as unknown as Parameters<typeof handleAgentEvent>[0],
evt.payload as AgentEventPayload | undefined,
); return;
}
if (evt.event === "chat") {
handleChatGatewayEvent(host, evt.payload as ChatEventPayload | undefined); return;
}
if (evt.event === "chat.side_result") { const sideResult = parseChatSideResult(evt.payload); if (!sideResult || sideResult.sessionKey !== host.sessionKey) { return;
} const sideResultHost = host as GatewayHostWithSideResults;
sideResultHost.chatSideResult = sideResult;
sideResultHost.chatSideResultTerminalRuns?.add(sideResult.runId); return;
}
if (evt.event === "session.message") {
handleSessionMessageGatewayEvent(host, evt.payload as { sessionKey?: string } | undefined); return;
}
if (evt.event === "sessions.changed") {
applySessionsChangedEvent(host as unknown as SessionsState, evt.payload); void loadSessions(host as unknown as SessionsState); return;
}
if (evt.event === "cron" && host.tab === "cron") { void loadCron(host as unknown as Parameters<typeof loadCron>[0]);
}
if (evt.event === "device.pair.requested" || evt.event === "device.pair.resolved") { void loadDevices(host as unknown as DevicesState, { quiet: true });
}
if (evt.event === "exec.approval.requested") {
enqueueApprovalRequest(host, parseExecApprovalRequested(evt.payload)); return;
}
if (evt.event === "exec.approval.resolved") {
removeResolvedApprovalRequest(host, evt.payload); return;
}
if (evt.event === "plugin.approval.requested") {
enqueueApprovalRequest(host, parsePluginApprovalRequested(evt.payload)); return;
}
if (evt.event === "plugin.approval.resolved") {
removeResolvedApprovalRequest(host, evt.payload); return;
}
if (evt.event === GATEWAY_EVENT_UPDATE_AVAILABLE) { const payload = evt.payload as GatewayUpdateAvailableEventPayload | undefined;
host.updateAvailable = payload?.updateAvailable ?? null;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.