diff --git a/apps/web/src/routes/_chat.settings.tsx b/apps/web/src/routes/_chat.settings.tsx index bee745c33..9a4043671 100644 --- a/apps/web/src/routes/_chat.settings.tsx +++ b/apps/web/src/routes/_chat.settings.tsx @@ -3,12 +3,18 @@ import { useQuery, useQueryClient } from "@tanstack/react-query"; import { CheckCircle2Icon, ChevronDownIcon, + CpuIcon, + GitBranchIcon, ImportIcon, Loader2Icon, + PaletteIcon, PlusIcon, RotateCcwIcon, SkipForwardIcon, + SmartphoneIcon, Undo2Icon, + VariableIcon, + WrenchIcon, XCircleIcon, XIcon, } from "lucide-react"; @@ -79,6 +85,34 @@ import { ensureNativeApi, readNativeApi } from "../nativeApi"; import { useStore } from "../store"; import { PairingLink } from "../components/mobile/PairingLink"; +// --------------------------------------------------------------------------- +// Settings navigation sections +// --------------------------------------------------------------------------- +type SettingsSectionId = "general" | "environment" | "git" | "models" | "mobile" | "advanced"; + +interface SettingsNavItem { + id: SettingsSectionId; + label: string; + icon: ReactNode; + hidden?: boolean; +} + +function useSettingsNavItems(): SettingsNavItem[] { + return [ + { id: "general", label: "General", icon: }, + { id: "environment", label: "Environment", icon: }, + { id: "git", label: "Git", icon: }, + { id: "models", label: "Models", icon: }, + { + id: "mobile", + label: "Mobile Companion", + icon: , + hidden: isMobileShell, + }, + { id: "advanced", label: "Advanced", icon: }, + ]; +} + const THEME_OPTIONS = [ { value: "system", @@ -259,19 +293,67 @@ const INSTALL_PROVIDER_SETTINGS: readonly InstallProviderSettings[] = [ }, ]; -function SettingsSection({ title, children }: { title: string; children: ReactNode }) { +function SettingsSection({ + title, + description, + children, + actions, +}: { + title: string; + description?: string; + children: ReactNode; + actions?: ReactNode; +}) { return ( -
-

- {title} -

-
+
+
+
+

{title}

