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
+44 -5
View File
@@ -6,7 +6,7 @@
* snapshot belongs in the reducer, which has access to the current state.
*
* Plain closure API:
* const { cleanup } = createEventPipeline({ sdk, onEvent })
* const { cleanup } = createEventPipeline({ sdk, onEvents })
*
* No class, no start/stop lifecycle. One pipeline per mount.
* Abort controller created once at init, cleaned up via returned cleanup fn.
@@ -19,6 +19,7 @@ import { clearRuntimeUrlAuthToken, refreshRuntimeUrlAuthToken } from "@/lib/runt
import { type RelayTunnelWebSocket } from "@/lib/relay/tunnel-client"
import { openRuntimeWebSocket } from "@/lib/relay/runtime-socket"
import { syncDebug } from "./debug"
import { countSyncPerformance } from "./performance-diagnostics"
const FLUSH_FRAME_MS = 33
const BACKPRESSURE_FLUSH_FRAME_MS = 200
@@ -38,9 +39,16 @@ const RETRY_BACKOFF_BASE_MS = 250
const RETRY_BACKOFF_CAP_VISIBLE_MS = 5_000
const RETRY_BACKOFF_CAP_HIDDEN_OR_OFFLINE_MS = 60_000
const RETRY_BACKOFF_MAX_EXPONENT = 8
type EventPipelineDelivery = {
onEvent: (directory: string, payload: Event) => void
onEvents?: never
} | {
onEvent?: never
onEvents: (directory: string, payloads: readonly Event[]) => void
}
export type EventPipelineInput = {
sdk: OpencodeClient
onEvent: (directory: string, payload: Event) => void
routeDirectory?: (directory: string, payload: Event) => string
/** Called after stream reconnects (visibility restore or heartbeat timeout). */
onReconnect?: () => void
@@ -52,7 +60,7 @@ export type EventPipelineInput = {
heartbeatTimeoutMs?: number
reconnectDelayMs?: number
wsReadyTimeoutMs?: number
}
} & EventPipelineDelivery
export type EventPipeline = {
cleanup: () => void
@@ -242,6 +250,7 @@ export function createEventPipeline(input: EventPipelineInput): EventPipeline {
const {
sdk,
onEvent,
onEvents,
onReconnect,
onDisconnect,
onTransportSwitch,
@@ -308,8 +317,13 @@ export function createEventPipeline(input: EventPipelineInput): EventPipeline {
d.last = Date.now()
syncDebug.pipeline.flush(events.length)
for (const payload of events) {
onEvent(directory, payload)
for (let index = 0; index < events.length; index += 1) {
countSyncPerformance("pipelineDeliveredEvents")
}
if (onEvents) {
onEvents(directory, events)
} else if (onEvent) {
for (const payload of events) onEvent(directory, payload)
}
d.buffer.length = 0
@@ -450,6 +464,7 @@ export function createEventPipeline(input: EventPipelineInput): EventPipeline {
}
const enqueueEvent = (directory: string, payload: Event) => {
countSyncPerformance("pipelineRawEvents")
const normalizedPayload = normalizeEventType(payload)
const routedDirectory = routeDirectory?.(directory, normalizedPayload) || directory
const d = getOrCreateDir(routedDirectory)
@@ -473,6 +488,29 @@ export function createEventPipeline(input: EventPipelineInput): EventPipeline {
}
}
if (
normalizedPayload.type === "session.idle"
|| normalizedPayload.type === "session.error"
|| normalizedPayload.type === "session.created"
|| normalizedPayload.type === "session.deleted"
) {
const properties = normalizedPayload.properties as {
sessionID?: unknown
info?: { id?: unknown }
}
const sessionID = typeof properties.sessionID === "string"
? properties.sessionID
: typeof properties.info?.id === "string"
? properties.info.id
: undefined
if (sessionID) {
d.coalesced.delete(`session.status:${sessionID}`)
if (normalizedPayload.type === "session.created" || normalizedPayload.type === "session.deleted") {
d.coalesced.delete(`session.updated:${sessionID}`)
}
}
}
const k = key(normalizedPayload)
if (k) {
const i = d.coalesced.get(k)
@@ -490,6 +528,7 @@ export function createEventPipeline(input: EventPipelineInput): EventPipeline {
} else {
d.queue[i] = normalizedPayload
}
countSyncPerformance("pipelineCoalescedEvents")
syncDebug.pipeline.coalesced(normalizedPayload.type, k)
return
}