Removing a worktree archived its sessions one SDK call at a time and then re-rendered the whole sidebar once per streamed session.updated echo. On a worktree with 121 sessions that meant 14.8s of main-thread work, 121 requests, and 328 localStorage writes. - Add POST /api/openchamber/sessions/archive: validates a batch (max 500 ids, per-request archivedAt), archives sequentially, and reports partial failures instead of dropping the batch. VS Code serves no such route and answers 501; the shared UI then falls back to the per-session path. - Plan batches from the sessions this client actually holds, live directory stores first, so worktree-only sessions still batch. - Claim (id, archivedAt) pairs before the request and consume the matching session.updated echoes, so the server's own confirmations no longer fan out into 121 store publications. Runtime-scoped, TTL 30s, released on response or fallback; non-matching updates pass. - Make the managed-chats persistence a real trailing debounce instead of a 50ms throttle, so a burst of publications coalesces into one localStorage write. Benchmark (121 sessions, production build, real Chrome): 14785ms -> ~1030ms, long tasks 100 -> 1, global store publications 236 -> 1, persistence writes 328 -> 3.
50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
import type { Event } from "@opencode-ai/sdk/v2/client"
|
|
import { subscribeRuntimeEndpointWillChange } from "@/lib/runtime-switch"
|
|
|
|
const BULK_ARCHIVE_ECHO_TTL_MS = 30_000
|
|
const pendingEchoes = new Map<string, Map<string, { archivedAt: number; expiresAt: number }>>()
|
|
|
|
subscribeRuntimeEndpointWillChange(() => pendingEchoes.clear())
|
|
|
|
export const registerBulkArchiveEchoes = (
|
|
runtimeKey: string,
|
|
sessions: Iterable<{ id: string; archivedAt: number }>,
|
|
now = Date.now(),
|
|
): void => {
|
|
let runtimeEchoes = pendingEchoes.get(runtimeKey)
|
|
if (!runtimeEchoes) {
|
|
runtimeEchoes = new Map()
|
|
pendingEchoes.set(runtimeKey, runtimeEchoes)
|
|
}
|
|
for (const session of sessions) {
|
|
runtimeEchoes.set(session.id, {
|
|
archivedAt: session.archivedAt,
|
|
expiresAt: now + BULK_ARCHIVE_ECHO_TTL_MS,
|
|
})
|
|
}
|
|
}
|
|
|
|
export const releaseBulkArchiveEchoes = (runtimeKey: string, sessionIds: Iterable<string>): void => {
|
|
const runtimeEchoes = pendingEchoes.get(runtimeKey)
|
|
if (!runtimeEchoes) return
|
|
for (const sessionId of sessionIds) runtimeEchoes.delete(sessionId)
|
|
if (runtimeEchoes.size === 0) pendingEchoes.delete(runtimeKey)
|
|
}
|
|
|
|
export const shouldConsumeBulkArchiveEcho = (
|
|
event: Event,
|
|
runtimeKey: string,
|
|
now = Date.now(),
|
|
): boolean => {
|
|
if (event.type !== "session.updated") return false
|
|
const runtimeEchoes = pendingEchoes.get(runtimeKey)
|
|
const expected = runtimeEchoes?.get(event.properties.info.id)
|
|
if (!expected) return false
|
|
if (expected.expiresAt < now) {
|
|
runtimeEchoes?.delete(event.properties.info.id)
|
|
if (runtimeEchoes?.size === 0) pendingEchoes.delete(runtimeKey)
|
|
return false
|
|
}
|
|
return event.properties.info.time.archived === expected.archivedAt
|
|
}
|