+ {description ?

{description}

: null} +
+ {actions ?
{actions}
: null} +
+
{children}
); } +function SettingsNavSidebar({ + items, + activeSection, + onSelect, +}: { + items: SettingsNavItem[]; + activeSection: SettingsSectionId; + onSelect: (id: SettingsSectionId) => void; +}) { + return ( + + ); +} + function SettingsRow({ title, description, @@ -818,14 +900,23 @@ function SettingsRouteView() { setFontSizeOverrideState(null); } + const navItems = useSettingsNavItems(); + const [activeSection, setActiveSection] = useState("general"); + const activeSectionLabel = navItems.find((item) => item.id === activeSection)?.label ?? "General"; + return (
+ {/* Header */} {!isElectron && ( -
-
+
+
- Settings +
+ Settings + / + {activeSectionLabel} +
- } - /> - Import from tweakcn.com - -
- } - /> - { - clearRadiusOverride(); - setRadiusOverrideState(null); - }} - /> - ) : null - } - control={ -
- { - const value = Number.parseFloat(e.target.value); - setRadiusOverrideState(value); - setStoredRadiusOverride(value); - }} - className="h-1.5 w-24 cursor-pointer appearance-none rounded-full bg-muted accent-foreground sm:w-28" - aria-label="Border radius" + { + setColorTheme(DEFAULT_COLOR_THEME); + clearStoredCustomTheme(); + removeCustomTheme(); + }} + /> + ) : null + } + control={ +
+ + + setCustomThemeDialogOpen(true)} + aria-label="Import custom theme" + > + + + } + /> + Import from tweakcn.com + +
+ } /> - - {(radiusOverride ?? 0.625).toFixed(2)}rem - -
- } - /> - { - clearFontSizeOverride(); - setFontSizeOverrideState(null); - }} - /> - ) : null - } - control={ -
- { - const value = Number.parseFloat(e.target.value); - setFontSizeOverrideState(value); - setStoredFontSizeOverride(value); - }} - className="h-1.5 w-24 cursor-pointer appearance-none rounded-full bg-muted accent-foreground sm:w-28" - aria-label="Font size" + { + clearRadiusOverride(); + setRadiusOverrideState(null); + }} + /> + ) : null + } + control={ +
+ { + const value = Number.parseFloat(e.target.value); + setRadiusOverrideState(value); + setStoredRadiusOverride(value); + }} + className="h-1.5 w-24 cursor-pointer appearance-none rounded-full bg-muted accent-foreground sm:w-28" + aria-label="Border radius" + /> + + {(radiusOverride ?? 0.625).toFixed(2)}rem + +
+ } /> - - {fontSizeOverride ?? 12}px - -
- } - /> - { - clearFontOverride(); - setFontOverrideState(""); - }} - /> - ) : null - } - control={ - { - const value = e.target.value; - setFontOverrideState(value); - if (value.trim()) { - setStoredFontOverride(value); - } else { - clearFontOverride(); + { + clearFontSizeOverride(); + setFontSizeOverrideState(null); + }} + /> + ) : null } - }} - placeholder="e.g. Inter, sans-serif" - spellCheck={false} - aria-label="Font family override" - /> - } - /> - - { - applyCustomTheme(theme); - setColorTheme("custom"); - }} - /> - - setFontFamily("inter")} /> - ) : null - } - control={ - - } - /> + control={ +
+ { + const value = Number.parseFloat(e.target.value); + setFontSizeOverrideState(value); + setStoredFontSizeOverride(value); + }} + className="h-1.5 w-24 cursor-pointer appearance-none rounded-full bg-muted accent-foreground sm:w-28" + aria-label="Font size" + /> + + {fontSizeOverride ?? 12}px + +
+ } + /> - updateSettings({ sidebarOpacity: defaults.sidebarOpacity })} + { + clearFontOverride(); + setFontOverrideState(""); + }} + /> + ) : null + } + control={ + { + const value = e.target.value; + setFontOverrideState(value); + if (value.trim()) { + setStoredFontOverride(value); + } else { + clearFontOverride(); + } + }} + placeholder="e.g. Inter, sans-serif" + spellCheck={false} + aria-label="Font family override" + /> + } /> - ) : null - } - control={ -
- { - const value = Number(e.target.value) / 100; - updateSettings({ sidebarOpacity: value }); + + { + applyCustomTheme(theme); + setColorTheme("custom"); }} - className="h-1.5 w-24 cursor-pointer appearance-none rounded-full bg-muted accent-foreground sm:w-28" - aria-label="Sidebar opacity" /> - - {Math.round(settings.sidebarOpacity * 100)}% - -
- } - /> - - - - updateSettings({ - sidebarAccentProjectNames: defaults.sidebarAccentProjectNames, - }) + setFontFamily("inter")} /> + ) : null + } + control={ + } /> - ) : null - } - control={ - - updateSettings({ - sidebarAccentProjectNames: Boolean(checked), - }) - } - aria-label="Accent project names" - /> - } - /> - - updateSettings({ - sidebarAccentColorOverride: undefined, - }) + + updateSettings({ sidebarOpacity: defaults.sidebarOpacity }) + } + /> + ) : null + } + control={ +
+ { + const value = Number(e.target.value) / 100; + updateSettings({ sidebarOpacity: value }); + }} + className="h-1.5 w-24 cursor-pointer appearance-none rounded-full bg-muted accent-foreground sm:w-28" + aria-label="Sidebar opacity" + /> + + {Math.round(settings.sidebarOpacity * 100)}% + +
} /> - ) : null - } - control={ -
- - { - const value = e.target.value.trim(); - updateSettings({ - sidebarAccentColorOverride: value || undefined, - }); - }} - className="h-8 w-28 rounded-md border border-border bg-background px-2 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring sm:w-32" - aria-label="Accent color value" + + -
- } - /> - - updateSettings({ - sidebarAccentBgColorOverride: undefined, - }) + + updateSettings({ + sidebarAccentProjectNames: defaults.sidebarAccentProjectNames, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + sidebarAccentProjectNames: Boolean(checked), + }) + } + aria-label="Accent project names" + /> } /> - ) : null - } - control={ -
- - { - const value = e.target.value.trim(); - updateSettings({ - sidebarAccentBgColorOverride: value || undefined, - }); - }} - className="h-8 w-28 rounded-md border border-border bg-background px-2 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring sm:w-32" - aria-label="Accent background color value" - /> -
- } - /> - - updateSettings({ - prReviewRequestChangesTone: DEFAULT_PR_REVIEW_REQUEST_CHANGES_TONE, - }) + + updateSettings({ + sidebarAccentColorOverride: undefined, + }) + } + /> + ) : null + } + control={ +
+ + { + const value = e.target.value.trim(); + updateSettings({ + sidebarAccentColorOverride: value || undefined, + }); + }} + className="h-8 w-28 rounded-md border border-border bg-background px-2 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring sm:w-32" + aria-label="Accent color value" + /> +
} /> - ) : null - } - control={ - - } - /> + control={ +
+ + { + const value = e.target.value.trim(); + updateSettings({ + sidebarAccentBgColorOverride: value || undefined, + }); + }} + className="h-8 w-28 rounded-md border border-border bg-background px-2 text-xs text-foreground placeholder:text-muted-foreground/60 focus:outline-none focus:ring-1 focus:ring-ring sm:w-32" + aria-label="Accent background color value" + /> +
+ } + /> - - updateSettings({ - timestampFormat: defaults.timestampFormat, - }) + + updateSettings({ + prReviewRequestChangesTone: DEFAULT_PR_REVIEW_REQUEST_CHANGES_TONE, + }) + } + /> + ) : null + } + control={ + } /> - ) : null - } - control={ - - } - /> + control={ + + } + /> - - updateSettings({ - showStitchBorder: defaults.showStitchBorder, - }) + + updateSettings({ + showStitchBorder: defaults.showStitchBorder, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + showStitchBorder: Boolean(checked), + }) + } + aria-label="Show stitch border" + /> } /> - ) : null - } - control={ - - updateSettings({ - showStitchBorder: Boolean(checked), - }) - } - aria-label="Show stitch border" - /> - } - /> - - updateSettings({ - enableAssistantStreaming: defaults.enableAssistantStreaming, - }) + + updateSettings({ + enableAssistantStreaming: defaults.enableAssistantStreaming, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + enableAssistantStreaming: Boolean(checked), + }) + } + aria-label="Stream assistant messages" + /> } /> - ) : null - } - control={ - - updateSettings({ - enableAssistantStreaming: Boolean(checked), - }) - } - aria-label="Stream assistant messages" - /> - } - /> - - updateSettings({ - showReasoningContent: defaults.showReasoningContent, - }) + + updateSettings({ + showReasoningContent: defaults.showReasoningContent, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + showReasoningContent: Boolean(checked), + }) + } + aria-label="Show reasoning content in work log" + /> } /> - ) : null - } - control={ - - updateSettings({ - showReasoningContent: Boolean(checked), - }) - } - aria-label="Show reasoning content in work log" - /> - } - /> - - updateSettings({ - showAuthFailuresAsErrors: defaults.showAuthFailuresAsErrors, - }) + + updateSettings({ + showAuthFailuresAsErrors: defaults.showAuthFailuresAsErrors, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + showAuthFailuresAsErrors: Boolean(checked), + }) + } + aria-label="Show authentication failures as thread errors" + /> } /> - ) : null - } - control={ - - updateSettings({ - showAuthFailuresAsErrors: Boolean(checked), - }) - } - aria-label="Show authentication failures as thread errors" - /> - } - /> - - updateSettings({ - openLinksExternally: defaults.openLinksExternally, - }) + + updateSettings({ + openLinksExternally: defaults.openLinksExternally, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + openLinksExternally: Boolean(checked), + }) + } + aria-label="Open links externally" + /> } /> - ) : null - } - control={ - - updateSettings({ - openLinksExternally: Boolean(checked), - }) - } - aria-label="Open links externally" - /> - } - /> - - updateSettings({ - codeViewerAutosave: defaults.codeViewerAutosave, - }) + + updateSettings({ + codeViewerAutosave: defaults.codeViewerAutosave, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + codeViewerAutosave: Boolean(checked), + }) + } + aria-label="Enable code preview autosave" + /> } /> - ) : null - } - control={ - - updateSettings({ - codeViewerAutosave: Boolean(checked), - }) - } - aria-label="Enable code preview autosave" - /> - } - /> - - updateSettings({ - defaultThreadEnvMode: defaults.defaultThreadEnvMode, - }) + + updateSettings({ + defaultThreadEnvMode: defaults.defaultThreadEnvMode, + }) + } + /> + ) : null + } + control={ + } /> - ) : null - } - control={ - - } - /> - - updateSettings({ - autoUpdateWorktreeBaseBranch: defaults.autoUpdateWorktreeBaseBranch, - }) + + updateSettings({ + autoUpdateWorktreeBaseBranch: defaults.autoUpdateWorktreeBaseBranch, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + autoUpdateWorktreeBaseBranch: Boolean(checked), + }) + } + aria-label="Refresh base branch before creating new worktrees" + /> } /> - ) : null - } - control={ - - updateSettings({ - autoUpdateWorktreeBaseBranch: Boolean(checked), - }) - } - aria-label="Refresh base branch before creating new worktrees" - /> - } - /> - - updateSettings({ - confirmThreadDelete: defaults.confirmThreadDelete, - }) + + updateSettings({ + confirmThreadDelete: defaults.confirmThreadDelete, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + confirmThreadDelete: Boolean(checked), + }) + } + aria-label="Confirm thread deletion" + /> } /> - ) : null - } - control={ - - updateSettings({ - confirmThreadDelete: Boolean(checked), - }) - } - aria-label="Confirm thread deletion" - /> - } - /> - - updateSettings({ - autoDeleteMergedThreads: defaults.autoDeleteMergedThreads, - }) + + updateSettings({ + autoDeleteMergedThreads: defaults.autoDeleteMergedThreads, + }) + } + /> + ) : null + } + control={ + + updateSettings({ + autoDeleteMergedThreads: Boolean(checked), + }) + } + aria-label="Auto-delete merged threads" + /> } /> - ) : null - } - control={ - - updateSettings({ - autoDeleteMergedThreads: Boolean(checked), - }) - } - aria-label="Auto-delete merged threads" - /> - } - /> - {settings.autoDeleteMergedThreads ? ( - - updateSettings({ - autoDeleteMergedThreadsDelayMinutes: - defaults.autoDeleteMergedThreadsDelayMinutes, - }) + {settings.autoDeleteMergedThreads ? ( + + updateSettings({ + autoDeleteMergedThreadsDelayMinutes: + defaults.autoDeleteMergedThreadsDelayMinutes, + }) + } + /> + ) : null + } + control={ + } /> - ) : null - } - control={ - - } - /> - ) : null} - - - - - Failed to load saved variables:{" "} - {getErrorMessage(globalEnvironmentVariablesQuery.error)} - - ) : globalEnvironmentVariablesQuery.isFetching ? ( - Loading saved variables... - ) : globalEnvironmentVariablesQuery.data?.entries.length ? ( - - {globalEnvironmentVariablesQuery.data.entries.length} saved variables - - ) : ( - No global variables saved yet. - ) - } - > - - - - - {selectedProject.name} · {selectedProject.cwd} - - ) : ( - Open a project to edit project variables. - ) - } - control={ - projects.length > 0 ? ( - { + setSelectedProjectId(value as ProjectId); + }} + > + + + {selectedProject ? selectedProject.name : "Select project"} + + + + {projects.map((project) => ( + +
+ {project.name} + + {project.cwd} + +
+
+ ))} +
+ + ) : ( + + No projects available. + + ) + } > - - - {selectedProject ? selectedProject.name : "Select project"} - - - - {projects.map((project) => ( - -
- {project.name} - - {project.cwd} - -
-
- ))} -
- - ) : ( - No projects available. - ) - } - > - -
-
- - - - updateSettings({ - rebaseBeforeCommit: defaults.rebaseBeforeCommit, - }) + + + + )} + + {activeSection === "git" && ( + + + updateSettings({ + rebaseBeforeCommit: defaults.rebaseBeforeCommit, + }) + } + /> + ) : null } - /> - ) : null - } - control={ - - updateSettings({ - rebaseBeforeCommit: Boolean(checked), - }) - } - aria-label="Rebase onto the default branch before committing" - /> - } - /> - - - - - updateSettings({ - textGenerationModel: defaults.textGenerationModel, - }) + control={ + + updateSettings({ + rebaseBeforeCommit: Boolean(checked), + }) + } + aria-label="Rebase onto the default branch before committing" + /> } /> - ) : null - } - control={ - - } - /> + + )} - 0 ? ( - { - updateSettings({ - customCodexModels: defaults.customCodexModels, - customClaudeModels: defaults.customClaudeModels, - }); - setCustomModelErrorByProvider({}); - setShowAllCustomModels(false); - }} - /> - ) : null - } - > -
-
- { + if (!value) return; + updateSettings({ + textGenerationModel: value, + }); + }} + > + - {providerSettings.title} - - ))} - - - { - const value = event.target.value; - setCustomModelInputByProvider((existing) => ({ - ...existing, - [selectedCustomModelProvider]: value, - })); - if (selectedCustomModelError) { - setCustomModelErrorByProvider((existing) => ({ - ...existing, - [selectedCustomModelProvider]: null, - })); - } - }} - onKeyDown={(event) => { - if (event.key !== "Enter") return; - event.preventDefault(); - addCustomModel(selectedCustomModelProvider); - }} - placeholder={selectedCustomModelProviderSettings.example} - spellCheck={false} + {selectedGitTextGenerationModelLabel} + + + {gitTextGenerationModelOptions.map((option) => ( + + {option.name} + + ))} + + + } /> - -
- - {selectedCustomModelError ? ( -

{selectedCustomModelError}

- ) : null} - - {totalCustomModels > 0 ? ( -
-
- {visibleCustomModelRows.map((row) => ( -
+
+ + { + const value = event.target.value; + setCustomModelInputByProvider((existing) => ({ + ...existing, + [selectedCustomModelProvider]: value, + })); + if (selectedCustomModelError) { + setCustomModelErrorByProvider((existing) => ({ ...existing, - [providerSettings.provider]: !existing[providerSettings.provider], - })) + [selectedCustomModelProvider]: null, + })); } - > - - {providerSettings.title} - - {isDirty ? ( - Custom - ) : null} - - - - -
-
-
+ + {selectedCustomModelError ? ( +

+ {selectedCustomModelError} +

+ ) : null} + + {totalCustomModels > 0 ? ( +
+
+ {visibleCustomModelRows.map((row) => ( +
+ + {row.providerTitle} + + + {row.slug} + +
-
- + ))} +
+ + {savedCustomModelRows.length > 5 ? ( + + ) : null}
- - ); - })} -
-
- - - - updateSettings({ - openclawGatewayUrl: defaults.openclawGatewayUrl, - openclawPassword: defaults.openclawPassword, - }) + ) : null} +
+ + + )} + + {activeSection === "mobile" && !isMobileShell && ( + + +
+ +
+
+
+ )} + + {activeSection === "advanced" && ( + + { + updateSettings({ + claudeBinaryPath: defaults.claudeBinaryPath, + codexBinaryPath: defaults.codexBinaryPath, + codexHomePath: defaults.codexHomePath, + }); + setOpenInstallProviders({ + codex: false, + claudeAgent: false, + openclaw: false, + }); + }} + /> + ) : null } - /> - ) : null - } - > -
- - - - {/* Test Connection Button */} -
- -
- - {/* Debug / Results Panel */} - {openclawTestResult && ( -
- {/* Overall status header */} -
- {openclawTestResult.success ? ( - - ) : ( - - )} - - {openclawTestResult.success - ? "Connection successful" - : "Connection failed"} - - - {openclawTestResult.totalDurationMs}ms total - - +
+
+ {INSTALL_PROVIDER_SETTINGS.map((providerSettings) => { + const isOpen = openInstallProviders[providerSettings.provider]; + const isDirty = + providerSettings.provider === "codex" + ? settings.codexBinaryPath !== defaults.codexBinaryPath || + settings.codexHomePath !== defaults.codexHomePath + : settings.claudeBinaryPath !== defaults.claudeBinaryPath; + const binaryPathValue = + providerSettings.binaryPathKey === "claudeBinaryPath" + ? claudeBinaryPath + : codexBinaryPath; + + return ( + + setOpenInstallProviders((existing) => ({ + ...existing, + [providerSettings.provider]: open, + })) + } + > +
+ + + +
+
+ + + {providerSettings.homePathKey ? ( + + ) : null} +
+
+
+
+
+ ); + })} +
+ + + + updateSettings({ + openclawGatewayUrl: defaults.openclawGatewayUrl, + openclawPassword: defaults.openclawPassword, + }) + } + /> + ) : null + } + > +
+ + - {/* Step-by-step results */} - {openclawTestResult.steps.length > 0 && ( -
- {openclawTestResult.steps.map((step) => ( -
- {step.status === "pass" && ( - - )} - {step.status === "fail" && ( - - )} - {step.status === "skip" && ( - + {/* Test Connection Button */} +
+ +
+ + {/* Debug / Results Panel */} + {openclawTestResult && ( +
+ {/* Overall status header */} +
+ {openclawTestResult.success ? ( + + ) : ( + )} -
-
- {step.name} - - {step.durationMs}ms - -
- {step.detail && ( - - {step.detail} - + + > + {openclawTestResult.success + ? "Connection successful" + : "Connection failed"} + + + {openclawTestResult.totalDurationMs}ms total + +
- ))} -
- )} - {/* Server info */} - {openclawTestResult.serverInfo && ( -
- - Server Info - -
- {openclawTestResult.serverInfo.version && ( -
- Version:{" "} - - {openclawTestResult.serverInfo.version} - -
- )} - {openclawTestResult.serverInfo.sessionId && ( -
- Session:{" "} - - {openclawTestResult.serverInfo.sessionId} - + {/* Step-by-step results */} + {openclawTestResult.steps.length > 0 && ( +
+ {openclawTestResult.steps.map((step) => ( +
+ {step.status === "pass" && ( + + )} + {step.status === "fail" && ( + + )} + {step.status === "skip" && ( + + )} +
+
+ + {step.name} + + + {step.durationMs}ms + +
+ {step.detail && ( + + {step.detail} + + )} +
+
+ ))}
)} -
-
- )} - {openclawTestResult.diagnostics && ( -
- - Debugging Context - -
- {openclawTestResult.diagnostics.normalizedUrl && ( -
- Endpoint:{" "} - - {openclawTestResult.diagnostics.normalizedUrl} + {/* Server info */} + {openclawTestResult.serverInfo && ( +
+ + Server Info -
- )} - {openclawTestResult.diagnostics.hostKind && ( -
- Host type:{" "} - - {describeOpenclawGatewayHostKind( - openclawTestResult.diagnostics.hostKind, +
+ {openclawTestResult.serverInfo.version && ( +
+ Version:{" "} + + {openclawTestResult.serverInfo.version} + +
)} - -
- )} - {openclawTestResult.diagnostics.resolvedAddresses.length > 0 && ( -
- Resolved:{" "} - - {openclawTestResult.diagnostics.resolvedAddresses.join(", ")} - -
- )} - {describeOpenclawGatewayHealthStatus(openclawTestResult) && ( -
- Health probe:{" "} - - {describeOpenclawGatewayHealthStatus(openclawTestResult)} - - {openclawTestResult.diagnostics.healthUrl && ( - <> - {" "} - at{" "} - - {openclawTestResult.diagnostics.healthUrl} - - - )} -
- )} - {openclawTestResult.diagnostics.socketCloseCode !== undefined && ( -
- Socket close:{" "} - - {openclawTestResult.diagnostics.socketCloseCode} - {openclawTestResult.diagnostics.socketCloseReason - ? ` (${openclawTestResult.diagnostics.socketCloseReason})` - : ""} - -
- )} - {openclawTestResult.diagnostics.socketError && ( -
- Socket error:{" "} - - {openclawTestResult.diagnostics.socketError} - + {openclawTestResult.serverInfo.sessionId && ( +
+ Session:{" "} + + {openclawTestResult.serverInfo.sessionId} + +
+ )} +
)} - {openclawTestResult.diagnostics.observedNotifications.length > 0 && ( -
- Gateway notifications:{" "} - - {openclawTestResult.diagnostics.observedNotifications.join(", ")} + + {openclawTestResult.diagnostics && ( +
+ + Debugging Context +
+ {openclawTestResult.diagnostics.normalizedUrl && ( +
+ Endpoint:{" "} + + {openclawTestResult.diagnostics.normalizedUrl} + +
+ )} + {openclawTestResult.diagnostics.hostKind && ( +
+ Host type:{" "} + + {describeOpenclawGatewayHostKind( + openclawTestResult.diagnostics.hostKind, + )} + +
+ )} + {openclawTestResult.diagnostics.resolvedAddresses.length > 0 && ( +
+ Resolved:{" "} + + {openclawTestResult.diagnostics.resolvedAddresses.join( + ", ", + )} + +
+ )} + {describeOpenclawGatewayHealthStatus(openclawTestResult) && ( +
+ Health probe:{" "} + + {describeOpenclawGatewayHealthStatus(openclawTestResult)} + + {openclawTestResult.diagnostics.healthUrl && ( + <> + {" "} + at{" "} + + {openclawTestResult.diagnostics.healthUrl} + + + )} +
+ )} + {openclawTestResult.diagnostics.socketCloseCode !== undefined && ( +
+ Socket close:{" "} + + {openclawTestResult.diagnostics.socketCloseCode} + {openclawTestResult.diagnostics.socketCloseReason + ? ` (${openclawTestResult.diagnostics.socketCloseReason})` + : ""} + +
+ )} + {openclawTestResult.diagnostics.socketError && ( +
+ Socket error:{" "} + + {openclawTestResult.diagnostics.socketError} + +
+ )} + {openclawTestResult.diagnostics.observedNotifications.length > + 0 && ( +
+ Gateway notifications:{" "} + + {openclawTestResult.diagnostics.observedNotifications.join( + ", ", + )} + +
+ )} +
)} -
-
- )} - {openclawTestResult.diagnostics && - openclawTestResult.diagnostics.hints.length > 0 && ( -
- - Troubleshooting - -
    - {openclawTestResult.diagnostics.hints.map((hint) => ( -
  • - - {hint} -
  • - ))} -
-
- )} + {openclawTestResult.diagnostics && + openclawTestResult.diagnostics.hints.length > 0 && ( +
+ + Troubleshooting + +
    + {openclawTestResult.diagnostics.hints.map((hint) => ( +
  • + + {hint} +
  • + ))} +
+
+ )} - {/* Error summary */} - {openclawTestResult.error && - !openclawTestResult.steps.some((s) => s.status === "fail") && ( -
- {openclawTestResult.error} + {/* Error summary */} + {openclawTestResult.error && + !openclawTestResult.steps.some((s) => s.status === "fail") && ( +
+ {openclawTestResult.error} +
+ )}
)} -
- )} -
- - - - - {keybindingsConfigPath ?? "Resolving keybindings path..."} - - {openKeybindingsError ? ( - {openKeybindingsError} - ) : ( - Opens in your preferred editor. - )} - - } - control={ - - } - /> +
+ - - - {serverConfigQuery.data?.buildInfo ? ( - - ) : null} -
- } - /> - + + + {keybindingsConfigPath ?? "Resolving keybindings path..."} + + {openKeybindingsError ? ( + + {openKeybindingsError} + + ) : ( + Opens in your preferred editor. + )} + + } + control={ + + } + /> + + + + {serverConfigQuery.data?.buildInfo ? ( + + ) : null} +
+ } + /> + + )} +
+