perf: overhaul session loading, caching, and runtime isolation (#2360)

Improve OpenChamber responsiveness under large session workloads while fixing
cache, synchronization, and persistence correctness across runtimes, projects,
directories, and worktrees.

- prioritize selected and visible sessions during bootstrap and defer
  non-critical enrichment work
- reduce redundant message loading, event processing, store publication, and
  hidden sidebar work
- prevent stale session and message requests from overwriting newer
  authoritative state
- preserve existing data when authoritative fetches fail instead of treating
  failures as successful empty responses
- scope session materialization, messages, drafts, queues, todos, pins,
  permissions, folders, tabs, Git state, and pull request data by runtime and
  directory identity
- harden runtime switching, reconnect, cleanup, mutation reconciliation, and
  persisted-state ordering
- preserve live subagent Task linkage when metadata arrives after an older
  message request or while streaming parts are suspended
- coalesce overlapping tail refreshes without losing newer refresh demand
- improve cold-session loading by moving deferrable work out of the critical
  bootstrap path
- isolate URL authentication, mobile credentials, native secrets, and other
  runtime-owned state across endpoint changes
- bound long-lived caches and remove avoidable allocations from event and
  rendering hot paths
- limit virtualization to archive collections where it improves rendering
  without disrupting active sidebar layout
- stabilize session folders, pin ordering, expanded state, and persisted
  sidebar behavior
- open skill files through the same secure editor and outside-workspace grant
  flow used by file navigation, including worktree sessions
- expand regression coverage for stale completions, runtime collisions,
  reconnect behavior, persistence races, authoritative empty results, and
  subagent refresh ordering
- document the updated synchronization, cache ownership, performance, and
  runtime-isolation invariants
This commit is contained in:
Bohdan Triapitsyn
2026-07-21 20:52:20 +03:00
committed by GitHub
parent 485efc7117
commit 85400459e9
197 changed files with 10835 additions and 3400 deletions
+21 -16
View File
@@ -1,13 +1,10 @@
import { create } from 'zustand';
import type { Event } from '@opencode-ai/sdk/v2/client';
import type { Event, SessionStatus } from '@opencode-ai/sdk/v2/client';
import { normalizeProjectPath } from '@/lib/projectResolution';
// Live busy/retry status for sessions in directories WITHOUT a synced child
// store. The global event stream (`/api/global/event/ws`) carries status
// events for every directory, but the sync dispatcher can only apply them to
// an existing child store — events for unopened directories used to be
// dropped. They land here instead, so cross-project consumers (the macOS tray)
// see live status for all sessions, not just the synced ones.
// Shared live busy/retry index for every directory. Global events update it
// incrementally and authoritative directory snapshots reconcile it, so each
// sidebar row can subscribe to one leaf instead of every child store.
//
// Only non-idle entries are kept; absence means idle. Entries carry their
// directory so a polled per-directory snapshot can authoritatively replace
@@ -15,7 +12,7 @@ import { normalizeProjectPath } from '@/lib/projectResolution';
type ActiveStatusType = 'busy' | 'retry';
type GlobalSessionStatusEntry = { status: ActiveStatusType; directory: string };
type GlobalSessionStatusEntry = { status: SessionStatus; directory: string };
type GlobalSessionStatusState = {
statusById: Map<string, GlobalSessionStatusEntry>;
@@ -34,16 +31,17 @@ const normalizeStatusType = (type: unknown): ActiveStatusType | 'idle' =>
const normalizeDirectory = (directory: string): string =>
normalizeProjectPath(directory) ?? directory;
const setStatus = (sessionId: string, directory: string, status: ActiveStatusType | 'idle'): void => {
const setStatus = (sessionId: string, directory: string, status: SessionStatus | { type: 'idle' }): void => {
useGlobalSessionStatusStore.setState((state) => {
const current = state.statusById.get(sessionId);
if (status === 'idle') {
if (status.type === 'idle') {
if (!current) return state;
const next = new Map(state.statusById);
next.delete(sessionId);
return { statusById: next };
}
if (current && current.status === status && current.directory === directory) return state;
if (current && current.status.type === status.type && current.directory === directory
&& JSON.stringify(current.status) === JSON.stringify(status)) return state;
const next = new Map(state.statusById);
next.set(sessionId, { status, directory });
return { statusById: next };
@@ -58,14 +56,19 @@ export const applyGlobalSessionStatusEvent = (directory: string, payload: Event)
case 'session.status': {
const props = payload.properties as { sessionID?: string; status?: { type?: string } } | undefined;
if (typeof props?.sessionID !== 'string' || !props.sessionID) return;
setStatus(props.sessionID, normalizeDirectory(directory), normalizeStatusType(props.status?.type));
const type = normalizeStatusType(props.status?.type);
setStatus(
props.sessionID,
normalizeDirectory(directory),
type === 'idle' ? { type: 'idle' } : { ...(props.status ?? {}), type } as SessionStatus,
);
return;
}
case 'session.idle':
case 'session.error': {
const props = payload.properties as { sessionID?: string } | undefined;
if (typeof props?.sessionID === 'string' && props.sessionID) {
setStatus(props.sessionID, normalizeDirectory(directory), 'idle');
setStatus(props.sessionID, normalizeDirectory(directory), { type: 'idle' });
}
return;
}
@@ -101,14 +104,16 @@ export const applyGlobalSessionStatusSnapshot = (
const type = normalizeStatusType(status?.type);
const current = next.get(sessionId);
if (type === 'idle') {
if (current && current.directory === directory) {
if (current && (current.directory === directory || known.has(sessionId))) {
next.delete(sessionId);
changed = true;
}
continue;
}
if (!current || current.status !== type || current.directory !== directory) {
next.set(sessionId, { status: type, directory });
const normalizedStatus = { ...status, type } as SessionStatus;
if (!current || current.status.type !== type || current.directory !== directory
|| JSON.stringify(current.status) !== JSON.stringify(normalizedStatus)) {
next.set(sessionId, { status: normalizedStatus, directory });
changed = true;
}
}