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
160 lines
5.2 KiB
TypeScript
160 lines
5.2 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
import type { Message, Part, SessionStatus } from "@opencode-ai/sdk/v2/client"
|
|
import type { Session } from "@opencode-ai/sdk/v2"
|
|
import { getReconnectCandidateSessionIds, mergeBootstrapSessions } from "./reconnect-recovery"
|
|
|
|
function createSession(id: string, overrides: Partial<Session> = {}): Session {
|
|
return {
|
|
id,
|
|
title: id,
|
|
time: { created: 1, updated: 1 },
|
|
version: "1",
|
|
...overrides,
|
|
} as Session
|
|
}
|
|
|
|
function createAssistantMessage(id: string, sessionID: string, completed?: number): Message {
|
|
return {
|
|
id,
|
|
sessionID,
|
|
role: "assistant",
|
|
time: completed ? { created: 1, updated: 1, completed } : { created: 1, updated: 1 },
|
|
parts: [],
|
|
} as unknown as Message
|
|
}
|
|
|
|
function createPart(id: string, messageID: string): Part {
|
|
return { id, messageID, sessionID: "active", type: "text", text: "done" } as Part
|
|
}
|
|
|
|
describe("getReconnectCandidateSessionIds", () => {
|
|
test("includes non-idle, incomplete assistant, and parent sessions", () => {
|
|
const busyStatus = { type: "busy" } as SessionStatus
|
|
|
|
expect(getReconnectCandidateSessionIds({
|
|
session: [
|
|
createSession("busy"),
|
|
createSession("child", { parentID: "parent" }),
|
|
createSession("parent"),
|
|
createSession("incomplete"),
|
|
],
|
|
session_status: { busy: busyStatus },
|
|
message: {
|
|
incomplete: [createAssistantMessage("m-1", "incomplete")],
|
|
},
|
|
}).sort()).toEqual(["busy", "incomplete", "parent"])
|
|
})
|
|
|
|
test("includes the currently viewed session even when it looks idle and complete", () => {
|
|
expect(getReconnectCandidateSessionIds({
|
|
session: [createSession("active")],
|
|
session_status: { active: { type: "idle" } as SessionStatus },
|
|
message: {
|
|
active: [createAssistantMessage("m-1", "active", 1)],
|
|
},
|
|
part: {
|
|
"m-1": [createPart("p-1", "m-1")],
|
|
},
|
|
}, {
|
|
directory: "/repo",
|
|
viewedSession: { directory: "/repo", sessionId: "active" },
|
|
}).sort()).toContain("active")
|
|
})
|
|
|
|
test("includes completed assistant sessions when the latest assistant parts are missing", () => {
|
|
expect(getReconnectCandidateSessionIds({
|
|
session: [createSession("blank")],
|
|
session_status: { blank: { type: "idle" } as SessionStatus },
|
|
message: {
|
|
blank: [createAssistantMessage("m-1", "blank", 1)],
|
|
},
|
|
part: {},
|
|
})).toEqual(["blank"])
|
|
})
|
|
|
|
test("does not include a viewed session from another directory", () => {
|
|
expect(getReconnectCandidateSessionIds({
|
|
session: [createSession("active")],
|
|
session_status: { active: { type: "idle" } as SessionStatus },
|
|
message: {
|
|
active: [createAssistantMessage("m-1", "active", 1)],
|
|
},
|
|
part: {
|
|
"m-1": [createPart("p-1", "m-1")],
|
|
},
|
|
}, {
|
|
directory: "/repo-a",
|
|
viewedSession: { directory: "/repo-b", sessionId: "active" },
|
|
}).sort()).not.toContain("active")
|
|
})
|
|
})
|
|
|
|
describe("mergeBootstrapSessions", () => {
|
|
test("recovers a referenced parent when the roots response is temporarily empty", () => {
|
|
const parent = createSession("parent")
|
|
const child = createSession("child", { parentID: "parent" })
|
|
|
|
expect(mergeBootstrapSessions([], [child], [parent])).toEqual({
|
|
sessions: [child, parent],
|
|
rootCount: 1,
|
|
})
|
|
})
|
|
|
|
test("recovers referenced parents from the broader response without retaining stale roots", () => {
|
|
const parent = createSession("parent")
|
|
const stale = createSession("stale")
|
|
const child = createSession("child", { parentID: "parent" })
|
|
|
|
expect(mergeBootstrapSessions([], [parent, child], [stale])).toEqual({
|
|
sessions: [child, parent],
|
|
rootCount: 1,
|
|
})
|
|
})
|
|
|
|
test("treats a successful empty response as authoritative", () => {
|
|
const persisted = createSession("persisted")
|
|
|
|
expect(mergeBootstrapSessions([], [], [persisted])).toEqual({
|
|
sessions: [],
|
|
rootCount: 0,
|
|
})
|
|
})
|
|
|
|
test("preserves known children when the child-session request fails", () => {
|
|
const cachedParent = createSession("parent")
|
|
const authoritativeParent = createSession("parent", { title: "Current" })
|
|
const cachedChild = createSession("child", { parentID: "parent" })
|
|
|
|
expect(mergeBootstrapSessions([authoritativeParent], null, [cachedChild, cachedParent])).toEqual({
|
|
sessions: [cachedChild, authoritativeParent],
|
|
rootCount: 1,
|
|
})
|
|
})
|
|
|
|
test("overlays live session events that arrive after the request starts", () => {
|
|
const staleResponse = createSession("existing", { title: "Stale" })
|
|
const liveUpdate = createSession("existing", { title: "Live" })
|
|
const liveCreate = createSession("new")
|
|
|
|
expect(mergeBootstrapSessions([staleResponse], [], [liveUpdate, liveCreate], {
|
|
baselineRevision: 4,
|
|
eventRevision: { existing: 5, new: 6 },
|
|
})).toEqual({
|
|
sessions: [liveUpdate, liveCreate],
|
|
rootCount: 2,
|
|
})
|
|
})
|
|
|
|
test("does not resurrect a session deleted after the request starts", () => {
|
|
const deleted = createSession("deleted")
|
|
|
|
expect(mergeBootstrapSessions([deleted], [], [], {
|
|
baselineRevision: 2,
|
|
deletedRevision: { deleted: 3 },
|
|
})).toEqual({
|
|
sessions: [],
|
|
rootCount: 0,
|
|
})
|
|
})
|
|
})
|