Files
paseo/packages/app/src/stores/session-store.test.ts
T

891 lines
28 KiB
TypeScript

import { afterEach, describe, expect, it } from "vitest";
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
import type { WorkspaceDescriptorPayload } from "@getpaseo/protocol/messages";
import {
normalizeWorkspaceDescriptor,
selectAgentSentMessages,
selectAgentTurnPresentation,
selectAgentTimelineState,
useSessionStore,
type Agent,
type WorkspaceDescriptor,
} from "./session-store";
import type { StreamItem } from "../types/stream";
import { reduceTurnLiveness, type TurnLivenessTransition } from "@/timeline/turn-liveness";
function createTestAgent(agentId: string): Agent {
return {
serverId: "test-server",
id: agentId,
provider: "codex",
status: "idle",
turn: { phase: "idle", cancellationRequestId: null },
createdAt: new Date(0),
updatedAt: new Date(0),
lastUserMessageAt: null,
lastActivityAt: new Date(0),
capabilities: {
supportsStreaming: true,
supportsSessionPersistence: true,
supportsDynamicModes: false,
supportsMcpServers: false,
supportsReasoningStream: false,
supportsToolInvocations: false,
},
currentModeId: null,
availableModes: [],
pendingPermissions: [],
persistence: null,
title: null,
cwd: "/repo",
model: null,
parentAgentId: null,
labels: {},
};
}
function applyTestTurn(
serverId: string,
agentId: string,
transition: TurnLivenessTransition | readonly TurnLivenessTransition[],
): void {
useSessionStore.getState().setAgents(serverId, (agents) => {
const agent = agents.get(agentId) ?? createTestAgent(agentId);
const transitions = Array.isArray(transition) ? transition : [transition];
const turn = transitions.reduce(reduceTurnLiveness, agent.turn);
const next = new Map(agents);
next.set(agentId, { ...agent, status: turn.phase === "open" ? "running" : "idle", turn });
return next;
});
}
function createWorkspace(
input: Partial<WorkspaceDescriptor> & Pick<WorkspaceDescriptor, "id">,
): WorkspaceDescriptor {
return {
createdAt: null,
lastUserMessageAt: null,
id: input.id,
projectId: input.projectId ?? "project-1",
projectDisplayName: input.projectDisplayName ?? "Project 1",
projectCustomName: input.projectCustomName ?? null,
projectRootPath: input.projectRootPath ?? "/repo",
workspaceDirectory: input.workspaceDirectory ?? "/repo",
projectKind: input.projectKind ?? "git",
workspaceKind: input.workspaceKind ?? "local_checkout",
name: input.name ?? "main",
status: input.status ?? "done",
statusEnteredAt: input.statusEnteredAt ?? null,
archivingAt: input.archivingAt ?? null,
diffStat: input.diffStat ?? null,
scripts: input.scripts ?? [],
};
}
afterEach(() => {
useSessionStore.getState().clearSession("test-server");
});
function initializeTestSession(): void {
useSessionStore.getState().initializeSession("test-server", null as unknown as DaemonClient);
}
function getTestSessionReferences() {
const state = useSessionStore.getState();
const session = state.sessions["test-server"];
if (!session) {
throw new Error("test session is not initialized");
}
return {
sessions: state.sessions,
session,
workspaces: session.workspaces,
};
}
function submittedMessage(clientMessageId: string): Extract<StreamItem, { kind: "user_message" }> {
return {
kind: "user_message",
id: clientMessageId,
clientMessageId,
text: clientMessageId,
timestamp: new Date("2026-07-31T10:00:00.000Z"),
};
}
function hasSubmittedMessage(items: readonly StreamItem[] | undefined, clientMessageId: string) {
return items?.some(
(item) => item.kind === "user_message" && item.clientMessageId === clientMessageId,
);
}
function permutations<T>(values: readonly T[]): T[][] {
if (values.length === 0) return [[]];
return values.flatMap((value, index) =>
permutations([...values.slice(0, index), ...values.slice(index + 1)]).map((suffix) =>
[value].concat(suffix),
),
);
}
function taskTexts(tasks: ReadonlyArray<{ text: string }>): string[] {
return tasks.map((task) => task.text);
}
describe("agent task state", () => {
it("notifies the task subscriber only when its task snapshot changes", () => {
initializeTestSession();
const snapshots: string[][] = [];
const unsubscribe = useSessionStore.subscribe(
(state) => state.sessions["test-server"]?.agentTasks.get("agent-1") ?? [],
(tasks) => snapshots.push(taskTexts(tasks)),
);
const tasks = [{ id: "1", text: "Inspect", status: "pending" as const, completed: false }];
useSessionStore
.getState()
.setAgentStreamState("test-server", "agent-1", { taskSnapshot: tasks });
useSessionStore.getState().setIsPlayingAudio("test-server", true);
useSessionStore
.getState()
.setAgentStreamState("test-server", "agent-1", { taskSnapshot: [...tasks] });
useSessionStore.getState().setAgentStreamState("test-server", "agent-1", {
taskSnapshot: [{ ...tasks[0], status: "completed", completed: true }],
});
unsubscribe();
expect(snapshots).toEqual([["Inspect"], ["Inspect"]]);
});
it("restores the latest task snapshot from an authoritative timeline", () => {
initializeTestSession();
const todo: StreamItem = {
kind: "todo_list",
id: "todo-1",
provider: "codex",
timestamp: new Date("2026-08-11T10:00:00.000Z"),
activity: { type: "started", task: "Verify" },
items: [{ text: "Verify", status: "in_progress", completed: false }],
};
useSessionStore.getState().applyAgentTimelineResponseState("test-server", "agent-1", {
items: [todo],
head: [],
range: null,
older: "none",
newer: false,
synchronized: true,
acknowledgedClientMessageIds: [],
});
expect(useSessionStore.getState().sessions["test-server"]?.agentTasks.get("agent-1")).toEqual(
todo.items,
);
});
});
describe("agent timeline state", () => {
it("commits canonical items, range, and older availability as one synced state", () => {
initializeTestSession();
const items: StreamItem[] = [
{
kind: "assistant_message",
id: "canonical-row",
text: "canonical",
timestamp: new Date("2026-07-27T10:00:00.000Z"),
},
];
useSessionStore.getState().applyAgentTimelineResponseState("test-server", "agent-1", {
items,
head: [],
range: { epoch: "epoch-1", startSeq: 51, endSeq: 100 },
older: "available",
newer: false,
synchronized: true,
acknowledgedClientMessageIds: [],
});
expect(
selectAgentTimelineState(useSessionStore.getState().sessions["test-server"], "agent-1"),
).toEqual({
status: "synced",
items,
range: { epoch: "epoch-1", startSeq: 51, endSeq: 100 },
older: "available",
newer: "none",
});
});
it("represents an empty authoritative timeline without inventing a range", () => {
initializeTestSession();
useSessionStore.getState().applyAgentTimelineResponseState("test-server", "agent-1", {
items: [],
head: [],
range: null,
older: "none",
newer: false,
synchronized: true,
acknowledgedClientMessageIds: [],
});
expect(
selectAgentTimelineState(useSessionStore.getState().sessions["test-server"], "agent-1"),
).toEqual({ status: "synced", items: [], range: null, older: "none", newer: "none" });
});
it("lists sent messages newest first, including the live head", () => {
initializeTestSession();
const sent = (id: string, text: string) => ({ ...submittedMessage(id), text });
useSessionStore.getState().applyAgentTimelineResponseState("test-server", "agent-1", {
items: [sent("m1", "fix the auth bug"), sent("m2", "add tests"), sent("m3", "add tests")],
head: [sent("m4", " "), sent("m5", "refactor the wrong module")],
range: { epoch: "epoch-1", startSeq: 1, endSeq: 5 },
older: "none",
newer: false,
synchronized: true,
acknowledgedClientMessageIds: [],
});
expect(
selectAgentSentMessages(useSessionStore.getState().sessions["test-server"], "agent-1"),
).toEqual(["refactor the wrong module", "add tests", "fix the auth bug"]);
expect(selectAgentSentMessages(undefined, "agent-1")).toEqual([]);
});
it("preserves older availability when an applied response leaves it unchanged", () => {
initializeTestSession();
const store = useSessionStore.getState();
store.applyAgentTimelineResponseState("test-server", "agent-1", {
items: [],
head: [],
range: { epoch: "epoch-1", startSeq: 200, endSeq: 240 },
older: "available",
newer: false,
synchronized: true,
acknowledgedClientMessageIds: [],
});
store.applyAgentTimelineResponseState("test-server", "agent-1", {
items: [],
head: [],
range: { epoch: "epoch-1", startSeq: 200, endSeq: 240 },
older: "unchanged",
newer: false,
synchronized: false,
acknowledgedClientMessageIds: [],
});
expect(
selectAgentTimelineState(useSessionStore.getState().sessions["test-server"], "agent-1"),
).toEqual({
status: "synced",
items: [],
range: { epoch: "epoch-1", startSeq: 200, endSeq: 240 },
older: "available",
newer: "none",
});
});
it("stores turn liveness transitions without duplicating their policy", () => {
initializeTestSession();
const store = useSessionStore.getState();
applyTestTurn("test-server", "agent-1", {
type: "snapshot",
activeTurn: { turnId: "turn-1", startedAt: null },
});
expect(store.getSession("test-server")?.agents.get("agent-1")?.turn).toEqual({
phase: "open",
turnId: "turn-1",
startedAt: null,
cancellationRequestId: null,
});
applyTestTurn("test-server", "agent-1", {
type: "snapshot",
activeTurn: null,
});
expect(store.getSession("test-server")?.agents.get("agent-1")?.turn).toEqual({
phase: "idle",
cancellationRequestId: null,
});
});
});
describe("message submission ordering", () => {
const agentId = "agent-1";
const clientMessageId = "client-1";
const startedAt = new Date("2026-07-31T10:00:01.000Z");
const steps = [
"accept",
"canonical-row",
"stream-open",
"snapshot-idle",
"snapshot-open",
"stream-close",
] as const;
function applyStep(step: (typeof steps)[number]): void {
const store = useSessionStore.getState();
if (step === "accept") {
store.acceptAgentMessageSubmission("test-server", agentId, clientMessageId);
return;
}
if (step === "canonical-row") {
store.setAgentStreamState("test-server", agentId, {
acknowledgedClientMessageIds: [clientMessageId],
});
return;
}
if (step === "stream-open") {
applyTestTurn("test-server", agentId, {
type: "stream_open",
turn: { turnId: "turn-1", startedAt },
});
return;
}
if (step === "snapshot-open") {
applyTestTurn("test-server", agentId, {
type: "snapshot",
activeTurn: { turnId: "turn-1", startedAt },
});
return;
}
if (step === "snapshot-idle") {
applyTestTurn("test-server", agentId, {
type: "snapshot",
activeTurn: null,
});
return;
}
applyTestTurn("test-server", agentId, {
type: "stream_close",
turnId: "turn-1",
});
}
it("keeps every ordering active until its canonical row and settles at quiescence", () => {
const continuityViolations: string[] = [];
const settlementViolations: string[] = [];
for (const ordering of permutations(steps)) {
useSessionStore.getState().clearSession("test-server");
initializeTestSession();
useSessionStore
.getState()
.beginAgentMessageSubmission("test-server", agentId, submittedMessage(clientMessageId));
let canonicalObserved = false;
for (const step of ordering) {
applyStep(step);
canonicalObserved ||= step === "canonical-row";
const presentation = selectAgentTurnPresentation(
useSessionStore.getState().sessions["test-server"],
agentId,
);
if (!canonicalObserved && !presentation.isActive) {
continuityViolations.push(`${ordering.join(" → ")}: inactive after ${step}`);
}
}
const store = useSessionStore.getState();
store.acceptAgentMessageSubmission("test-server", agentId, clientMessageId);
store.setAgentStreamState("test-server", agentId, {
acknowledgedClientMessageIds: [clientMessageId],
});
applyTestTurn("test-server", agentId, [
{ type: "stream_close", turnId: "turn-1" },
{ type: "snapshot", activeTurn: null },
]);
const settled = selectAgentTurnPresentation(
useSessionStore.getState().sessions["test-server"],
agentId,
);
if (settled.isActive) settlementViolations.push(ordering.join(" → "));
}
expect({ continuityViolations, settlementViolations }).toEqual({
continuityViolations: [],
settlementViolations: [],
});
});
it("publishes the optimistic row and active presentation in one store notification", () => {
initializeTestSession();
const notifications: Array<{ hasOptimisticRow: boolean; isActive: boolean }> = [];
const unsubscribe = useSessionStore.subscribe((state) => {
const session = state.sessions["test-server"];
notifications.push({
hasOptimisticRow:
hasSubmittedMessage(session?.agentStreamTail.get(agentId), clientMessageId) === true,
isActive: selectAgentTurnPresentation(session, agentId).isActive,
});
});
useSessionStore
.getState()
.beginAgentMessageSubmission("test-server", agentId, submittedMessage(clientMessageId));
unsubscribe();
expect(notifications).toEqual([{ hasOptimisticRow: true, isActive: true }]);
});
it("keeps another unacknowledged submission active after one row is acknowledged", () => {
initializeTestSession();
const store = useSessionStore.getState();
store.beginAgentMessageSubmission("test-server", agentId, submittedMessage(clientMessageId));
store.beginAgentMessageSubmission("test-server", agentId, submittedMessage("client-2"));
store.acceptAgentMessageSubmission("test-server", agentId, clientMessageId);
store.setAgentStreamState("test-server", agentId, {
acknowledgedClientMessageIds: [clientMessageId],
});
expect(
selectAgentTurnPresentation(useSessionStore.getState().sessions["test-server"], agentId)
.isActive,
).toBe(true);
});
it("reconciles a created-agent row without creating a submission transaction", () => {
initializeTestSession();
const message = submittedMessage(clientMessageId);
const handedOff = useSessionStore
.getState()
.handoffCreatedAgentUserMessage("test-server", agentId, message);
const session = useSessionStore.getState().sessions["test-server"];
expect({
handedOff,
tail: session?.agentStreamTail.get(agentId),
head: session?.agentStreamHead.get(agentId) ?? [],
submissions: session?.messageSubmissions.get(agentId) ?? [],
}).toEqual({
handedOff: true,
tail: [message],
head: [],
submissions: [],
});
});
});
describe("normalizeWorkspaceDescriptor", () => {
it("normalizes workspace scripts and invalid activity timestamps", () => {
const scripts = [
{
scriptName: "web",
type: "service" as const,
hostname: "web.paseo.localhost",
port: 3000,
proxyUrl: "http://web.paseo.localhost:6767",
lifecycle: "running" as const,
health: "healthy" as const,
exitCode: null,
terminalId: null,
},
];
const workspace = normalizeWorkspaceDescriptor({
id: "1",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: "/repo",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
archivingAt: null,
status: "running",
statusEnteredAt: null,
activityAt: "not-a-date",
diffStat: null,
scripts,
});
expect(workspace.scripts).toEqual([
{
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(workspace.scripts).not.toBe(scripts);
});
it("canonicalizes the workspace directory and treats a blank one as empty", () => {
const canonical = normalizeWorkspaceDescriptor({
id: "1",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: "/repo/app/",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
archivingAt: null,
status: "done",
statusEnteredAt: null,
activityAt: null,
diffStat: null,
scripts: [],
});
expect(canonical.workspaceDirectory).toBe("/repo/app");
const blank = normalizeWorkspaceDescriptor({
id: "1",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: " ",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
archivingAt: null,
status: "done",
statusEnteredAt: null,
activityAt: null,
diffStat: null,
scripts: [],
});
expect(blank.workspaceDirectory).toBe("");
});
it("preserves informational workspace parentage", () => {
const payload = {
id: "child",
parentWorkspaceId: "parent",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: "/repo",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
archivingAt: null,
status: "done",
statusEnteredAt: null,
activityAt: null,
diffStat: null,
scripts: [],
} as WorkspaceDescriptorPayload;
const workspace = normalizeWorkspaceDescriptor(payload);
expect(workspace.parentWorkspaceId).toBe("parent");
expect(
normalizeWorkspaceDescriptor({ ...payload, parentWorkspaceId: undefined }).parentWorkspaceId,
).toBeUndefined();
});
it("defaults missing scripts to an empty array", () => {
const payload = {
id: "1",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: "/repo",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
archivingAt: null,
status: "done",
statusEnteredAt: null,
activityAt: null,
diffStat: null,
scripts: [],
} as WorkspaceDescriptorPayload;
const workspace = normalizeWorkspaceDescriptor(payload);
expect(workspace.scripts).toEqual([]);
});
it("defaults missing archivingAt to null", () => {
const payload = {
id: "1",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: "/repo",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
status: "done",
activityAt: null,
diffStat: null,
scripts: [],
} as unknown as WorkspaceDescriptorPayload;
const workspace = normalizeWorkspaceDescriptor(payload);
expect(workspace.archivingAt).toBeNull();
});
it("normalizes statusEnteredAt strings to Date and missing or null values to null", () => {
const basePayload = {
id: "1",
projectId: "1",
projectDisplayName: "Project 1",
projectRootPath: "/repo",
workspaceDirectory: "/repo",
projectKind: "git",
workspaceKind: "checkout",
name: "main",
status: "running",
activityAt: null,
diffStat: null,
scripts: [],
} satisfies Omit<WorkspaceDescriptorPayload, "statusEnteredAt" | "archivingAt">;
const withString = normalizeWorkspaceDescriptor({
...basePayload,
archivingAt: null,
statusEnteredAt: "2026-05-12T09:30:00.000Z",
});
const withNull = normalizeWorkspaceDescriptor({
...basePayload,
archivingAt: null,
statusEnteredAt: null,
});
const missing = normalizeWorkspaceDescriptor({
...basePayload,
archivingAt: null,
} as unknown as WorkspaceDescriptorPayload);
expect(withString.statusEnteredAt).toEqual(new Date("2026-05-12T09:30:00.000Z"));
expect(withNull.statusEnteredAt).toBeNull();
expect(missing.statusEnteredAt).toBeNull();
});
it("preserves project placement from workspace descriptor payloads", () => {
const workspace = normalizeWorkspaceDescriptor({
id: "1",
projectId: "remote:github.com/acme/app",
projectDisplayName: "acme/app",
projectRootPath: "/repo/app",
workspaceDirectory: "/repo/app",
projectKind: "git",
workspaceKind: "local_checkout",
name: "main",
archivingAt: null,
status: "done",
statusEnteredAt: null,
activityAt: null,
diffStat: null,
scripts: [],
project: {
projectKey: "remote:github.com/acme/app",
projectName: "acme/app",
checkout: {
cwd: "/repo/app",
isGit: true,
currentBranch: "main",
remoteUrl: "https://github.com/acme/app.git",
worktreeRoot: "/repo/app",
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
},
},
});
expect(workspace.project).toEqual({
projectKey: "remote:github.com/acme/app",
projectName: "acme/app",
checkout: {
cwd: "/repo/app",
isGit: true,
currentBranch: "main",
remoteUrl: "https://github.com/acme/app.git",
worktreeRoot: "/repo/app",
isPaseoOwnedWorktree: false,
mainRepoRoot: null,
},
});
});
});
describe("mergeWorkspaces", () => {
it("preserves scripts on merged workspace entries", () => {
const store = useSessionStore.getState();
store.initializeSession("test-server", null as unknown as DaemonClient);
store.setWorkspaces(
"test-server",
new Map([["/repo/main", createWorkspace({ id: "/repo/main", scripts: [] })]]),
);
store.mergeWorkspaces("test-server", [
createWorkspace({
id: "/repo/main",
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(store.getSession("test-server")?.workspaces.get("/repo/main")?.scripts).toEqual([
{
scriptName: "web",
type: "service",
hostname: "web.paseo.localhost",
port: 3000,
proxyUrl: "http://web.paseo.localhost:6767",
lifecycle: "running",
health: "healthy",
exitCode: null,
terminalId: null,
},
]);
});
it("preserves identity when merging content-equal workspace descriptors", () => {
const store = useSessionStore.getState();
initializeTestSession();
const workspace = createWorkspace({ id: "/repo/main" });
store.mergeWorkspaces("test-server", [workspace]);
const first = getTestSessionReferences();
store.mergeWorkspaces("test-server", [{ ...workspace, scripts: [...workspace.scripts] }]);
const second = getTestSessionReferences();
expect(second.sessions).toBe(first.sessions);
expect(second.session).toBe(first.session);
expect(second.workspaces).toBe(first.workspaces);
expect(second.workspaces.get("/repo/main")).toBe(first.workspaces.get("/repo/main"));
});
it("preserves unaffected workspace entry identity when one workspace changes", () => {
const store = useSessionStore.getState();
initializeTestSession();
const workspaceA = createWorkspace({ id: "/repo/a", name: "main" });
const workspaceB = createWorkspace({ id: "/repo/b", name: "feature" });
store.mergeWorkspaces("test-server", [workspaceA, workspaceB]);
const before = getTestSessionReferences();
const beforeA = before.workspaces.get("/repo/a");
const beforeB = before.workspaces.get("/repo/b");
store.mergeWorkspaces("test-server", [{ ...workspaceA, status: "running" }]);
const after = getTestSessionReferences();
expect(after.sessions).not.toBe(before.sessions);
expect(after.session).not.toBe(before.session);
expect(after.workspaces).not.toBe(before.workspaces);
expect(after.workspaces.get("/repo/a")).not.toBe(beforeA);
expect(after.workspaces.get("/repo/b")).toBe(beforeB);
});
it("uses incoming null diff stat as authoritative", () => {
const store = useSessionStore.getState();
initializeTestSession();
const workspace = createWorkspace({
id: "/repo/main",
diffStat: { additions: 2, deletions: 1 },
});
store.mergeWorkspaces("test-server", [workspace]);
const before = getTestSessionReferences();
store.mergeWorkspaces("test-server", [{ ...workspace, diffStat: null }]);
const after = getTestSessionReferences();
expect(after.sessions).not.toBe(before.sessions);
expect(after.session).not.toBe(before.session);
expect(after.workspaces).not.toBe(before.workspaces);
expect(after.workspaces.get(workspace.id)?.diffStat).toBeNull();
});
});
describe("setWorkspaces", () => {
it("preserves identity when replacing workspaces with content-equal entries", () => {
const store = useSessionStore.getState();
initializeTestSession();
const workspace = createWorkspace({ id: "/repo/main" });
store.setWorkspaces("test-server", new Map([[workspace.id, workspace]]));
const before = getTestSessionReferences();
store.setWorkspaces(
"test-server",
new Map([[workspace.id, { ...workspace, scripts: [...workspace.scripts] }]]),
);
const after = getTestSessionReferences();
expect(after.sessions).toBe(before.sessions);
expect(after.session).toBe(before.session);
expect(after.workspaces).toBe(before.workspaces);
expect(after.workspaces.get(workspace.id)).toBe(before.workspaces.get(workspace.id));
});
});
describe("removeWorkspace", () => {
it("preserves identity when removing a missing workspace", () => {
const store = useSessionStore.getState();
initializeTestSession();
const workspace = createWorkspace({ id: "/repo/main" });
store.setWorkspaces("test-server", new Map([[workspace.id, workspace]]));
const before = getTestSessionReferences();
store.removeWorkspace("test-server", "/repo/missing");
const after = getTestSessionReferences();
expect(after.sessions).toBe(before.sessions);
expect(after.session).toBe(before.session);
expect(after.workspaces).toBe(before.workspaces);
});
});
describe("acknowledgedClientMessageIds pruning", () => {
it("prunes acknowledged queued messages in setAgentStreamState and applyAgentTimelineResponseState", () => {
const store = useSessionStore.getState();
initializeTestSession();
store.setQueuedMessages(
"test-server",
new Map([
[
"agent-1",
[
{ id: "cmsg-1", text: "first", attachments: [] },
{ id: "cmsg-2", text: "second", attachments: [] },
],
],
]),
);
store.setAgentStreamState("test-server", "agent-1", {
acknowledgedClientMessageIds: ["cmsg-1"],
});
expect(
useSessionStore.getState().sessions["test-server"]?.queuedMessages.get("agent-1"),
).toEqual([{ id: "cmsg-2", text: "second", attachments: [] }]);
store.applyAgentTimelineResponseState("test-server", "agent-1", {
items: [],
head: [],
range: null,
older: "unchanged",
newer: false,
synchronized: true,
acknowledgedClientMessageIds: ["cmsg-2"],
});
expect(
useSessionStore.getState().sessions["test-server"]?.queuedMessages.get("agent-1"),
).toBeUndefined();
});
});