Resume cached timelines without replaying their tail (#3329)

* perf(timeline): resume cached history from its exact range

Warm launches were repainting cached history and then downloading the same bounded tail to rediscover that nothing changed. Persist synchronization authority only when the complete current canonical window can be encoded losslessly; otherwise retain the existing display-only fallback.

* test(timeline): hide replica setup behind E2E helper

* fix(timeline): bound persisted resume overflow

* test(timeline): cover workspace eviction catch-up order

* test(timeline): read persisted range from IndexedDB
This commit is contained in:
Mohamed Boudra
2026-08-14 12:57:44 +02:00
committed by GitHub
parent 30195d3bd1
commit f0bd2c8483
18 changed files with 707 additions and 75 deletions
@@ -49,7 +49,7 @@ afterEach(() => {
});
describe("ensureAgentIsInitialized", () => {
it("requests a bounded projected tail when authoritative history is loaded", () => {
it("catches up after loaded authoritative history", () => {
const client = new FakeDaemonClient();
const runtime = new FakeTimelineRuntime();
useSessionStore.getState().initializeSession(serverId, client as never);
@@ -74,13 +74,14 @@ describe("ensureAgentIsInitialized", () => {
serverId,
agentId,
request: {
direction: "tail",
direction: "after",
cursor: { epoch: "epoch-1", seq: 42 },
limit: TIMELINE_FETCH_PAGE_SIZE,
projection: "projected",
},
},
]);
expect(getInitDeferred(getInitKey(serverId, agentId))?.requestDirection).toBe("tail");
expect(getInitDeferred(getInitKey(serverId, agentId))?.requestDirection).toBe("after");
});
it("requests a bounded projected tail when no authoritative cursor is available", () => {
@@ -127,6 +128,8 @@ describe("ensureAgentIsInitialized", () => {
timestamp: new Date("2026-07-27T10:00:00.000Z"),
},
],
range: null,
hasOlder: false,
},
});
@@ -151,6 +154,52 @@ describe("ensureAgentIsInitialized", () => {
]);
});
it("catches up after the restored canonical replica range", () => {
const client = new FakeDaemonClient();
const runtime = new FakeTimelineRuntime();
useSessionStore.getState().restoreSessionReplica(serverId, {
agents: new Map(),
workspaces: new Map(),
projects: new Map(),
timeline: {
agentId,
items: [
{
kind: "assistant_message",
id: "canonical-item",
text: "Canonical before restart",
timelineCursor: { epoch: "epoch-1", seq: 12 },
timestamp: new Date("2026-07-27T10:00:00.000Z"),
},
],
range: { epoch: "epoch-1", startSeq: 10, endSeq: 12 },
hasOlder: true,
},
});
void ensureAgentIsInitialized({
serverId,
agentId,
client: client as never,
runtime,
setAgentInitializing: bindSetAgentInitializing(),
});
expect(runtime.requests).toEqual([
{
serverId,
agentId,
request: {
direction: "after",
cursor: { epoch: "epoch-1", seq: 12 },
limit: TIMELINE_FETCH_PAGE_SIZE,
projection: "projected",
},
},
]);
expect(getInitDeferred(getInitKey(serverId, agentId))?.requestDirection).toBe("after");
});
it("times out initialization after 65 seconds", async () => {
vi.useFakeTimers();
const client = new FakeDaemonClient();
@@ -1,7 +1,7 @@
import { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next";
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
import { useSessionStore } from "@/stores/session-store";
import { selectAgentTimelineState, useSessionStore } from "@/stores/session-store";
import {
createInitDeferred,
getInitDeferred,
@@ -11,7 +11,7 @@ import {
refreshInitTimeout,
} from "@/utils/agent-initialization";
import { getHostRuntimeStore, type HostRuntimeStore } from "@/runtime/host-runtime";
import { planTimelineTailFetch } from "@/timeline/timeline-sync-plan";
import { planTimelineResumeFetch, planTimelineTailFetch } from "@/timeline/timeline-sync-plan";
import { i18n } from "@/i18n/i18next";
export type SetAgentInitializing = (agentId: string, initializing: boolean) => void;
@@ -51,7 +51,10 @@ export function ensureAgentIsInitialized(input: EnsureAgentIsInitializedInput):
return existing.promise;
}
const timelineRequest = planTimelineTailFetch();
const timeline = selectAgentTimelineState(useSessionStore.getState().sessions[serverId], agentId);
const timelineRequest = planTimelineResumeFetch(
timeline.status === "synced" ? timeline.range : null,
);
const deferred = createInitDeferred(key, timelineRequest.direction);
refreshAgentInitializationTimeout({ key, agentId, setAgentInitializing });