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
@@ -12,7 +12,10 @@ let questionRejectError: unknown | null = null
|
||||
let sessionShareResult: { data?: unknown; error?: unknown; response?: { status?: number } } = {}
|
||||
let sessionUpdateResult: { data?: unknown; error?: unknown; response?: { status?: number } } = {}
|
||||
let sessionMessagesResult: { data?: unknown; error?: unknown; response?: { status?: number } } = { data: [] }
|
||||
let sessionDeleteError: unknown | null = null
|
||||
const globalUpsertedSessions: unknown[] = []
|
||||
const globalRemovedSessionIds: string[] = []
|
||||
const deletedCleanupIdentities: Array<{ runtimeKey: string; directory: string; sessionId: string }> = []
|
||||
const movedSessionDirectories: Array<{ sessionID: string; directory: string }> = []
|
||||
|
||||
const mockScopedClient = {
|
||||
@@ -135,6 +138,11 @@ mock.module("@/lib/opencode/client", () => ({
|
||||
replyCalls.push({ method: "session.update", params: { sessionID: sessionId, ...changes, directory } })
|
||||
return Promise.resolve(sessionUpdateResult.data)
|
||||
}),
|
||||
deleteSession: mock((sessionId: string, directory?: string | null) => {
|
||||
replyCalls.push({ method: "session.delete", params: { sessionID: sessionId, directory } })
|
||||
if (sessionDeleteError) throw sessionDeleteError
|
||||
return Promise.resolve(true)
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -157,6 +165,9 @@ mock.module("./session-ui-store", () => ({
|
||||
if (sessionId === "session-b") return "/other/project"
|
||||
return null
|
||||
},
|
||||
currentSessionId: null,
|
||||
setCurrentSession: () => {},
|
||||
setWorktreeMetadata: () => {},
|
||||
setSessionDirectory: (sessionID: string, directory: string) => {
|
||||
movedSessionDirectories.push({ sessionID, directory })
|
||||
},
|
||||
@@ -185,6 +196,7 @@ mock.module("./input-store", () => ({
|
||||
}))
|
||||
|
||||
mock.module("@/stores/useGlobalSessionsStore", () => ({
|
||||
resolveGlobalSessionDirectory: (session: SessionWithDirectory) => session.directory ?? session.project?.worktree ?? null,
|
||||
mergeSessionDirectoryMetadata: (incoming: Session, existing?: SessionWithDirectory | null): SessionWithDirectory => {
|
||||
if (!existing) return incoming as SessionWithDirectory
|
||||
const next = { ...(incoming as SessionWithDirectory) }
|
||||
@@ -197,13 +209,24 @@ mock.module("@/stores/useGlobalSessionsStore", () => ({
|
||||
},
|
||||
useGlobalSessionsStore: {
|
||||
getState: () => ({
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
upsertSession: (session: unknown) => {
|
||||
globalUpsertedSessions.push(session)
|
||||
},
|
||||
removeSessions: (ids: Iterable<string>) => {
|
||||
globalRemovedSessionIds.push(...ids)
|
||||
},
|
||||
}),
|
||||
},
|
||||
}))
|
||||
|
||||
mock.module("./session-deletion-cleanup", () => ({
|
||||
cleanupPersistedSessionState: (identity: { runtimeKey: string; directory: string; sessionId: string }) => {
|
||||
deletedCleanupIdentities.push(identity)
|
||||
},
|
||||
}))
|
||||
|
||||
mock.module("./sync-refs", () => ({
|
||||
registerSessionDirectory: (sessionID: string, directory: string) => {
|
||||
registeredSessionDirectories.push({ sessionID, directory })
|
||||
@@ -328,6 +351,75 @@ describe("moveSessionToDirectory", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("confirmed session removal", () => {
|
||||
beforeEach(() => {
|
||||
replyCalls.length = 0
|
||||
globalUpsertedSessions.length = 0
|
||||
globalRemovedSessionIds.length = 0
|
||||
deletedCleanupIdentities.length = 0
|
||||
sessionDeleteError = null
|
||||
sessionUpdateResult = {}
|
||||
})
|
||||
|
||||
test("does not remove live or persisted state when delete fails", async () => {
|
||||
sessionDeleteError = new Error("delete failed")
|
||||
const source = createStore({}, {
|
||||
session: [{ id: "session-a", directory: "/test/project", time: { created: 1 } } as Session],
|
||||
})
|
||||
const { deleteSession, setActionRefs } = await import("./session-actions")
|
||||
setActionRefs(mockSdk as unknown as OpencodeClient, createChildStores([["/test/project", source]]), () => "/test/project")
|
||||
|
||||
expect(await deleteSession("session-a")).toBe(false)
|
||||
expect(source.getState().session.map((item) => item.id)).toEqual(["session-a"])
|
||||
expect(globalRemovedSessionIds).toEqual([])
|
||||
expect(deletedCleanupIdentities).toEqual([])
|
||||
})
|
||||
|
||||
test("cleans persisted state after the server confirms deletion", async () => {
|
||||
const source = createStore({}, {
|
||||
session: [{ id: "session-a", directory: "/test/project", time: { created: 1 } } as Session],
|
||||
})
|
||||
const { deleteSession, setActionRefs } = await import("./session-actions")
|
||||
setActionRefs(mockSdk as unknown as OpencodeClient, createChildStores([["/test/project", source]]), () => "/test/project")
|
||||
|
||||
expect(await deleteSession("session-a")).toBe(true)
|
||||
expect(source.getState().session).toEqual([])
|
||||
expect(globalRemovedSessionIds).toEqual(["session-a"])
|
||||
expect(deletedCleanupIdentities).toHaveLength(1)
|
||||
expect({
|
||||
directory: deletedCleanupIdentities[0]?.directory,
|
||||
sessionId: deletedCleanupIdentities[0]?.sessionId,
|
||||
}).toEqual({ directory: "/test/project", sessionId: "session-a" })
|
||||
})
|
||||
|
||||
test("does not archive locally until the server returns the archived session", async () => {
|
||||
const source = createStore({}, {
|
||||
session: [{ id: "session-a", directory: "/test/project", time: { created: 1 } } as Session],
|
||||
})
|
||||
const { archiveSession, setActionRefs } = await import("./session-actions")
|
||||
setActionRefs(mockSdk as unknown as OpencodeClient, createChildStores([["/test/project", source]]), () => "/test/project")
|
||||
|
||||
expect(await archiveSession("session-a")).toBe(false)
|
||||
expect(source.getState().session.map((item) => item.id)).toEqual(["session-a"])
|
||||
expect(globalUpsertedSessions).toEqual([])
|
||||
})
|
||||
|
||||
test("moves the session to archived state after server confirmation", async () => {
|
||||
sessionUpdateResult = {
|
||||
data: { id: "session-a", directory: "/test/project", time: { created: 1, archived: 2 } } as Session,
|
||||
}
|
||||
const source = createStore({}, {
|
||||
session: [{ id: "session-a", directory: "/test/project", time: { created: 1 } } as Session],
|
||||
})
|
||||
const { archiveSession, setActionRefs } = await import("./session-actions")
|
||||
setActionRefs(mockSdk as unknown as OpencodeClient, createChildStores([["/test/project", source]]), () => "/test/project")
|
||||
|
||||
expect(await archiveSession("session-a")).toBe(true)
|
||||
expect(source.getState().session).toEqual([])
|
||||
expect((globalUpsertedSessions[0] as Session)?.time?.archived).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("fetchMessagesForSession startup race", () => {
|
||||
test("does not reject before sync action refs are initialized", async () => {
|
||||
const { fetchMessagesForSession } = await import("./session-actions")
|
||||
|
||||
Reference in New Issue
Block a user