fix: improve startup readiness performance

This commit is contained in:
Bohdan Triapitsyn
2026-06-05 15:09:24 +03:00
parent 0b881fec8e
commit e192359da9
32 changed files with 502 additions and 110 deletions
+10 -2
View File
@@ -56,6 +56,7 @@ import { SyncAppEffects } from '@/apps/AppEffects';
import { useAppFontEffects } from '@/apps/useAppFontEffects';
import { resetStreamingState } from '@/sync/streaming';
import { OpenCodeUpdateToast } from '@/components/update/OpenCodeUpdateToast';
import { markStartupTrace, startupTraceEnabled } from '@/lib/startupTrace';
// Lazy-loaded heavy views — loaded on demand to reduce initial bundle size.
const OnboardingScreen = lazyWithChunkRecovery(() =>
@@ -201,6 +202,13 @@ const EmbeddedSessionChatContent: React.FC<{
};
function App({ apis }: AppProps) {
React.useEffect(() => {
markStartupTrace('App:mounted');
if (startupTraceEnabled()) {
console.info('[startup-trace] enabled. Run console.table(window.__OPENCHAMBER_STARTUP_TRACE__) after startup.');
}
}, []);
const initializeApp = useConfigStore((s) => s.initializeApp);
const isInitialized = useConfigStore((s) => s.isInitialized);
const isConnected = useConfigStore((s) => s.isConnected);
@@ -470,8 +478,8 @@ function App({ apis }: AppProps) {
const state = useConfigStore.getState();
if (state.providers.length > 0 && state.agents.length > 0) return;
try {
if (state.providers.length === 0) await loadProviders();
if (useConfigStore.getState().agents.length === 0) await loadAgents();
if (state.providers.length === 0) await loadProviders({ source: 'startupRecovery' });
if (useConfigStore.getState().agents.length === 0) await loadAgents({ source: 'startupRecovery' });
} catch { /* retry next interval */ }
};
+2 -2
View File
@@ -114,8 +114,8 @@ const MiniChatBootstrap: React.FC<{ config: MiniChatConfig }> = ({ config }) =>
React.useEffect(() => {
if (!isConnected) return;
if (providersCount === 0) void loadProviders();
if (agentsCount === 0) void loadAgents();
if (providersCount === 0) void loadProviders({ source: 'electronMiniChat:recovery' });
if (agentsCount === 0) void loadAgents({ source: 'electronMiniChat:recovery' });
}, [agentsCount, isConnected, loadAgents, loadProviders, providersCount]);
const sessionBootstrappedRef = React.useRef(false);
+2 -2
View File
@@ -373,8 +373,8 @@ export function MobileApp({ apis }: MobileAppProps) {
React.useEffect(() => {
if (!isConnected) return;
if (providersCount === 0) void loadProviders();
if (agentsCount === 0) void loadAgents();
if (providersCount === 0) void loadProviders({ source: 'mobileApp:recovery' });
if (agentsCount === 0) void loadAgents({ source: 'mobileApp:recovery' });
}, [agentsCount, isConnected, loadAgents, loadProviders, providersCount]);
React.useEffect(() => {
@@ -49,14 +49,15 @@ export const AgentMentionAutocomplete = React.forwardRef<AgentMentionAutocomplet
const itemRefs = React.useRef<(HTMLDivElement | null)[]>([]);
const ignoreTabClickRef = React.useRef(false);
const getVisibleAgents = useConfigStore((state) => state.getVisibleAgents);
const configAgentsCount = useConfigStore((state) => state.agents.length);
const agentsWithMetadata = useAgentsStore((state) => state.agents);
const loadAgents = useAgentsStore((state) => state.loadAgents);
React.useEffect(() => {
if (agentsWithMetadata.length === 0) {
if (agentsWithMetadata.length === 0 && configAgentsCount === 0) {
void loadAgents();
}
}, [loadAgents, agentsWithMetadata.length]);
}, [loadAgents, agentsWithMetadata.length, configAgentsCount]);
React.useEffect(() => {
const visibleAgents = getVisibleAgents();
@@ -38,6 +38,7 @@ import { formatEffortLabel, getCycledPrimaryAgentName, type MobileControlsPanel
import { useI18n } from '@/lib/i18n';
import { useOpenCodeReadiness } from '@/hooks/useOpenCodeReadiness';
import { eventMatchesShortcut, getEffectiveShortcutCombo, normalizeCombo } from '@/lib/shortcuts';
import { markStartupTrace } from '@/lib/startupTrace';
type IconComponent = IconName;
@@ -312,6 +313,19 @@ export const ModelControls: React.FC<ModelControlsProps> = ({
// Use visible agents (excludes hidden internal agents)
const agents = getVisibleAgents();
const primaryAgents = React.useMemo(() => agents.filter((agent) => agent.mode === 'primary'), [agents]);
const tracedReadyRef = React.useRef(false);
React.useEffect(() => {
if (tracedReadyRef.current || !isReady) return;
tracedReadyRef.current = true;
markStartupTrace('ModelControls:ready', {
providers: providers.length,
agents: agents.length,
currentProviderId,
currentModelId,
currentAgentName,
});
}, [agents.length, currentAgentName, currentModelId, currentProviderId, isReady, providers.length]);
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
const getDirectoryForSession = useSessionUIStore((s) => s.getDirectoryForSession);
@@ -320,10 +320,10 @@ export const VSCodeLayout: React.FC = () => {
// Keep trying to fetch core datasets on cold starts.
if (configStore.isConnected) {
if (configStore.providers.length === 0) {
await configStore.loadProviders();
await configStore.loadProviders({ source: 'vscodeLayout:bootstrap' });
}
if (configStore.agents.length === 0) {
await configStore.loadAgents();
await configStore.loadAgents({ source: 'vscodeLayout:bootstrap' });
}
}
@@ -50,8 +50,8 @@ export const AgentSelector: React.FC<AgentSelectorProps> = ({
// Load agents on mount
React.useEffect(() => {
loadAgents();
}, [loadAgents]);
if (agents.length === 0) void loadAgents();
}, [agents.length, loadAgents]);
// Ensure we always have a valid selection (defaults to current default agent, then first selectable agent).
React.useEffect(() => {
@@ -8,6 +8,7 @@ import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Icon } from "@/components/icon/Icon";
import { useI18n } from '@/lib/i18n';
import { runtimeFetch } from '@/lib/runtime-fetch';
const GITHUB_URL = 'https://github.com/btriapitsyn/openchamber';
@@ -17,6 +18,7 @@ export const AboutSettings: React.FC = () => {
const { t } = useI18n();
const [updateDialogOpen, setUpdateDialogOpen] = React.useState(false);
const [showChecking, setShowChecking] = React.useState(false);
const [openCodeVersion, setOpenCodeVersion] = React.useState<string | null>(null);
const updateStore = useUpdateStore(useShallow((s) => ({
info: s.info,
checking: s.checking,
@@ -34,6 +36,33 @@ export const AboutSettings: React.FC = () => {
const currentVersion = updateStore.info?.currentVersion || 'unknown';
React.useEffect(() => {
let cancelled = false;
const loadOpenCodeVersion = async () => {
try {
const response = await runtimeFetch('/api/opencode/version', {
method: 'GET',
headers: { Accept: 'application/json' },
});
if (!response.ok) return;
const data = await response.json().catch(() => null) as { version?: unknown } | null;
const version = typeof data?.version === 'string' && data.version.trim().length > 0
? data.version.trim()
: null;
if (!cancelled) setOpenCodeVersion(version);
} catch {
if (!cancelled) setOpenCodeVersion(null);
}
};
void loadOpenCodeVersion();
return () => {
cancelled = true;
};
}, []);
// Track if we initiated a check to show toast on completion
const didInitiateCheck = React.useRef(false);
@@ -91,6 +120,11 @@ export const AboutSettings: React.FC = () => {
)}
</div>
<div className="flex items-center justify-between">
<span className="typography-meta text-muted-foreground">{t('settings.openchamber.about.field.openCodeVersion')}</span>
<span className="typography-meta text-muted-foreground font-mono">{openCodeVersion || t('settings.openchamber.about.state.unknown')}</span>
</div>
{updateStore.error && (
<p className="typography-micro text-[var(--status-error)] truncate">{updateStore.error}</p>
)}
@@ -159,6 +193,10 @@ export const AboutSettings: React.FC = () => {
<span className="typography-ui-label text-foreground">{t('settings.openchamber.about.field.version')}</span>
<span className="typography-meta text-muted-foreground font-mono">{currentVersion}</span>
</div>
<div className="flex min-w-0 flex-col">
<span className="typography-ui-label text-foreground">{t('settings.openchamber.about.field.openCodeVersion')}</span>
<span className="typography-meta text-muted-foreground font-mono">{openCodeVersion || t('settings.openchamber.about.state.unknown')}</span>
</div>
<div className="flex items-center gap-3">
{updateStore.checking && (
@@ -53,7 +53,7 @@ export function ForkSessionDialog(props: ForkSessionDialogProps) {
React.useEffect(() => {
if (!open) return;
void loadProviders({ directory: projectDirectory });
void loadProviders({ directory: projectDirectory, source: 'forkSessionDialog' });
void loadConfigAgents({ directory: projectDirectory });
void loadAgentsStoreAgents();
}, [open, loadProviders, loadConfigAgents, loadAgentsStoreAgents, projectDirectory]);
@@ -662,8 +662,8 @@ export function ScheduledTaskEditorDialog(props: {
if (!open) {
return;
}
void loadProviders();
void loadAgents();
void loadProviders({ source: 'scheduledTaskEditor' });
void loadAgents({ source: 'scheduledTaskEditor' });
}, [open, loadProviders, loadAgents]);
React.useEffect(() => {
@@ -66,7 +66,7 @@ export function TodoSendDialog(props: TodoSendDialogProps) {
React.useEffect(() => {
if (!open) return;
void loadProviders({ directory: projectDirectory });
void loadProviders({ directory: projectDirectory, source: 'todoSendDialog' });
void loadConfigAgents({ directory: projectDirectory });
void loadAgentsStoreAgents();
}, [open, loadProviders, loadConfigAgents, loadAgentsStoreAgents, projectDirectory]);
@@ -360,8 +360,10 @@ export const settingsDict = {
'settings.projects.actions.toast.saved': 'Project actions saved',
'settings.openchamber.about.title': 'About OpenChamber',
'settings.openchamber.about.field.version': 'Version',
'settings.openchamber.about.field.openCodeVersion': 'OpenCode version',
'settings.openchamber.about.state.checking': 'Checking...',
'settings.openchamber.about.state.upToDate': 'Up to date',
'settings.openchamber.about.state.unknown': 'unknown',
'settings.openchamber.about.actions.checkUpdates': 'Check updates',
'settings.openchamber.about.actions.update': 'Update',
'settings.openchamber.about.actions.updateToVersion': 'Update to {version}',
@@ -327,8 +327,10 @@ export const settingsDict = {
"settings.projects.actions.toast.saved": "Acciones del proyecto guardadas",
"settings.openchamber.about.title": "Acerca de OpenChamber",
"settings.openchamber.about.field.version": "Versión",
"settings.openchamber.about.field.openCodeVersion": "Versión de OpenCode",
"settings.openchamber.about.state.checking": "Comprobando...",
"settings.openchamber.about.state.upToDate": "Actualizado",
"settings.openchamber.about.state.unknown": "desconocido",
"settings.openchamber.about.actions.checkUpdates": "Comprobar actualizaciones",
"settings.openchamber.about.actions.update": "Actualizar",
"settings.openchamber.about.actions.updateToVersion": "Actualizar a {version}",
@@ -327,8 +327,10 @@ export const settingsDict = {
'settings.projects.actions.toast.saved': '프로젝트 작업이 저장되었습니다',
'settings.openchamber.about.title': 'OpenChamber 정보',
'settings.openchamber.about.field.version': '버전',
'settings.openchamber.about.field.openCodeVersion': 'OpenCode 버전',
'settings.openchamber.about.state.checking': '확인 중...',
'settings.openchamber.about.state.upToDate': '최신 상태',
'settings.openchamber.about.state.unknown': '알 수 없음',
'settings.openchamber.about.actions.checkUpdates': '업데이트 확인',
'settings.openchamber.about.actions.update': '업데이트',
'settings.openchamber.about.actions.updateToVersion': '{version}으로 업데이트',
@@ -640,8 +640,10 @@ export const settingsDict = {
'settings.openchamber.about.actions.update': 'Aktualizuj',
'settings.openchamber.about.actions.updateToVersion': 'Aktualizuj do wersji {version}',
'settings.openchamber.about.field.version': 'Wersja',
'settings.openchamber.about.field.openCodeVersion': 'Wersja OpenCode',
'settings.openchamber.about.state.checking': 'Sprawdzanie...',
'settings.openchamber.about.state.upToDate': 'Aktualna wersja',
'settings.openchamber.about.state.unknown': 'nieznane',
'settings.openchamber.about.title': 'O OpenChamber',
'settings.openchamber.about.toast.latestVersion': 'Używasz najnowszej wersji',
'settings.openchamber.defaults.field.defaultAgent': 'Domyślny Agent',
@@ -327,8 +327,10 @@ export const settingsDict = {
"settings.projects.actions.toast.saved": "Ações do projeto salvas",
"settings.openchamber.about.title": "Sobre o OpenChamber",
"settings.openchamber.about.field.version": "Versão",
"settings.openchamber.about.field.openCodeVersion": "Versão do OpenCode",
"settings.openchamber.about.state.checking": "Verificando...",
"settings.openchamber.about.state.upToDate": "Atualizado",
"settings.openchamber.about.state.unknown": "desconhecido",
"settings.openchamber.about.actions.checkUpdates": "Verificar atualizações",
"settings.openchamber.about.actions.update": "Atualizar",
"settings.openchamber.about.actions.updateToVersion": "Atualizar a {version}",
@@ -327,8 +327,10 @@ export const settingsDict = {
"settings.projects.actions.toast.saved": "Дії проєкту збережено",
"settings.openchamber.about.title": "Про OpenChamber",
"settings.openchamber.about.field.version": "Версія",
"settings.openchamber.about.field.openCodeVersion": "Версія OpenCode",
"settings.openchamber.about.state.checking": "Перевірка...",
"settings.openchamber.about.state.upToDate": "В актуальному стані",
"settings.openchamber.about.state.unknown": "невідомо",
"settings.openchamber.about.actions.checkUpdates": "Перевірити оновлення",
"settings.openchamber.about.actions.update": "Оновити",
"settings.openchamber.about.actions.updateToVersion": "Оновити до {version}",
@@ -327,8 +327,10 @@ export const settingsDict = {
'settings.projects.actions.toast.saved': '项目操作已保存',
'settings.openchamber.about.title': '关于 OpenChamber',
'settings.openchamber.about.field.version': '版本',
'settings.openchamber.about.field.openCodeVersion': 'OpenCode 版本',
'settings.openchamber.about.state.checking': '检查中...',
'settings.openchamber.about.state.upToDate': '已是最新',
'settings.openchamber.about.state.unknown': '未知',
'settings.openchamber.about.actions.checkUpdates': '检查更新',
'settings.openchamber.about.actions.update': '更新',
'settings.openchamber.about.actions.updateToVersion': '更新到 {version}',
@@ -324,8 +324,10 @@
'settings.projects.actions.toast.saved': '專案操作已儲存',
'settings.openchamber.about.title': '關於 OpenChamber',
'settings.openchamber.about.field.version': '版本',
'settings.openchamber.about.field.openCodeVersion': 'OpenCode 版本',
'settings.openchamber.about.state.checking': '檢查中...',
'settings.openchamber.about.state.upToDate': '已是最新',
'settings.openchamber.about.state.unknown': '未知',
'settings.openchamber.about.actions.checkUpdates': '檢查更新',
'settings.openchamber.about.actions.update': '更新',
'settings.openchamber.about.actions.updateToVersion': '更新到 {version}',
+1 -1
View File
@@ -235,7 +235,7 @@ export const buildOpenCodeStatusReport = async (): Promise<string> => {
};
const probeTargets: Array<{ label: string; path: string; includeDirectory?: boolean; timeoutMs?: number }> = [
{ label: 'health', path: '/global/health', includeDirectory: false },
{ label: 'health', path: '/api/health', includeDirectory: false },
{ label: 'config', path: '/config', includeDirectory: true },
{ label: 'providers', path: '/config/providers', includeDirectory: true },
{ label: 'agents', path: '/agent', includeDirectory: true, timeoutMs: 12000 },
+17 -18
View File
@@ -18,6 +18,7 @@ import { waitForWorktreeBootstrap } from "@/lib/worktrees/worktreeBootstrap";
import { getRuntimeUrlResolver } from "@/lib/runtime-url";
import { runtimeFetch } from "@/lib/runtime-fetch";
import { getRegisteredRuntimeAPIs } from "@/contexts/runtimeAPIRegistry";
import { markStartupTrace } from "@/lib/startupTrace";
import {
assertProviderCircuitClosed,
recordProviderSuccess,
@@ -346,7 +347,14 @@ class OpencodeService {
// Set the current working directory for all API calls
setDirectory(directory: string | undefined) {
this.currentDirectory = this.normalizeCandidatePath(directory) ?? directory;
const normalized = this.normalizeCandidatePath(directory) ?? directory;
if (this.currentDirectory !== normalized) {
markStartupTrace('opencodeClient:setDirectory', {
previous: this.currentDirectory ?? null,
next: normalized ?? null,
});
}
this.currentDirectory = normalized;
}
getDirectory(): string | undefined {
@@ -1414,33 +1422,24 @@ class OpencodeService {
}
}
// Health Check - using /health endpoint for detailed status
// Lightweight readiness check. Full diagnostics still live at /health.
async checkHealth(): Promise<boolean> {
try {
// Health endpoint is at root, not under /api
let healthUrl: string;
const normalizedBase = this.baseUrl.endsWith('/') ? this.baseUrl.replace(/\/+$/, '') : this.baseUrl;
if (normalizedBase === '/api') {
healthUrl = '/health';
} else if (normalizedBase.endsWith('/api')) {
// Desktop: http://127.0.0.1:PORT/api -> http://127.0.0.1:PORT/health
healthUrl = `${normalizedBase.slice(0, -4)}/health`;
} else {
healthUrl = `${normalizedBase}/health`;
}
const healthUrl = normalizedBase === '/api' || normalizedBase.endsWith('/api')
? '/api/opencode/health'
: `${normalizedBase}/opencode/health`;
markStartupTrace('opencodeClient.checkHealth:url', { baseUrl: this.baseUrl, healthUrl });
const response = await runtimeFetch(healthUrl);
markStartupTrace('opencodeClient.checkHealth:response', { status: response.status });
if (!response.ok) {
return false;
}
const healthData = await response.json();
markStartupTrace('opencodeClient.checkHealth:result', { healthy: healthData?.healthy });
// Check if the upstream API is ready (not just OpenChamber server)
if (healthData.isOpenCodeReady === false) {
return false;
}
return true;
return healthData?.healthy === true;
} catch {
return false;
}
+6 -8
View File
@@ -126,14 +126,12 @@ export const runtimeFetch = async (input: string | URL | Request, init: RuntimeF
const inputHeaders = resolvedInput instanceof Request ? resolvedInput.headers : undefined;
const headers = await mergeHeaders(inputHeaders, requestInit.headers, shouldAttachRuntimeAuth(resolvedInput));
if (resolvedInput instanceof Request) {
return fetch(new Request(resolvedInput, { ...requestInit, headers }));
}
return fetch(resolvedInput, {
...requestInit,
headers,
});
return resolvedInput instanceof Request
? fetch(new Request(resolvedInput, { ...requestInit, headers }))
: fetch(resolvedInput, {
...requestInit,
headers,
});
};
let runtimeFetchBridgeInstalled = false;
+90
View File
@@ -0,0 +1,90 @@
export type StartupTraceEvent = {
t: number;
name: string;
data?: Record<string, unknown>;
};
declare global {
interface Window {
__OPENCHAMBER_STARTUP_TRACE__?: StartupTraceEvent[];
__OPENCHAMBER_STARTUP_TRACE_START__?: number;
}
}
const MAX_STARTUP_TRACE_EVENTS = 500;
const enabled = () => {
if (typeof window === 'undefined') return false;
try {
const params = new URLSearchParams(window.location.search);
return params.get('startupTrace') === '1' || window.localStorage?.getItem('OPENCHAMBER_STARTUP_TRACE') === '1';
} catch {
return false;
}
};
export const startupTraceEnabled = () => enabled();
export const markStartupTrace = (name: string, data?: Record<string, unknown>) => {
if (!enabled()) return;
const now = typeof performance !== 'undefined' ? performance.now() : Date.now();
window.__OPENCHAMBER_STARTUP_TRACE_START__ ??= now;
window.__OPENCHAMBER_STARTUP_TRACE__ ??= [];
window.__OPENCHAMBER_STARTUP_TRACE__.push({
t: Math.round(now - window.__OPENCHAMBER_STARTUP_TRACE_START__),
name,
...(data ? { data } : {}),
});
if (window.__OPENCHAMBER_STARTUP_TRACE__.length > MAX_STARTUP_TRACE_EVENTS) {
window.__OPENCHAMBER_STARTUP_TRACE__.splice(
0,
window.__OPENCHAMBER_STARTUP_TRACE__.length - MAX_STARTUP_TRACE_EVENTS,
);
}
};
export const getStartupTraceSummary = () => {
const trace = typeof window !== 'undefined' ? window.__OPENCHAMBER_STARTUP_TRACE__ ?? [] : [];
const readyIndex = trace.findIndex((event) => event.name === 'ModelControls:ready');
const endIndex = readyIndex >= 0 ? Math.min(trace.length, readyIndex + 8) : trace.length;
return trace.slice(0, endIndex).filter((event) => (
event.name.includes('checkConnection')
|| event.name.includes('checkHealth')
|| event.name.includes('initializeApp')
|| event.name.includes('initApp')
|| event.name.includes('loadProviders')
|| event.name.includes('loadAgents')
|| event.name.includes('config.defaults')
|| event.name.includes('modelsMetadata')
|| event.name.includes('ModelControls')
|| event.name.includes('activateDirectory')
|| event.name.includes('opencodeClient:setDirectory')
));
};
if (typeof window !== 'undefined') {
(window as typeof window & { __OPENCHAMBER_STARTUP_TRACE_SUMMARY__?: typeof getStartupTraceSummary })
.__OPENCHAMBER_STARTUP_TRACE_SUMMARY__ = getStartupTraceSummary;
}
export const measureStartupTrace = async <T>(
name: string,
fn: () => Promise<T>,
data?: Record<string, unknown>,
): Promise<T> => {
const started = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace(`${name}:start`, data);
try {
const result = await fn();
const ended = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace(`${name}:end`, { durationMs: Math.round(ended - started) });
return result;
} catch (error) {
const ended = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace(`${name}:error`, {
durationMs: Math.round(ended - started),
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
};
+2 -2
View File
@@ -620,10 +620,10 @@ async function performConfigRefresh(options: {
const sdkRefreshTasks: Promise<void>[] = [];
for (const directory of directoriesToRefresh) {
if (refreshProviders) {
sdkRefreshTasks.push(configStore.loadProviders({ directory }).then(() => undefined));
sdkRefreshTasks.push(configStore.loadProviders({ directory, source: 'agentsStore:refreshConfig' }).then(() => undefined));
}
if (refreshSdkAgents) {
sdkRefreshTasks.push(configStore.loadAgents({ directory }).then(() => undefined));
sdkRefreshTasks.push(configStore.loadAgents({ directory, source: 'agentsStore:refreshConfig' }).then(() => undefined));
}
}
+180 -30
View File
@@ -15,6 +15,7 @@ import { useDirectoryStore } from "@/stores/useDirectoryStore";
import { streamDebugEnabled } from "@/stores/utils/streamDebug";
import { parseModelIdentifier } from "@/lib/modelIdentifier";
import { runtimeFetch } from "@/lib/runtime-fetch";
import { markStartupTrace, measureStartupTrace } from "@/lib/startupTrace";
const MODELS_DEV_API_URL = "https://models.dev/api.json";
const MODELS_DEV_PROXY_URL = "/api/openchamber/models-metadata";
@@ -61,6 +62,18 @@ interface OpenChamberDefaults {
}
const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
markStartupTrace('config.defaults:start');
const started = typeof performance !== 'undefined' ? performance.now() : Date.now();
const finish = (source: string, result: OpenChamberDefaults) => {
const ended = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('config.defaults:end', {
source,
durationMs: Math.round(ended - started),
hasDefaultModel: Boolean(result.defaultModel),
hasDefaultAgent: Boolean(result.defaultAgent),
});
return result;
};
try {
// 1. Runtime settings API (VSCode)
const runtimeSettings = getRegisteredRuntimeAPIs()?.settings;
@@ -86,7 +99,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
const sttSilenceThresholdDb = normalizeSttSilenceThresholdDb(data?.sttSilenceThresholdDb);
const sttSilenceHoldMs = normalizeSttSilenceHoldMs(data?.sttSilenceHoldMs);
return {
return finish('runtime-settings', {
defaultModel: defaultModel.length > 0 ? defaultModel : undefined,
defaultVariant: defaultVariant.length > 0 ? defaultVariant : undefined,
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
@@ -101,7 +114,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
sttLanguage,
sttSilenceThresholdDb,
sttSilenceHoldMs,
};
});
}
} catch {
// Fall through to fetch
@@ -114,7 +127,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
headers: { Accept: 'application/json' },
});
if (!response.ok) {
return {};
return finish('settings-route-not-ok', {});
}
const data = await response.json();
const defaultModel = typeof data?.defaultModel === 'string' ? data.defaultModel.trim() : '';
@@ -134,7 +147,7 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
const sttSilenceThresholdDb = normalizeSttSilenceThresholdDb(data?.sttSilenceThresholdDb);
const sttSilenceHoldMs = normalizeSttSilenceHoldMs(data?.sttSilenceHoldMs);
return {
return finish('settings-route', {
defaultModel: defaultModel.length > 0 ? defaultModel : undefined,
defaultVariant: defaultVariant.length > 0 ? defaultVariant : undefined,
defaultAgent: defaultAgent.length > 0 ? defaultAgent : undefined,
@@ -149,9 +162,10 @@ const fetchOpenChamberDefaults = async (): Promise<OpenChamberDefaults> => {
sttLanguage,
sttSilenceThresholdDb,
sttSilenceHoldMs,
};
} catch {
return {};
});
} catch (error) {
markStartupTrace('config.defaults:error', { error: error instanceof Error ? error.message : String(error) });
return finish('error', {});
}
};
@@ -458,9 +472,11 @@ const ensureModelsMetadataFetch = (
return;
}
modelsMetadataInFlight = fetchModelsDevMetadata()
markStartupTrace('modelsMetadata:queued');
modelsMetadataInFlight = measureStartupTrace('modelsMetadata', fetchModelsDevMetadata)
.then((metadata) => {
if (metadata.size > 0) {
markStartupTrace('modelsMetadata:set', { entries: metadata.size });
setModelsMetadata(metadata);
}
return metadata;
@@ -602,8 +618,8 @@ interface ConfigStore {
activateDirectory: (directory: string | null | undefined) => Promise<void>;
loadProviders: (options?: { directory?: string | null }) => Promise<void>;
loadAgents: (options?: { directory?: string | null }) => Promise<boolean>;
loadProviders: (options?: { directory?: string | null; source?: string }) => Promise<void>;
loadAgents: (options?: { directory?: string | null; source?: string }) => Promise<boolean>;
invalidateModelMetadataCache: () => void;
setProvider: (providerId: string) => void;
setModel: (modelId: string) => void;
@@ -906,10 +922,14 @@ export const useConfigStore = create<ConfigStore>()(
})(),
activateDirectory: async (directory) => {
const directoryKey = toDirectoryKey(directory);
let snapshotHadProviders = false;
let snapshotHadAgents = false;
set((state) => {
const snapshot = state.directoryScoped[directoryKey];
if (snapshot) {
snapshotHadProviders = snapshot.providers.length > 0;
snapshotHadAgents = snapshot.agents.length > 0;
return {
activeDirectoryKey: directoryKey,
providers: snapshot.providers,
@@ -941,18 +961,36 @@ export const useConfigStore = create<ConfigStore>()(
return;
}
await get().loadProviders({ directory: fromDirectoryKey(directoryKey) });
await get().loadAgents({ directory: fromDirectoryKey(directoryKey) });
if (snapshotHadProviders) {
markStartupTrace('activateDirectory:skipProviders', { directoryKey });
} else {
await get().loadProviders({ directory: fromDirectoryKey(directoryKey), source: 'activateDirectory' });
}
if (snapshotHadAgents) {
markStartupTrace('activateDirectory:skipAgents', { directoryKey });
} else {
await get().loadAgents({ directory: fromDirectoryKey(directoryKey), source: 'activateDirectory' });
}
},
loadProviders: async (options) => {
const directoryKey = toDirectoryKey(options?.directory ?? fromDirectoryKey(get().activeDirectoryKey));
const requestedDirectory = options?.directory ?? fromDirectoryKey(get().activeDirectoryKey);
const effectiveDirectory = requestedDirectory ?? opencodeClient.getDirectory() ?? null;
const directoryKey = toDirectoryKey(requestedDirectory);
const source = options?.source ?? 'unknown';
markStartupTrace('loadProviders:called', { directoryKey, source, requestedDirectory, effectiveDirectory });
// Dedup: if a load is already in-flight for this directory, reuse it
const existing = _inFlightProviders.get(directoryKey);
if (existing) return existing;
if (existing) {
markStartupTrace('loadProviders:deduped', { directoryKey, source, requestedDirectory, effectiveDirectory });
return existing;
}
const promise = (async () => {
const loaderStarted = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('loadProviders:start', { directoryKey, source, requestedDirectory, effectiveDirectory });
const existingSnapshot = get().directoryScoped[directoryKey];
const previousProviders = existingSnapshot?.providers ?? (get().activeDirectoryKey === directoryKey ? get().providers : []);
const previousDefaults = existingSnapshot?.defaultProviders ?? (get().activeDirectoryKey === directoryKey ? get().defaultProviders : {});
@@ -964,9 +1002,13 @@ export const useConfigStore = create<ConfigStore>()(
() => get().modelsMetadata,
(metadata) => set({ modelsMetadata: metadata }),
);
const apiResult = await opencodeClient.withDirectory(
fromDirectoryKey(directoryKey),
() => opencodeClient.getProviders()
const apiResult = await measureStartupTrace(
'loadProviders:api',
() => opencodeClient.withDirectory(
fromDirectoryKey(directoryKey),
() => opencodeClient.getProviders()
),
{ directoryKey, source, requestedDirectory, effectiveDirectory, attempt: attempt + 1 },
);
const providers = Array.isArray(apiResult?.providers) ? apiResult.providers : [];
const defaults = apiResult?.default || {};
@@ -1036,15 +1078,40 @@ export const useConfigStore = create<ConfigStore>()(
return nextState;
});
const loaderEnded = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('loadProviders:end', {
directoryKey,
source,
requestedDirectory,
effectiveDirectory,
durationMs: Math.round(loaderEnded - loaderStarted),
providers: processedProviders.length,
models: processedProviders.reduce((count, provider) => count + provider.models.length, 0),
});
return;
} catch (error) {
lastError = error;
markStartupTrace('loadProviders:attemptError', {
directoryKey,
source,
requestedDirectory,
effectiveDirectory,
attempt: attempt + 1,
error: error instanceof Error ? error.message : String(error),
});
const waitMs = 200 * (attempt + 1);
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}
console.error("Failed to load providers:", lastError);
markStartupTrace('loadProviders:error', {
directoryKey,
source,
requestedDirectory,
effectiveDirectory,
error: lastError instanceof Error ? lastError.message : String(lastError),
});
set((state) => {
const baseSnapshot: DirectoryScopedConfig = state.directoryScoped[directoryKey] ?? {
@@ -1314,13 +1381,22 @@ export const useConfigStore = create<ConfigStore>()(
},
loadAgents: async (options) => {
const directoryKey = toDirectoryKey(options?.directory ?? fromDirectoryKey(get().activeDirectoryKey));
const requestedDirectory = options?.directory ?? fromDirectoryKey(get().activeDirectoryKey);
const effectiveDirectory = requestedDirectory ?? opencodeClient.getDirectory() ?? null;
const directoryKey = toDirectoryKey(requestedDirectory);
const source = options?.source ?? 'unknown';
markStartupTrace('loadAgents:called', { directoryKey, source, requestedDirectory, effectiveDirectory });
// Dedup: if a load is already in-flight for this directory, reuse it
const existing = _inFlightAgents.get(directoryKey);
if (existing) return existing;
if (existing) {
markStartupTrace('loadAgents:deduped', { directoryKey, source, requestedDirectory, effectiveDirectory });
return existing;
}
const promise = (async (): Promise<boolean> => {
const loaderStarted = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('loadAgents:start', { directoryKey, source, requestedDirectory, effectiveDirectory });
const existingSnapshot = get().directoryScoped[directoryKey];
const previousAgents = existingSnapshot?.agents ?? (get().activeDirectoryKey === directoryKey ? get().agents : []);
let lastError: unknown = null;
@@ -1329,12 +1405,22 @@ export const useConfigStore = create<ConfigStore>()(
try {
// Fetch agents and OpenChamber settings in parallel
const [agents, openChamberDefaults] = await Promise.all([
opencodeClient.withDirectory(fromDirectoryKey(directoryKey), () => opencodeClient.listAgents()),
measureStartupTrace(
'loadAgents:api',
() => opencodeClient.withDirectory(fromDirectoryKey(directoryKey), () => opencodeClient.listAgents()),
{ directoryKey, source, requestedDirectory, effectiveDirectory, attempt: attempt + 1 },
),
fetchOpenChamberDefaults(),
]);
const safeAgents = Array.isArray(agents) ? agents : [];
const providerLoad = _inFlightProviders.get(directoryKey);
if (providerLoad) {
markStartupTrace('loadAgents:awaitProviders', { directoryKey, source });
await providerLoad;
}
const providers = get().activeDirectoryKey === directoryKey
? get().providers
: (get().directoryScoped[directoryKey]?.providers ?? []);
@@ -1452,6 +1538,15 @@ export const useConfigStore = create<ConfigStore>()(
return nextState;
});
const loaderEnded = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('loadAgents:end', {
directoryKey,
source,
requestedDirectory,
effectiveDirectory,
durationMs: Math.round(loaderEnded - loaderStarted),
agents: safeAgents.length,
});
return true;
}
@@ -1592,15 +1687,39 @@ export const useConfigStore = create<ConfigStore>()(
});
}
const loaderEnded = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('loadAgents:end', {
directoryKey,
source,
requestedDirectory,
effectiveDirectory,
durationMs: Math.round(loaderEnded - loaderStarted),
agents: safeAgents.length,
});
return true;
} catch (error) {
lastError = error;
markStartupTrace('loadAgents:attemptError', {
directoryKey,
source,
requestedDirectory,
effectiveDirectory,
attempt: attempt + 1,
error: error instanceof Error ? error.message : String(error),
});
const waitMs = 200 * (attempt + 1);
await new Promise((resolve) => setTimeout(resolve, waitMs));
}
}
console.error("Failed to load agents:", lastError);
markStartupTrace('loadAgents:error', {
directoryKey,
source,
requestedDirectory,
effectiveDirectory,
error: lastError instanceof Error ? lastError.message : String(lastError),
});
set((state) => {
const providers = state.activeDirectoryKey === directoryKey
@@ -2064,13 +2183,31 @@ export const useConfigStore = create<ConfigStore>()(
},
checkConnection: async () => {
markStartupTrace('checkConnection:start');
const maxAttempts = 5;
let attempt = 0;
let lastError: unknown = null;
while (attempt < maxAttempts) {
try {
const isHealthy = await opencodeClient.checkHealth();
markStartupTrace('checkConnection:attempt', { attempt: attempt + 1 });
const isHealthy = await measureStartupTrace(
'checkConnection:health',
() => opencodeClient.checkHealth(),
{ attempt: attempt + 1 },
);
if (!isHealthy && attempt < maxAttempts - 1) {
const hasEverConnected = get().hasEverConnected;
set({
isConnected: false,
connectionPhase: hasEverConnected ? "reconnecting" : "connecting",
lastDisconnectReason: 'health_check_unhealthy',
});
attempt += 1;
await sleep(400 * attempt);
continue;
}
const hasEverConnected = get().hasEverConnected;
set(isHealthy
? { isConnected: true, hasEverConnected: true, connectionPhase: "connected" }
@@ -2079,6 +2216,7 @@ export const useConfigStore = create<ConfigStore>()(
connectionPhase: hasEverConnected ? "reconnecting" : "connecting",
lastDisconnectReason: 'health_check_unhealthy',
});
markStartupTrace('checkConnection:end', { healthy: isHealthy, attempts: attempt + 1 });
return isHealthy;
} catch (error) {
lastError = error;
@@ -2096,15 +2234,19 @@ export const useConfigStore = create<ConfigStore>()(
connectionPhase: get().hasEverConnected ? "reconnecting" : "connecting",
lastDisconnectReason: 'health_check_failed',
});
markStartupTrace('checkConnection:end', { healthy: false, attempts: maxAttempts });
return false;
},
initializeApp: async () => {
if (_initializeAppInFlight) {
markStartupTrace('initializeApp:deduped');
return _initializeAppInFlight;
}
const run = (async () => {
const initStarted = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('initializeApp:start');
try {
const debug = streamDebugEnabled();
if (debug) console.log("Starting app initialization...");
@@ -2123,15 +2265,21 @@ export const useConfigStore = create<ConfigStore>()(
}
if (debug) console.log("Initializing app...");
await opencodeClient.initApp();
markStartupTrace('initApp:skipped', { reason: 'checkConnection already verified health' });
if (debug) console.log("Loading providers...");
await get().loadProviders();
if (debug) console.log("Loading agents...");
await get().loadAgents();
if (debug) console.log("Loading providers and agents...");
await Promise.all([
get().loadProviders({ source: 'initializeApp' }),
get().loadAgents({ source: 'initializeApp' }),
]);
set({ isInitialized: true, isConnected: true, hasEverConnected: true, connectionPhase: "connected" });
const initEnded = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('initializeApp:end', {
durationMs: Math.round(initEnded - initStarted),
providers: get().providers.length,
agents: get().agents.length,
});
if (debug) console.log("App initialized successfully");
} catch (error) {
console.error("Failed to initialize app:", error);
@@ -2141,6 +2289,7 @@ export const useConfigStore = create<ConfigStore>()(
connectionPhase: get().hasEverConnected ? "reconnecting" : "connecting",
lastDisconnectReason: 'init_error',
});
markStartupTrace('initializeApp:error', { error: error instanceof Error ? error.message : String(error) });
}
})().finally(() => {
_initializeAppInFlight = null;
@@ -2235,16 +2384,16 @@ let unsubscribeConfigStoreChanges: (() => void) | null = null;
if (!unsubscribeConfigStoreChanges) {
unsubscribeConfigStoreChanges = subscribeToConfigChanges(async (event) => {
const tasks: Promise<void>[] = [];
const tasks: Promise<void>[] = [];
if (scopeMatches(event, "agents")) {
const { loadAgents } = useConfigStore.getState();
tasks.push(loadAgents().then(() => {}));
tasks.push(loadAgents({ source: 'configChange:agents' }).then(() => {}));
}
if (scopeMatches(event, "providers")) {
const { loadProviders } = useConfigStore.getState();
tasks.push(loadProviders());
tasks.push(loadProviders({ source: 'configChange:providers' }));
}
if (tasks.length > 0) {
@@ -2263,6 +2412,7 @@ if (typeof window !== "undefined" && !unsubscribeConfigStoreDirectoryChanges) {
return;
}
markStartupTrace('directoryStore:changed', { previous: prevKey, next: nextKey });
void useConfigStore.getState().activateDirectory(state.currentDirectory);
});
}
@@ -241,6 +241,32 @@ export async function handleSystemBridgeMessage(
}
}
case 'api:opencode/version': {
try {
const apiUrl = ctx?.manager?.getApiUrl();
if (!apiUrl) {
return { id, type, success: true, data: { version: null, error: 'OpenCode manager unavailable' } };
}
const base = `${apiUrl.replace(/\/+$/, '')}/`;
const response = await fetch(new URL('global/health', base).toString(), {
method: 'GET',
headers: { Accept: 'application/json', ...ctx?.manager?.getOpenCodeAuthHeaders() },
});
const health = await response.json().catch(() => null) as { version?: unknown; error?: unknown } | null;
if (!response.ok) {
const message = typeof health?.error === 'string' ? health.error : response.statusText || 'Failed to read OpenCode version';
return { id, type, success: true, data: { version: null, error: message } };
}
const version = typeof health?.version === 'string' && health.version.trim().length > 0
? health.version.trim().replace(/^v/, '')
: null;
return { id, type, success: true, data: { version } };
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return { id, type, success: true, data: { version: null, error: errorMessage } };
}
}
case 'api:session-activity:get': {
return { id, type, success: true, data: getSessionActivitySnapshot() };
}
+1 -1
View File
@@ -547,7 +547,7 @@ export async function activate(context: vscode.ExtensionContext) {
};
const probeTargets: Array<{ label: string; path: string; includeDirectory?: boolean; timeoutMs?: number }> = [
{ label: 'health', path: '/global/health', includeDirectory: false },
{ label: 'health', path: '/api/health', includeDirectory: false },
{ label: 'config', path: '/config', includeDirectory: true },
{ label: 'providers', path: '/config/providers', includeDirectory: true },
// Can be slower on large configs; keep the probe from producing false negatives.
+1 -1
View File
@@ -568,7 +568,7 @@ async function waitForReady(
const timeout = setTimeout(() => controller.abort(), 3000);
// OpenCode readiness check.
const url = new URL(`${baseUrl}/global/health`);
const url = new URL(`${baseUrl}/api/health`);
const res = await fetch(url.toString(), {
method: 'GET',
headers: { Accept: 'application/json', ...authHeaders },
+18
View File
@@ -968,6 +968,24 @@ const handleLocalApiRequest = async (input: RequestInfo | URL, url: URL, init: R
}
}
if (pathname === '/api/opencode/version' && method === 'GET') {
try {
const data = await sendBridgeMessage('api:opencode/version');
return new Response(JSON.stringify(data), { status: 200, headers: { 'Content-Type': 'application/json' } });
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return new Response(JSON.stringify({ version: null, error: message }), { status: 502, headers: { 'Content-Type': 'application/json' } });
}
}
if (pathname === '/api/opencode/health' && method === 'GET') {
const connectionStatus = window.__OPENCHAMBER_CONNECTION__?.status;
return new Response(JSON.stringify({ healthy: connectionStatus === 'connected' }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
}
if (pathname === '/api/zen/models' && method === 'GET') {
try {
const data = await sendBridgeMessage('api:zen:models');
+21 -32
View File
@@ -392,7 +392,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
}
try {
const response = await fetch(buildOpenCodeUrl('/global/health', ''), {
const response = await fetch(buildOpenCodeUrl('/api/health', ''), {
method: 'GET',
headers: {
Accept: 'application/json',
@@ -417,7 +417,7 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 3000);
const base = origin ?? `http://127.0.0.1:${port}`;
const response = await fetch(`${base}/global/health`, {
const response = await fetch(`${base}/api/health`, {
method: 'GET',
headers: {
Accept: 'application/json',
@@ -659,51 +659,40 @@ export const createOpenCodeLifecycleRuntime = (deps) => {
let lastError = null;
while (Date.now() < deadline) {
let timeout = null;
try {
const [configResult, agentResult] = await Promise.all([
fetch(buildOpenCodeUrl('/config', ''), {
method: 'GET',
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
}).catch((error) => error),
fetch(buildOpenCodeUrl('/agent', ''), {
method: 'GET',
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
}).catch((error) => error),
]);
const controller = new AbortController();
timeout = setTimeout(() => controller.abort(), HEALTH_CHECK_TIMEOUT_MS);
const response = await fetch(buildOpenCodeUrl('/api/health', ''), {
method: 'GET',
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
signal: controller.signal,
});
clearTimeout(timeout);
timeout = null;
if (configResult instanceof Error) {
lastError = configResult;
if (!response.ok) {
lastError = new Error(`OpenCode health endpoint responded with status ${response.status}`);
await new Promise((resolve) => setTimeout(resolve, intervalMs));
continue;
}
if (!configResult.ok) {
lastError = new Error(`OpenCode config endpoint responded with status ${configResult.status}`);
const body = await response.json().catch(() => null);
if (body?.healthy !== true) {
lastError = new Error('OpenCode health endpoint returned unhealthy response');
await new Promise((resolve) => setTimeout(resolve, intervalMs));
continue;
}
await configResult.json().catch(() => null);
if (agentResult instanceof Error) {
lastError = agentResult;
await new Promise((resolve) => setTimeout(resolve, intervalMs));
continue;
}
if (!agentResult.ok) {
lastError = new Error(`Agent endpoint responded with status ${agentResult.status}`);
await new Promise((resolve) => setTimeout(resolve, intervalMs));
continue;
}
await agentResult.json().catch(() => []);
state.isOpenCodeReady = true;
state.lastOpenCodeError = null;
return;
} catch (error) {
lastError = error;
} finally {
if (timeout) {
clearTimeout(timeout);
}
}
await new Promise((resolve) => setTimeout(resolve, intervalMs));
@@ -33,7 +33,7 @@ export const createOpenCodeNetworkRuntime = (deps) => {
try {
const controller = new AbortController();
timeout = setTimeout(() => controller.abort(), 3000);
const response = await fetch(`${url.replace(/\/+$/, '')}/global/health`, {
const response = await fetch(`${url.replace(/\/+$/, '')}/api/health`, {
method: 'GET',
headers: {
Accept: 'application/json',
@@ -212,6 +212,51 @@ export const registerOpenCodeRoutes = (app, dependencies) => {
}
});
app.get('/api/opencode/health', async (_req, res) => {
try {
const healthResponse = await fetch(buildOpenCodeUrl('/api/health', ''), {
method: 'GET',
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
});
const health = await healthResponse.json().catch(() => null);
if (!healthResponse.ok) {
return res.status(healthResponse.status).json({
healthy: false,
error: health?.error || healthResponse.statusText || 'OpenCode health check failed',
});
}
return res.json({ healthy: health?.healthy === true });
} catch (error) {
return res.status(503).json({
healthy: false,
error: error instanceof Error ? error.message : 'OpenCode health check failed',
});
}
});
app.get('/api/opencode/version', async (_req, res) => {
try {
const healthResponse = await fetch(buildOpenCodeUrl('/global/health', ''), {
method: 'GET',
headers: { Accept: 'application/json', ...getOpenCodeAuthHeaders() },
});
const health = await healthResponse.json().catch(() => null);
if (!healthResponse.ok) {
return res.status(healthResponse.status).json({
version: null,
error: health?.error || healthResponse.statusText || 'Failed to read OpenCode version',
});
}
const version = typeof health?.version === 'string' ? health.version.replace(/^v/, '') : null;
return res.json({ version });
} catch (error) {
return res.status(500).json({
version: null,
error: error instanceof Error ? error.message : 'Failed to read OpenCode version',
});
}
});
app.put('/api/config/settings', async (req, res) => {
console.log('[API:PUT /api/config/settings] Received request');
try {