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