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:
committed by
GitHub
parent
485efc7117
commit
85400459e9
@@ -68,6 +68,7 @@ import { useSessionWorktreeStore } from "./session-worktree-store"
|
||||
import { getAttachedSessionDirectory } from "./session-worktree-contract"
|
||||
import { setSessionOpener } from "./session-navigation"
|
||||
import { getRuntimeKey } from "@/lib/runtime-switch"
|
||||
import { persistWorktreeTopology, readPersistedWorktreeTopology } from "./worktree-topology-cache"
|
||||
import { rememberRuntimeLiveStatus } from "./runtime-live-memory"
|
||||
|
||||
export type { AttachedFile }
|
||||
@@ -172,6 +173,7 @@ export function routeMessage(params: {
|
||||
|
||||
type SendMessageOptions = {
|
||||
sessionId?: string
|
||||
directory?: string
|
||||
delivery?: 'steer'
|
||||
}
|
||||
|
||||
@@ -392,6 +394,8 @@ type RuntimeSessionMemory = {
|
||||
sessionId: string | null
|
||||
directory: string | null
|
||||
draft: NewSessionDraftState
|
||||
worktreeMetadata: Map<string, WorktreeMetadata>
|
||||
availableWorktreesByProject: Map<string, WorktreeMetadata[]>
|
||||
}
|
||||
const runtimeSessionMemory = new Map<string, RuntimeSessionMemory>()
|
||||
|
||||
@@ -408,6 +412,8 @@ const writeRuntimeSessionMemory = (key: string, patch: Partial<RuntimeSessionMem
|
||||
sessionId: current?.sessionId ?? null,
|
||||
directory: current?.directory ?? null,
|
||||
draft: current?.draft ? cloneDraft(current.draft) : { ...DEFAULT_DRAFT },
|
||||
worktreeMetadata: current?.worktreeMetadata ?? new Map(),
|
||||
availableWorktreesByProject: current?.availableWorktreesByProject ?? new Map(),
|
||||
...patch,
|
||||
})
|
||||
}
|
||||
@@ -519,37 +525,13 @@ export async function materializeOpenDraftSession(selection: {
|
||||
// the FIRST launch — yielding a single project-scoped config load instead of a
|
||||
// worktree+project double-load. Discovery refreshes it in the background.
|
||||
// ---------------------------------------------------------------------------
|
||||
const WORKTREE_MAP_STORAGE_KEY = 'oc.worktreeMap'
|
||||
|
||||
const loadPersistedWorktreeMap = (): Map<string, WorktreeMetadata[]> => {
|
||||
try {
|
||||
const raw = getDeferredSafeStorage().getItem(WORKTREE_MAP_STORAGE_KEY)
|
||||
if (!raw) return new Map()
|
||||
const entries = JSON.parse(raw) as Array<[string, WorktreeMetadata[]]>
|
||||
if (!Array.isArray(entries)) return new Map()
|
||||
return new Map(
|
||||
entries.filter((entry) => Array.isArray(entry) && typeof entry[0] === 'string' && Array.isArray(entry[1])),
|
||||
)
|
||||
} catch {
|
||||
return new Map()
|
||||
}
|
||||
}
|
||||
|
||||
const persistWorktreeMap = (serialized: string): void => {
|
||||
try {
|
||||
getDeferredSafeStorage().setItem(WORKTREE_MAP_STORAGE_KEY, serialized)
|
||||
} catch {
|
||||
// quota / serialization error — ignore; discovery still refreshes at runtime
|
||||
}
|
||||
}
|
||||
|
||||
const flattenWorktreeMap = (map: Map<string, WorktreeMetadata[]>): WorktreeMetadata[] => {
|
||||
const out: WorktreeMetadata[] = []
|
||||
for (const list of map.values()) out.push(...list)
|
||||
return out
|
||||
}
|
||||
|
||||
const PERSISTED_WORKTREE_MAP = loadPersistedWorktreeMap()
|
||||
const PERSISTED_WORKTREE_MAP = readPersistedWorktreeTopology(runtimeMemoryKey())
|
||||
|
||||
export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
||||
currentSessionId: null,
|
||||
@@ -660,6 +642,8 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
||||
sessionId: currentSessionId,
|
||||
directory,
|
||||
draft: cloneDraft(get().newSessionDraft),
|
||||
worktreeMetadata: new Map(get().worktreeMetadata),
|
||||
availableWorktreesByProject: new Map(get().availableWorktreesByProject),
|
||||
})
|
||||
},
|
||||
|
||||
@@ -669,6 +653,8 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
||||
const restoredSessionId = memory?.sessionId ?? activeSessionByRuntime.get(key) ?? null
|
||||
const restoredDraft = memory?.draft ? cloneDraft(memory.draft) : { ...DEFAULT_DRAFT }
|
||||
const restoredDirectory = memory?.directory ?? null
|
||||
const availableWorktreesByProject = memory?.availableWorktreesByProject
|
||||
?? readPersistedWorktreeTopology(key)
|
||||
if (restoredDirectory) {
|
||||
useDirectoryStore.getState().setDirectory(restoredDirectory, { showOverlay: false })
|
||||
}
|
||||
@@ -679,6 +665,9 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
||||
abortPromptSessionId: null,
|
||||
abortPromptExpiresAt: null,
|
||||
error: null,
|
||||
worktreeMetadata: memory?.worktreeMetadata ?? new Map(),
|
||||
availableWorktrees: flattenWorktreeMap(availableWorktreesByProject),
|
||||
availableWorktreesByProject,
|
||||
sessionAbortFlags: new Map(),
|
||||
pendingChangesBarDismissed: new Map(),
|
||||
})
|
||||
@@ -793,6 +782,23 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
||||
// closeNewSessionDraft
|
||||
// ---------------------------------------------------------------------------
|
||||
closeNewSessionDraft: () => {
|
||||
const currentDraft = get().newSessionDraft
|
||||
if (
|
||||
!currentDraft.open
|
||||
&& currentDraft.selectedProjectId == null
|
||||
&& currentDraft.directoryOverride == null
|
||||
&& currentDraft.pendingWorktreeRequestId == null
|
||||
&& currentDraft.bootstrapPendingDirectory == null
|
||||
&& !currentDraft.preserveDirectoryOverride
|
||||
&& currentDraft.parentID == null
|
||||
&& currentDraft.title === undefined
|
||||
&& currentDraft.initialPrompt === undefined
|
||||
&& currentDraft.syntheticParts === undefined
|
||||
&& currentDraft.targetFolderId === undefined
|
||||
&& currentDraft.permissionAutoAcceptEnabled === undefined
|
||||
) {
|
||||
return
|
||||
}
|
||||
const nextDraft: NewSessionDraftState = {
|
||||
open: false,
|
||||
selectedProjectId: null,
|
||||
@@ -1145,7 +1151,7 @@ export const useSessionUIStore = create<SessionUIState>()((set, get) => ({
|
||||
}
|
||||
|
||||
const currentSessionDirectory = targetSessionId
|
||||
? normalizePath(get().getDirectoryForSession(targetSessionId))
|
||||
? normalizePath(options?.directory ?? get().getDirectoryForSession(targetSessionId))
|
||||
: null
|
||||
if (targetSessionId) {
|
||||
notifyMessageSent(targetSessionId)
|
||||
@@ -1613,13 +1619,14 @@ setSessionOpener((sessionID, directory) => {
|
||||
// comparison avoids redundant localStorage writes when the Map reference
|
||||
// changed but the content is identical (e.g., re-discovery that found the
|
||||
// same worktrees).
|
||||
let lastPersistedWorktreeSerialized = ''
|
||||
const lastPersistedWorktreeSerializedByRuntime = new Map<string, string>()
|
||||
useSessionUIStore.subscribe((state, prev) => {
|
||||
if (state.availableWorktreesByProject !== prev.availableWorktreesByProject) {
|
||||
const runtimeKey = runtimeMemoryKey()
|
||||
const serialized = JSON.stringify([...state.availableWorktreesByProject.entries()])
|
||||
if (serialized !== lastPersistedWorktreeSerialized) {
|
||||
lastPersistedWorktreeSerialized = serialized
|
||||
persistWorktreeMap(serialized)
|
||||
if (serialized !== lastPersistedWorktreeSerializedByRuntime.get(runtimeKey)) {
|
||||
lastPersistedWorktreeSerializedByRuntime.set(runtimeKey, serialized)
|
||||
persistWorktreeTopology(runtimeKey, state.availableWorktreesByProject)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user