import { DatabaseSync } from "node:sqlite"; import { ReplicaCache } from "@/runtime/replica-cache"; import { createSqliteReplicaRowStore, type ReplicaSqliteConnection, type SqliteValue, } from "@/runtime/replica-cache/row-store-sqlite"; import { afterEach, describe, expect, it } from "vitest"; import type { DaemonClient, WorkspaceLabelListPayload, } from "@getpaseo/client/internal/daemon-client"; import type { SessionOutboundMessage } from "@getpaseo/protocol/messages"; import { normalizeProjectDescriptor, normalizeWorkspaceDescriptor, useSessionStore, } from "@/stores/session-store"; import { normalizeAgentSnapshot } from "@/utils/agent-snapshots"; import { selectWorkspaceDirectoryServerIds } from "@/stores/session-store-hooks/selectors"; import { useWorkspaceLabels } from "@/workspace-labels"; import type { DirectoryReplicaMutation } from "@/runtime/replica-cache"; import { DirectoryRefreshSupersededError, DirectorySync, type DirectoryCheckpointStorage, } from "./index"; import { subscriptionFixture } from "../subscription-fixture"; type WorkspaceFetchResult = Awaited>; type ProjectListResult = Awaited>; type AgentFetchResult = Awaited>; class FakeDirectoryClient { supportsBackgroundWorkspaces(): boolean { return false; } supportsWorkspaceLabels = false; listWorkspaceLabelsCalls = 0; fetchAgentsCalls = 0; lastAgentOptions: unknown; fetchWorkspacesCalls = 0; lastWorkspaceOptions: unknown; listProjectsCalls = 0; lastProjectOptions: unknown; projectResult: ProjectListResult | null = null; private pendingAgentFetch: Promise | null = null; private pendingWorkspaceFetch: Promise | null = null; private pendingProjectFetch: Promise | null = null; private readonly handlers = new Map< SessionOutboundMessage["type"], Set<(message: SessionOutboundMessage) => void> >(); on( type: TType, handler: (message: Extract) => void, ): () => void { const handlers = this.handlers.get(type) ?? new Set(); const registered = handler as unknown as (message: SessionOutboundMessage) => void; handlers.add(registered); this.handlers.set(type, handlers); return () => handlers.delete(registered); } emit( message: Extract, ): void { for (const handler of this.handlers.get(message.type) ?? []) handler(message); } holdWorkspaceFetch(): (result: WorkspaceFetchResult) => void { let complete!: (result: WorkspaceFetchResult) => void; this.pendingWorkspaceFetch = new Promise((resolve) => { complete = resolve; }); return complete; } holdAgentFetch(): (result: AgentFetchResult) => void { let complete!: (result: AgentFetchResult) => void; this.pendingAgentFetch = new Promise((resolve) => { complete = resolve; }); return complete; } holdProjectFetch(): (result: ProjectListResult) => void { let complete!: (result: ProjectListResult) => void; this.pendingProjectFetch = new Promise((resolve) => { complete = resolve; }); return complete; } async fetchAgents(options?: unknown): Promise>> { this.fetchAgentsCalls += 1; this.lastAgentOptions = options; if (this.pendingAgentFetch) { const pending = this.pendingAgentFetch; this.pendingAgentFetch = null; return pending; } return { requestId: "agents", entries: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, }; } async fetchWorkspaces(options?: unknown): Promise { this.fetchWorkspacesCalls += 1; this.lastWorkspaceOptions = options; if (this.pendingWorkspaceFetch) { const pending = this.pendingWorkspaceFetch; this.pendingWorkspaceFetch = null; return pending; } return { requestId: "workspaces", entries: [], emptyProjects: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, }; } async listProjects(options?: unknown): Promise { this.listProjectsCalls += 1; this.lastProjectOptions = options; if (this.pendingProjectFetch) { const pending = this.pendingProjectFetch; this.pendingProjectFetch = null; return pending; } if (this.projectResult) return this.projectResult; return { requestId: "projects", projects: [ { projectId: "project-1", projectKey: "remote:github.com/acme/app", projectDisplayName: "acme/app", projectRootPath: "/repo/app", projectKind: "git", }, ], }; } observeAgents(options: Parameters[0]) { return subscriptionFixture(this.fetchAgents({ ...options, subscribe: {} }), (receive) => this.on("agent_update", receive), ); } observeWorkspaces(options: Parameters[0]) { return subscriptionFixture(this.fetchWorkspaces(options), (receive) => this.on("workspace_update", receive), ); } observeEvents(events: readonly SessionOutboundMessage["type"][]) { return subscriptionFixture(Promise.resolve({ events }), (receive) => { const stops = events.map((event) => this.on(event, receive)); return () => stops.forEach((stop) => stop()); }); } observeWorkspaceLabels() { return subscriptionFixture(this.listWorkspaceLabels(), () => () => {}); } async listWorkspaceLabels(): Promise { this.listWorkspaceLabelsCalls += 1; return { requestId: "workspace-labels", labels: [{ name: "Urgent", color: "red" }], sync: { mode: "snapshot", removals: [], generation: "generation-1", headSeq: 0 }, }; } getLastServerInfoMessage() { return this.supportsWorkspaceLabels ? { features: { workspaceLabels: true } } : null; } } const serverIds = new Set(); function createDirectory( serverId: string, options?: { workspaceLabels?: boolean }, ): { client: FakeDirectoryClient; directory: DirectorySync; } { serverIds.add(serverId); const client = new FakeDirectoryClient(); client.supportsWorkspaceLabels = options?.workspaceLabels === true; const directory = new DirectorySync(serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); return { client, directory }; } function createAgent(serverId: string, id: string) { return { ...normalizeAgentSnapshot( { id, provider: "codex", cwd: "/repo", model: null, createdAt: "2026-08-26T00:00:00.000Z", updatedAt: "2026-08-26T00:00:00.000Z", lastUserMessageAt: null, status: "idle", capabilities: { supportsStreaming: true, supportsSessionPersistence: true, supportsDynamicModes: true, supportsMcpServers: true, supportsReasoningStream: true, supportsToolInvocations: true, }, currentModeId: null, availableModes: [], pendingPermissions: [], persistence: null, title: "Cached", labels: {}, }, serverId, ), projectPlacement: null, }; } function createWorkspaceEntry(id: string): WorkspaceFetchResult["entries"][number] { return { id, projectId: "snapshot-project", projectDisplayName: "Snapshot project", projectRootPath: "/repo", workspaceDirectory: `/repo/${id}`, projectKind: "git", workspaceKind: "local_checkout", name: id, status: "done", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], }; } afterEach(() => { for (const serverId of serverIds) useSessionStore.getState().clearSession(serverId); serverIds.clear(); useWorkspaceLabels.setState({ hosts: {} }); }); describe("DirectorySync session readiness", () => { it("restores the cached directory before network demand", async () => { const serverId = "offline-cached-directory"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const cachedWorkspace = normalizeWorkspaceDescriptor({ id: "cached-workspace", projectId: "cached-project", projectDisplayName: "Cached project", projectRootPath: "/repo/cached", workspaceDirectory: "/repo/cached", projectKind: "git", workspaceKind: "local_checkout", name: "cached", status: "done", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], }); const cachedProject = normalizeProjectDescriptor({ projectId: "cached-project", projectDisplayName: "Cached project", projectRootPath: "/repo/cached", projectKind: "git", }); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map(), workspaces: new Map([[cachedWorkspace.id, cachedWorkspace]]), projects: new Map([[cachedProject.projectId, cachedProject]]), }), commitDirectoryMutations: () => undefined, }, ); useSessionStore.getState().initializeSession(serverId, client as unknown as DaemonClient, 1); await directory.restoreCachedDirectory(); expect( useSessionStore.getState().sessions[serverId]?.workspaces.get(cachedWorkspace.id), ).toEqual(cachedWorkspace); expect(selectWorkspaceDirectoryServerIds(useSessionStore.getState(), [serverId])).toEqual([ serverId, ]); expect(client.fetchAgentsCalls).toBe(0); expect(client.fetchWorkspacesCalls).toBe(0); directory.dispose(); }); it("prepares a cached agent workspace route without sidebar demand", async () => { const serverId = "cached-agent-route"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const cachedAgent = createAgent(serverId, "agent-1"); const cachedWorkspace = normalizeWorkspaceDescriptor({ id: "workspace-1", projectId: "project-1", projectDisplayName: "Paseo", projectRootPath: "/repo", workspaceDirectory: "/repo", projectKind: "git", workspaceKind: "local_checkout", name: "main", status: "done", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], }); cachedAgent.workspaceId = cachedWorkspace.id; const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => cachedAgent, readWorkspace: async () => ({ workspace: cachedWorkspace }), readDirectory: async () => ({ agents: new Map(), workspaces: new Map(), projects: new Map(), }), commitDirectoryMutations: () => undefined, }, ); useSessionStore.getState().initializeSession(serverId, client as unknown as DaemonClient, 1); await directory.prepareAgentRoute(cachedAgent.id); const session = useSessionStore.getState().sessions[serverId]; expect(session?.hasHydratedAgents).toBe(false); expect(session?.hasWorkspaceDirectorySnapshot).toBe(false); expect(session?.agents.get(cachedAgent.id)).toEqual(cachedAgent); expect(session?.workspaces.get(cachedWorkspace.id)).toEqual(cachedWorkspace); expect(client.fetchAgentsCalls).toBe(0); expect(client.fetchWorkspacesCalls).toBe(0); directory.dispose(); }); it("persists accepted script status updates through the directory owner", async () => { const serverId = "script-status-owner"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const commits: DirectoryReplicaMutation[][] = []; const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map(), workspaces: new Map(), projects: new Map(), }), commitDirectoryMutations: (_serverId, mutations) => commits.push([...mutations]), }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const workspace = normalizeWorkspaceDescriptor({ id: "workspace-1", projectId: "project-1", projectDisplayName: "Paseo", projectRootPath: "/repo", workspaceDirectory: "/repo", projectKind: "git", workspaceKind: "local_checkout", name: "main", status: "running", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); directory.setDemand({}, true); await directory.refreshDemand(); directory.acceptWorkspaces([workspace]); commits.length = 0; client.emit({ type: "script_status_update", payload: { workspaceId: workspace.id, scripts: [ { scriptName: "web", type: "service", hostname: "web.paseo.localhost", port: 3000, proxyUrl: "http://web.paseo.localhost:6767", lifecycle: "running", health: "healthy", exitCode: null, terminalId: null, }, ], }, }); expect( useSessionStore.getState().sessions[serverId]?.workspaces.get(workspace.id)?.scripts[0] ?.lifecycle, ).toBe("running"); const persisted = commits .flat() .find( ( mutation, ): mutation is Extract => mutation.kind === "workspace" && mutation.type === "upsert" && mutation.id === workspace.id, ); expect(persisted?.value.scripts[0]?.lifecycle).toBe("running"); directory.dispose(); }); it("subscribes to live agent updates when the directory is demanded", async () => { const serverId = "demanded-directory-subscription"; const { client, directory } = createDirectory(serverId); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); await directory.refreshAll(); expect(client.lastAgentOptions).toMatchObject({ subscribe: {} }); directory.dispose(); }); it("coalesces overlapping route and full-directory demand", async () => { const serverId = "coalesced-directory-demand"; const { client, directory } = createDirectory(serverId); const releaseAgents = client.holdAgentFetch(); const releaseWorkspaces = client.holdWorkspaceFetch(); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); directory.setAgentRouteDemand(["agent-1"]); directory.setDemand({}, true); await expect.poll(() => client.fetchAgentsCalls).toBe(1); await expect.poll(() => client.fetchWorkspacesCalls).toBe(1); releaseAgents({ requestId: "agents", entries: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, }); releaseWorkspaces({ requestId: "workspaces", entries: [], emptyProjects: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, }); await directory.refreshDemand(); expect(client.fetchAgentsCalls).toBe(1); expect(client.fetchWorkspacesCalls).toBe(1); directory.dispose(); }); it("publishes cached directory data before the authoritative request completes", async () => { const serverId = "cached-directory"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const releaseNetwork = client.holdWorkspaceFetch(); let cacheReads = 0; const cachedWorkspace = normalizeWorkspaceDescriptor({ id: "cached-workspace", projectId: "cached-project", projectDisplayName: "Cached project", projectRootPath: "/repo/cached", workspaceDirectory: "/repo/cached", projectKind: "git", workspaceKind: "local_checkout", name: "cached", status: "done", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], }); const cachedProject = normalizeProjectDescriptor({ projectId: "cached-project", projectDisplayName: "Cached project", projectRootPath: "/repo/cached", projectKind: "git", }); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: async () => { cacheReads += 1; return { agents: new Map(), workspaces: new Map([[cachedWorkspace.id, cachedWorkspace]]), projects: new Map([[cachedProject.projectId, cachedProject]]), }; }, commitDirectoryMutations: () => undefined, }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); const refresh = directory.refreshWorkspaces(); await expect.poll(() => client.fetchWorkspacesCalls).toBe(1); expect(cacheReads).toBe(1); expect( useSessionStore.getState().sessions[serverId]?.workspaces.get("cached-workspace")?.name, ).toBe("cached"); releaseNetwork({ requestId: "workspaces", entries: [], emptyProjects: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, }); await refresh; expect(useSessionStore.getState().sessions[serverId]?.workspaces.size).toBe(0); directory.dispose(); }); it("reconciles workspace changes on top of the accepted cached baseline", async () => { const serverId = "cached-workspace-changes"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const cachedWorkspace = normalizeWorkspaceDescriptor({ id: "cached-workspace", projectId: "cached-project", projectDisplayName: "Cached project", projectRootPath: "/repo/cached", workspaceDirectory: "/repo/cached", projectKind: "git", workspaceKind: "local_checkout", name: "cached", status: "done", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], }); const cachedProject = normalizeProjectDescriptor({ projectId: "cached-project", projectDisplayName: "Cached project", projectRootPath: "/repo/cached", projectKind: "git", }); const releaseNetwork = client.holdWorkspaceFetch(); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map(), workspaces: new Map([[cachedWorkspace.id, cachedWorkspace]]), projects: new Map([[cachedProject.projectId, cachedProject]]), checkpoint: { workspaces: { generation: "g", afterSeq: 7 } }, }), commitDirectoryMutations: () => undefined, }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true, directorySync: true }, }); const refresh = directory.refreshWorkspaces(); await expect.poll(() => client.fetchWorkspacesCalls).toBe(1); releaseNetwork({ requestId: "workspaces", entries: [], emptyProjects: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, sync: { generation: "g", headSeq: 7, mode: "changes", removals: [] }, }); await refresh; expect(client.lastWorkspaceOptions).toMatchObject({ sync: { generation: "g", afterSeq: 7 }, }); expect(useSessionStore.getState().sessions[serverId]?.workspaces.has(cachedWorkspace.id)).toBe( true, ); directory.dispose(); }); it("reconciles agent changes on top of the accepted cached baseline", async () => { const serverId = "cached-agent-changes"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const cachedAgent = createAgent(serverId, "cached-agent"); const releaseNetwork = client.holdAgentFetch(); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map([[cachedAgent.id, cachedAgent]]), workspaces: new Map(), projects: new Map(), checkpoint: { agents: { generation: "g", afterSeq: 7 } }, }), commitDirectoryMutations: () => undefined, }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true, directorySync: true }, }); const refresh = directory.refreshAgents(); await expect.poll(() => client.fetchAgentsCalls).toBe(1); releaseNetwork({ requestId: "agents", entries: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, sync: { generation: "g", headSeq: 7, mode: "changes", removals: [] }, }); await refresh; expect(client.lastAgentOptions).toMatchObject({ sync: { generation: "g", afterSeq: 7 }, }); expect(useSessionStore.getState().sessions[serverId]?.agents.has(cachedAgent.id)).toBe(true); directory.dispose(); }); it("does not use a cached checkpoint when the corresponding cache read loses its race", async () => { const serverId = "late-directory-cache"; serverIds.add(serverId); const client = new FakeDirectoryClient(); let releaseCache!: ( value: Awaited>, ) => void; const cacheRead = new Promise>>( (resolve) => { releaseCache = resolve; }, ); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: () => cacheRead, commitDirectoryMutations: () => undefined, }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true, directorySync: true }, }); directory.setAgentRouteDemand(["agent-1"]); await directory.refreshDemand(); const refresh = directory.refreshWorkspaces(); await Promise.resolve(); client.emit({ type: "workspace_update", payload: { kind: "remove", id: "deleted-live" }, }); releaseCache({ agents: new Map(), workspaces: new Map(), projects: new Map(), checkpoint: { workspaces: { generation: "stale", afterSeq: 99 } }, }); await refresh; expect(client.lastWorkspaceOptions).not.toHaveProperty("sync.generation"); directory.dispose(); }); it("does not resurrect an agent deleted while its targeted cache row is loading", async () => { const serverId = "late-agent-cache"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const cachedAgent = { ...normalizeAgentSnapshot( { id: "agent-1", provider: "codex", cwd: "/repo", model: null, createdAt: "2026-08-26T00:00:00.000Z", updatedAt: "2026-08-26T00:00:00.000Z", lastUserMessageAt: null, status: "idle", capabilities: { supportsStreaming: true, supportsSessionPersistence: true, supportsDynamicModes: true, supportsMcpServers: true, supportsReasoningStream: true, supportsToolInvocations: true, }, currentModeId: null, availableModes: [], pendingPermissions: [], persistence: null, title: "Cached", labels: {}, }, serverId, ), projectPlacement: null, }; let releaseAgent!: (agent: typeof cachedAgent) => void; const agentRead = new Promise((resolve) => { releaseAgent = resolve; }); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: () => agentRead, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map(), workspaces: new Map(), projects: new Map(), }), commitDirectoryMutations: () => undefined, }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); useSessionStore.getState().initializeSession(serverId, client as unknown as DaemonClient, 1); const load = directory.loadCachedAgent(cachedAgent.id); directory.removeAgent(cachedAgent.id); releaseAgent(cachedAgent); await load; expect(useSessionStore.getState().sessions[serverId]?.agents.has(cachedAgent.id)).toBe(false); directory.dispose(); }); it("does not resurrect an agent removed by an authoritative snapshot while cache loads", async () => { const serverId = "late-agent-authoritative-snapshot"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const cachedAgent = createAgent(serverId, "agent-1"); const releaseNetwork = client.holdAgentFetch(); let releaseAgent!: (agent: typeof cachedAgent) => void; const agentRead = new Promise((resolve) => { releaseAgent = resolve; }); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: () => agentRead, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map(), workspaces: new Map(), projects: new Map(), }), commitDirectoryMutations: () => undefined, }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { directorySync: true, workspaceMultiplicity: true }, }); const refresh = directory.refreshAgents(); const load = directory.loadCachedAgent(cachedAgent.id); releaseNetwork({ requestId: "authoritative-snapshot", entries: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, }); await refresh; releaseAgent(cachedAgent); await load; expect(useSessionStore.getState().sessions[serverId]?.agents.has(cachedAgent.id)).toBe(false); directory.dispose(); }); it("uses the saved agent sequence while bounding the bootstrap page", async () => { const serverId = "agent-list-sequence-page"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map(), workspaces: new Map(), projects: new Map(), checkpoint: { agents: { generation: "generation", afterSeq: 12 } }, }), commitDirectoryMutations: () => undefined, }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { directorySync: true, workspaceMultiplicity: true }, }); await directory.refreshAgents({ subscribe: {}, page: { limit: 200 }, }); expect(client.lastAgentOptions).toEqual({ scope: "active", sort: [{ key: "updated_at", direction: "desc" }], subscribe: {}, page: { limit: 200 }, sync: { generation: "generation", afterSeq: 12 }, }); directory.dispose(); }); it("waits for workspace capability metadata before choosing the workspace protocol", async () => { const serverId = "workspace-metadata"; const { client, directory } = createDirectory(serverId); const refresh = directory.refreshWorkspaces({ subscribe: true }); await Promise.resolve(); expect(client.fetchWorkspacesCalls).toBe(0); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); await Promise.resolve(); expect(client.fetchWorkspacesCalls).toBe(0); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); await refresh; expect(client.fetchWorkspacesCalls).toBe(1); expect(useSessionStore.getState().sessions[serverId]?.hasHydratedWorkspaces).toBe(true); directory.dispose(); }); it("fetches the project descriptor channel when the daemon advertises it", async () => { const serverId = "project-list"; const { client, directory } = createDirectory(serverId); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true, projectList: true }, }); await directory.refreshWorkspaces(); expect(client.listProjectsCalls).toBe(1); expect(useSessionStore.getState().sessions[serverId]?.projects.get("project-1")).toMatchObject({ projectId: "project-1", projectKey: "remote:github.com/acme/app", }); directory.dispose(); }); it("merges project changes from the existing list RPC and advances its cursor", async () => { const serverId = "project-list-sequence"; serverIds.add(serverId); const client = new FakeDirectoryClient(); const writes: Array<{ mutations: readonly DirectoryReplicaMutation[]; checkpoint: unknown; }> = []; const cachedProjects = [ normalizeProjectDescriptor({ projectId: "project-1", projectDisplayName: "Old name", projectRootPath: "/repo/one", projectKind: "git", }), normalizeProjectDescriptor({ projectId: "project-2", projectDisplayName: "Removed", projectRootPath: "/repo/two", projectKind: "git", }), normalizeProjectDescriptor({ projectId: "untouched-project", projectDisplayName: "Untouched", projectRootPath: "/repo/untouched", projectKind: "git", }), ]; const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => undefined, markAgentLoading: () => undefined, markAgentReady: () => undefined, markAgentError: () => undefined, }, { readAgent: async () => undefined, readWorkspace: async () => undefined, readDirectory: async () => ({ agents: new Map(), workspaces: new Map(), projects: new Map(cachedProjects.map((project) => [project.projectId, project])), checkpoint: { projects: { generation: "generation", afterSeq: 4 } }, }), commitDirectoryMutations: (_serverId, mutations, checkpoint) => writes.push({ mutations: [...mutations], checkpoint }), }, ); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true, projectList: true, directorySync: true }, }); client.projectResult = { requestId: "projects", projects: [ { projectId: "project-1", projectDisplayName: "New name", projectRootPath: "/repo/one", projectKind: "git", syncSeq: 5, }, ], sync: { generation: "generation", headSeq: 6, mode: "changes", removals: [{ id: "project-2", seq: 6 }], }, }; await directory.refreshWorkspaces(); expect(client.lastProjectOptions).toEqual({ sync: { generation: "generation", afterSeq: 4 }, }); const projects = useSessionStore.getState().sessions[serverId]?.projects; expect(Array.from(projects?.keys() ?? [])).toEqual(["project-1", "untouched-project"]); expect(projects?.get("project-1")?.projectDisplayName).toBe("New name"); expect(writes.map(({ checkpoint }) => checkpoint)).toContainEqual({ projects: { generation: "generation", afterSeq: 6 }, }); const writtenProjectIds = writes .flatMap(({ mutations }) => mutations) .filter((mutation) => mutation.kind === "project") .map((mutation) => mutation.id); expect(writtenProjectIds).toEqual(["project-1", "project-2"]); expect(writtenProjectIds).not.toContain("untouched-project"); directory.dispose(); }); it("keeps the accepted baseline when an old connection finishes its workspace request", async () => { const serverId = "superseded-workspace-baseline"; const { client, directory } = createDirectory(serverId); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true, directorySync: true }, }); directory.acceptWorkspaces([normalizeWorkspaceDescriptor(createWorkspaceEntry("retained"))]); const release = client.holdWorkspaceFetch(); const refresh = directory.refreshWorkspaces(); const rejected = expect(refresh).rejects.toBeInstanceOf(DirectoryRefreshSupersededError); await expect.poll(() => client.fetchWorkspacesCalls).toBe(1); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 2 }, }); release({ requestId: "old-empty-snapshot", entries: [], emptyProjects: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, sync: { generation: "g", headSeq: 7, mode: "snapshot", removals: [] }, }); await rejected; expect([...useSessionStore.getState().sessions[serverId].workspaces.keys()]).toEqual([ "retained", ]); directory.dispose(); }); it("rejects a session wait on disconnect so the reconnect can refresh", async () => { const serverId = "session-wait-reconnect"; const { client, directory } = createDirectory(serverId); const staleRefresh = directory.refreshAgents(); await Promise.resolve(); directory.connectionChanged({ client: null, status: "offline", source: { clientGeneration: 1, connectionEpoch: 1 }, }); await expect(staleRefresh).rejects.toBeInstanceOf(DirectoryRefreshSupersededError); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 2 }, }); const currentRefresh = directory.refreshAgents(); useSessionStore.getState().initializeSession(serverId, client as unknown as DaemonClient, 1); await currentRefresh; expect(client.fetchAgentsCalls).toBe(1); directory.dispose(); }); it("buffers workspace and project updates in the same hydration transaction", async () => { const serverId = "workspace-project-transaction"; const { client, directory } = createDirectory(serverId); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); const completeFetch = client.holdWorkspaceFetch(); directory.setDemand({}, true); const refresh = directory.refreshDemand(); await expect.poll(() => client.fetchWorkspacesCalls).toBe(1); client.emit({ type: "workspace_update", payload: { kind: "remove", id: "removed-workspace", emptyProject: { projectId: "workspace-project", projectDisplayName: "Project from workspace update", projectRootPath: "/repo/workspace-project", projectKind: "git", }, }, }); client.emit({ type: "project.update", payload: { kind: "upsert", project: { projectId: "snapshot-project", projectDisplayName: "Renamed during hydration", projectRootPath: "/moved/snapshot-project", projectKind: "directory", }, }, }); client.emit({ type: "workspace_update", payload: { kind: "upsert", workspace: { ...createWorkspaceEntry("updated-workspace"), name: "Live name" }, }, }); completeFetch({ requestId: "workspaces", entries: [ createWorkspaceEntry("removed-workspace"), createWorkspaceEntry("updated-workspace"), ], emptyProjects: [ { projectId: "snapshot-project", projectDisplayName: "Stale snapshot project", projectRootPath: "/repo/snapshot-project", projectKind: "git", }, ], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, }); await refresh; const projects = useSessionStore.getState().sessions[serverId]?.projects; expect(Array.from(projects?.keys() ?? [])).toEqual(["snapshot-project", "workspace-project"]); expect(projects?.get("snapshot-project")).toMatchObject({ projectDisplayName: "Renamed during hydration", projectRootPath: "/moved/snapshot-project", projectKind: "directory", }); expect(projects?.get("workspace-project")).toMatchObject({ projectDisplayName: "Project from workspace update", }); expect( [...useSessionStore.getState().sessions[serverId].workspaces.values()].map( ({ id, name }) => ({ id, name }), ), ).toEqual([{ id: "updated-workspace", name: "Live name" }]); directory.dispose(); }); it("ignores passive project events before directory demand", async () => { const serverId = "project-before-workspace-hydration"; const { client, directory } = createDirectory(serverId); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); client.emit({ type: "project.update", payload: { kind: "upsert", project: { projectId: "early-project", projectDisplayName: "Early project", projectRootPath: "/repo/early-project", projectKind: "git", }, }, }); expect(useSessionStore.getState().sessions[serverId]?.hasHydratedWorkspaces).toBe(false); await directory.refreshWorkspaces({ subscribe: true }); expect(useSessionStore.getState().sessions[serverId]?.hasHydratedWorkspaces).toBe(true); expect(useSessionStore.getState().sessions[serverId]?.projects.has("early-project")).toBe( false, ); directory.dispose(); }); }); function createSqliteCache() { const database = new DatabaseSync(":memory:"); let beforeRead = async () => {}; const connection: ReplicaSqliteConnection = { async exec(sql) { database.exec(sql); }, async run(sql, params = []) { database.prepare(sql).run(...params); }, async all(sql: string, params: readonly SqliteValue[] = []) { const rows = database.prepare(sql).all(...params) as Row[]; if (sql.includes("FROM rows") && sql.includes("WHERE")) await beforeRead(); return rows; }, async transaction(operation) { database.exec("BEGIN IMMEDIATE"); try { await operation(connection); database.exec("COMMIT"); } catch (error) { database.exec("ROLLBACK"); throw error; } }, }; const storage = createSqliteReplicaRowStore({ open: async () => connection }, 1); const cache = new ReplicaCache(storage, { clearLegacyCache: async () => {} }); return { cache, database, storage, reopen: () => new ReplicaCache(storage, { clearLegacyCache: async () => {} }), holdRead: (operation: () => Promise) => { beforeRead = operation; }, }; } it("fills every cached workspace beneath live updates received during the SQLite read", async () => { const serverId = "sqlite-directory-race"; serverIds.add(serverId); const { cache, database, holdRead } = createSqliteCache(); cache.setHosts([serverId]); const project = normalizeProjectDescriptor({ projectId: "project", projectDisplayName: "Cached", projectRootPath: "/repo", projectKind: "git", }); const workspaces = ["first", "second", "third"].map((id) => normalizeWorkspaceDescriptor({ id, projectId: "project", projectDisplayName: "Cached", projectRootPath: "/repo", workspaceDirectory: `/repo/${id}`, projectKind: "git", workspaceKind: "local_checkout", name: id, status: "done", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], }), ); cache.replaceDirectoryBaseline(serverId, { agents: new Map([["agent", createAgent(serverId, "agent")]]), workspaces: new Map(workspaces.map((workspace) => [workspace.id, workspace])), projects: new Map([[project.projectId, project]]), }); await cache.flush(); const client = new FakeDirectoryClient(); const directory = new DirectorySync( serverId, { onAgentStoppedRunning: () => {}, markAgentLoading: () => {}, markAgentReady: () => {}, markAgentError: () => {}, }, cache, ); useSessionStore.getState().initializeSession(serverId, client as unknown as DaemonClient, 1); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 1 }, }); useSessionStore.getState().updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true }, }); directory.setAgentRouteDemand(["agent"]); await directory.refreshDemand(); let updated = false; holdRead(async () => { if (updated) return; updated = true; client.emit({ type: "workspace_update", payload: { kind: "upsert", workspace: { ...workspaces[0], name: "Live", activityAt: null, statusEnteredAt: null }, }, }); }); await directory.restoreCachedDirectory(); const session = useSessionStore.getState().sessions[serverId]; expect( [...session.workspaces.values()] .map(({ id, name }) => ({ id, name })) .sort((a, b) => a.id.localeCompare(b.id)), ).toEqual([ { id: "first", name: "Live" }, { id: "second", name: "second" }, { id: "third", name: "third" }, ]); expect(session.agents.get("agent")?.title).toBe("Cached"); expect(session.hasWorkspaceDirectorySnapshot).toBe(true); directory.dispose(); await cache.flush(); database.close(); }); function createWorkspaceLabelDirectory(serverId: string) { const { client, directory } = createDirectory(serverId, { workspaceLabels: true }); const store = useSessionStore.getState(); store.initializeSession(serverId, client as unknown as DaemonClient, 1); store.updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { directorySync: true, projectList: true, workspaceLabels: true, workspaceMultiplicity: true, }, }); return { client, directory }; } async function flushWorkspaceLabels(): Promise { for (let index = 0; index < 4; index += 1) await Promise.resolve(); } describe("DirectorySync workspace labels", () => { it("loads the catalog when an agent route is open before the sidebar", async () => { const serverId = "workspace-labels-route-then-full"; const { directory } = createWorkspaceLabelDirectory(serverId); directory.setAgentRouteDemand(["agent-1"]); await directory.refreshDemand(); directory.setDemand({}, true); await flushWorkspaceLabels(); expect(useWorkspaceLabels.getState().hosts[serverId]).toMatchObject({ status: "online", labels: [{ name: "Urgent", color: "red" }], }); directory.dispose(); }); it("loads the catalog when the sidebar demands the directory first", async () => { const serverId = "workspace-labels-full-first"; const { directory } = createWorkspaceLabelDirectory(serverId); directory.setDemand({}, true); await directory.refreshDemand(); expect(useWorkspaceLabels.getState().hosts[serverId]).toMatchObject({ status: "online", labels: [{ name: "Urgent", color: "red" }], }); directory.dispose(); }); it("keeps the picker online during a full refresh", async () => { const serverId = "workspace-labels-refresh-no-flicker"; const { client, directory } = createWorkspaceLabelDirectory(serverId); directory.setDemand({}, true); await directory.refreshDemand(); const callsBeforeRefresh = client.listWorkspaceLabelsCalls; const statuses: string[] = []; const unsubscribe = useWorkspaceLabels.subscribe((state) => { const status = state.hosts[serverId]?.status; if (status) statuses.push(status); }); await directory.refreshDemand(); expect(client.listWorkspaceLabelsCalls).toBeGreaterThan(callsBeforeRefresh); expect(statuses).not.toContain("offline"); unsubscribe(); directory.dispose(); }); it("restores the catalog after reconnecting", async () => { const serverId = "workspace-labels-reconnect"; const { client, directory } = createWorkspaceLabelDirectory(serverId); directory.setDemand({}, true); await directory.refreshDemand(); directory.connectionChanged({ client: null, status: "offline", source: { clientGeneration: 1, connectionEpoch: 1 }, }); expect(useWorkspaceLabels.getState().hosts[serverId]?.status).toBe("offline"); directory.connectionChanged({ client: client as unknown as DaemonClient, status: "online", source: { clientGeneration: 1, connectionEpoch: 2 }, }); await flushWorkspaceLabels(); expect(useWorkspaceLabels.getState().hosts[serverId]).toMatchObject({ status: "online", labels: [{ name: "Urgent", color: "red" }], }); directory.dispose(); }); }); it.each(["before", "during", "before-metadata", "damaged"] as const)( "retains unchanged workspaces across route/full demand and restart (cache: %s)", async (timing) => { const cacheFirst = timing === "before"; const damaged = timing === "damaged"; const serverId = `cache-route-overlap-${timing}`; serverIds.add(serverId); const { cache, database, reopen, storage } = createSqliteCache(); cache.setHosts([serverId]); const projectEntry: ProjectListResult["projects"][number] = { projectId: "P", projectDisplayName: "Project P", projectRootPath: "/repo", projectKind: "git", }; const project = normalizeProjectDescriptor(projectEntry); const workspaceEntries: WorkspaceFetchResult["entries"] = ["A", "B"].map((id) => ({ id, projectId: "P", projectDisplayName: "Project P", projectRootPath: "/repo", workspaceDirectory: `/repo/${id}`, projectKind: "git", workspaceKind: "local_checkout", name: id, status: "done", statusEnteredAt: null, activityAt: null, archivingAt: null, diffStat: null, scripts: [], })); const workspaces = workspaceEntries.map(normalizeWorkspaceDescriptor); const cursor = { generation: "g", afterSeq: 7 }; cache.replaceDirectoryBaseline(serverId, { agents: new Map(), projects: new Map([[project.projectId, project]]), workspaces: new Map( (damaged ? [] : workspaces).map((workspace) => [workspace.id, workspace]), ), checkpoint: { projects: cursor, workspaces: cursor, agents: cursor }, }); await cache.flush(); if (damaged) { await storage.apply({ deletes: [], upserts: [ { serverId, kind: "checkpoint", id: "singleton", payload: JSON.stringify({ projects: cursor, workspaces: cursor, agents: cursor }), }, ], }); } // The host keeps A/B unchanged. A current cursor legitimately receives no rows. class UnchangedDirectoryClient extends FakeDirectoryClient { readonly workspaceSyncRequests: unknown[] = []; override async fetchWorkspaces( options?: Parameters[0], ): Promise { this.fetchWorkspacesCalls += 1; this.lastWorkspaceOptions = options; this.workspaceSyncRequests.push(options?.sync); const changes = options?.sync?.generation === "g" && options.sync.afterSeq === 7; return { requestId: "workspaces", entries: changes ? [] : [workspaceEntries[options?.page?.cursor ? 1 : 0]], emptyProjects: [], pageInfo: { hasMore: !changes && !options?.page?.cursor, nextCursor: !changes && !options?.page?.cursor ? "page-2" : null, prevCursor: null, }, sync: { generation: "g", headSeq: 7, mode: changes ? "changes" : "snapshot", removals: [], }, }; } } const client = new UnchangedDirectoryClient(); const releaseAgents = client.holdAgentFetch(); const releaseProjects = client.holdProjectFetch(); const projectResult: ProjectListResult = { requestId: "projects", projects: [projectEntry], sync: { generation: "g", headSeq: 7, mode: "snapshot", removals: [] }, }; client.projectResult = projectResult; const callbacks = { onAgentStoppedRunning: () => {}, markAgentLoading: () => {}, markAgentReady: () => {}, markAgentError: () => {}, }; const directory = new DirectorySync(serverId, callbacks, cache); const publishServerInfo = () => { useSessionStore.getState().updateSessionServerInfo(serverId, { serverId, hostname: null, version: "test", features: { workspaceMultiplicity: true, directorySync: true, projectList: true }, }); }; const initializeSession = (ready = true) => { useSessionStore.getState().initializeSession(serverId, client as unknown as DaemonClient, 1); if (ready) publishServerInfo(); }; const connection = { client: client as unknown as DaemonClient, status: "online" as const, source: { clientGeneration: 1, connectionEpoch: 1 }, }; initializeSession(timing !== "before-metadata"); directory.connectionChanged(connection); const visibleIds = () => [...useSessionStore.getState().sessions[serverId].workspaces.keys()].sort(); let restarted: DirectorySync | undefined; const restartedCache = reopen(); try { if (cacheFirst) await directory.restoreCachedDirectory(); directory.setAgentRouteDemand(["agent"]); if (timing === "before-metadata") { await directory.restoreCachedDirectory(); publishServerInfo(); } await expect.poll(() => client.listProjectsCalls).toBe(1); await expect.poll(() => client.fetchAgentsCalls).toBe(1); directory.setDemand({}, true); await directory.restoreCachedDirectory(); expect(visibleIds()).toEqual(damaged ? [] : ["A", "B"]); releaseProjects(projectResult); releaseAgents({ requestId: "agents", entries: [], pageInfo: { hasMore: false, nextCursor: null, prevCursor: null }, sync: { generation: "g", headSeq: 7, mode: cacheFirst ? "changes" : "snapshot", removals: [], }, }); await directory.refreshDemand(); expect(client.lastProjectOptions).toEqual({ sync: cacheFirst ? cursor : {} }); expect(client.workspaceSyncRequests).toEqual(cacheFirst ? [cursor] : [{}, {}]); expect.soft(visibleIds(), "after overlapping refresh").toEqual(["A", "B"]); await cache.flush(); expect([...(await cache.readDirectory(serverId)).workspaces.keys()].sort()).toEqual([ "A", "B", ]); await directory.refreshAll(); await cache.flush(); directory.dispose(); useSessionStore.getState().clearSession(serverId); restartedCache.setHosts([serverId]); const persisted = await restartedCache.readDirectory(serverId); expect(persisted.checkpoint?.workspaces).toEqual(cursor); expect .soft([...persisted.workspaces.keys()].sort(), "persisted baseline") .toEqual(["A", "B"]); initializeSession(); restarted = new DirectorySync(serverId, callbacks, restartedCache); restarted.connectionChanged(connection); await restarted.refreshAll(); expect.soft(visibleIds(), "after restart and refresh").toEqual(["A", "B"]); const beforeUpdate = visibleIds(); client.emit({ type: "workspace_update", payload: { kind: "upsert", workspace: workspaceEntries[0], generation: "g", seq: 8 }, }); expect(visibleIds()).toEqual([...new Set([...beforeUpdate, "A"])].sort()); client.emit({ type: "workspace_update", payload: { kind: "upsert", workspace: workspaceEntries[1], generation: "g", seq: 9 }, }); expect(visibleIds()).toEqual(["A", "B"]); } finally { directory.dispose(); restarted?.dispose(); await cache.flush(); await restartedCache.flush(); database.close(); } }, );