Revert the timeline optimistic and pagination rework (#2596)
* Add npm release track invariant and beta dist-tag procedure * Revert "Keep older chat history and image previews stable (#2490)" The timeline optimistic/pagination rework needs more work than main releases can wait for. Reverted together with #2484; both are reapplied on integration/timeline-optimistic-rework. * Revert "Stop completed turns from appearing stuck (#2484)" See the #2490 revert. #2490 built on this submission-authority change, so the two revert and reapply as a pair.
This commit is contained in:
@@ -4,7 +4,6 @@ import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||
import {
|
||||
normalizeEmptyProjectDescriptor,
|
||||
normalizeWorkspaceDescriptor,
|
||||
selectAgentTimelineState,
|
||||
useSessionStore,
|
||||
} from "@/stores/session-store";
|
||||
import type { StreamItem } from "@/types/stream";
|
||||
@@ -167,14 +166,13 @@ describe("ReplicaCache", () => {
|
||||
expect(session?.agents.get("agent-1")?.updatedAt).toBeInstanceOf(Date);
|
||||
expect(session?.workspaces.get("workspace-1")?.statusEnteredAt).toBeInstanceOf(Date);
|
||||
expect(session?.agentStreamTail.get("agent-1")).toEqual([message("message-1", "Cached")]);
|
||||
expect(session?.agentAuthoritativeHistoryApplied).toEqual(new Map());
|
||||
expect(session?.agentTimelineCursor).toEqual(new Map());
|
||||
expect(session?.agentTimelineHasOlder).toEqual(new Map());
|
||||
expect(session?.agentHistorySyncGeneration).toEqual(new Map());
|
||||
expect(selectAgentTimelineState(session, "agent-1")).toEqual({
|
||||
status: "painted",
|
||||
items: [message("message-1", "Cached")],
|
||||
expect(session?.agentAuthoritativeHistoryApplied.get("agent-1")).toBe(true);
|
||||
expect(session?.agentTimelineCursor.get("agent-1")).toEqual({
|
||||
epoch: "epoch-1",
|
||||
startSeq: 1,
|
||||
endSeq: 12,
|
||||
});
|
||||
expect(session?.agentTimelineHasOlder.get("agent-1")).toBe(true);
|
||||
});
|
||||
|
||||
it("persists only the focused agent view with a short timeline tail", async () => {
|
||||
@@ -218,13 +216,6 @@ describe("ReplicaCache", () => {
|
||||
expect(Array.from(session?.emptyProjects.keys() ?? [])).toEqual([]);
|
||||
expect(Array.from(timelines?.keys() ?? [])).toEqual(["agent-2"]);
|
||||
expect(timelines?.get("agent-2")).toEqual(secondTimeline.slice(-50));
|
||||
|
||||
const persisted = JSON.parse(storage.values.get("@paseo:replica-cache") ?? "null") as {
|
||||
version: number;
|
||||
hosts: Array<{ timeline: Record<string, unknown> | null }>;
|
||||
};
|
||||
expect(persisted.version).toBe(2);
|
||||
expect(Object.keys(persisted.hosts[0]?.timeline ?? {}).sort()).toEqual(["agentId", "items"]);
|
||||
});
|
||||
|
||||
it("evicts the least recently written host when the cache exceeds its byte budget", async () => {
|
||||
@@ -252,38 +243,14 @@ describe("ReplicaCache", () => {
|
||||
expect(Object.keys(useSessionStore.getState().sessions).sort()).toEqual(["host-a", "host-c"]);
|
||||
});
|
||||
|
||||
it("rejects version 1 cache data and overwrites it on flush", async () => {
|
||||
it("drops malformed or unknown cache versions", async () => {
|
||||
const storage = new MemoryStorage();
|
||||
storage.values.set(
|
||||
"@paseo:replica-cache",
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
hosts: [
|
||||
{
|
||||
serverId: SERVER_ID,
|
||||
agents: [],
|
||||
workspaces: [],
|
||||
emptyProjects: [],
|
||||
timeline: {
|
||||
agentId: "agent-1",
|
||||
items: [],
|
||||
cursor: { epoch: "poisoned", startSeq: 1, endSeq: 100 },
|
||||
hasOlder: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
storage.values.set("@paseo:replica-cache", JSON.stringify({ version: 999, hosts: [] }));
|
||||
const cache = new ReplicaCache(storage);
|
||||
cache.setHosts([SERVER_ID]);
|
||||
|
||||
await cache.restore();
|
||||
await cache.flush();
|
||||
|
||||
expect(useSessionStore.getState().sessions[SERVER_ID]).toBeUndefined();
|
||||
expect(JSON.parse(storage.values.get("@paseo:replica-cache") ?? "null")).toEqual({
|
||||
version: 2,
|
||||
hosts: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
import {
|
||||
normalizeEmptyProjectDescriptor,
|
||||
normalizeWorkspaceDescriptor,
|
||||
selectAgentTimelineState,
|
||||
useSessionStore,
|
||||
type Agent,
|
||||
type SessionReplica,
|
||||
@@ -20,10 +19,9 @@ import {
|
||||
} from "@/stores/session-store";
|
||||
import type { StreamItem } from "@/types/stream";
|
||||
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||
import { getSendingClientMessageIds } from "@/composer/submission/model";
|
||||
|
||||
const STORAGE_KEY = "@paseo:replica-cache";
|
||||
const CACHE_VERSION = 2;
|
||||
const CACHE_VERSION = 1;
|
||||
const PERSIST_DELAY_MS = 750;
|
||||
const MAX_TIMELINE_ITEMS = 50;
|
||||
const MAX_CACHE_BYTES = 1024 * 1024;
|
||||
@@ -38,6 +36,14 @@ const StoredAgentSchema = z.object({
|
||||
const StoredTimelineSchema = z.object({
|
||||
agentId: z.string(),
|
||||
items: z.unknown(),
|
||||
cursor: z
|
||||
.object({
|
||||
epoch: z.string(),
|
||||
startSeq: z.number().int().nonnegative(),
|
||||
endSeq: z.number().int().nonnegative(),
|
||||
})
|
||||
.nullable(),
|
||||
hasOlder: z.boolean(),
|
||||
});
|
||||
|
||||
const StoredHostSchema = z.object({
|
||||
@@ -134,6 +140,8 @@ function deserializeTimeline(stored: StoredHost["timeline"]): SessionReplica["ti
|
||||
return {
|
||||
agentId: stored.agentId,
|
||||
items: decoded,
|
||||
cursor: stored.cursor,
|
||||
hasOlder: stored.hasOlder,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -362,29 +370,14 @@ export class ReplicaCache {
|
||||
(workspace) => workspace.workspaceDirectory === focusedAgent.cwd,
|
||||
))
|
||||
: undefined;
|
||||
const localSubmissionIds = new Set(
|
||||
getSendingClientMessageIds(
|
||||
focusedAgentId ? session.messageSubmissions.get(focusedAgentId) : undefined,
|
||||
),
|
||||
);
|
||||
const timelineState = focusedAgentId
|
||||
? selectAgentTimelineState(session, focusedAgentId)
|
||||
: { status: "cold" as const };
|
||||
const items =
|
||||
timelineState.status === "cold"
|
||||
? undefined
|
||||
: timelineState.items.filter(
|
||||
(item) =>
|
||||
item.kind !== "user_message" ||
|
||||
item.messageId !== undefined ||
|
||||
!item.clientMessageId ||
|
||||
!localSubmissionIds.has(item.clientMessageId),
|
||||
);
|
||||
const items = focusedAgentId ? session.agentStreamTail.get(focusedAgentId) : undefined;
|
||||
const timeline =
|
||||
focusedAgent && items
|
||||
? {
|
||||
agentId: focusedAgent.id,
|
||||
items: encodeDates(items.slice(-MAX_TIMELINE_ITEMS)),
|
||||
cursor: session.agentTimelineCursor.get(focusedAgent.id) ?? null,
|
||||
hasOlder: session.agentTimelineHasOlder.get(focusedAgent.id) ?? false,
|
||||
}
|
||||
: null;
|
||||
const stored: StoredHost = {
|
||||
|
||||
Reference in New Issue
Block a user