fix: reload agent rehydrates timeline from disk under new epoch

Reload was a no-op for imported agents whose on-disk session was mutated
externally. The server preserved the in-memory timeline + historyPrimed
flag, so hydrate short-circuited and the epoch never changed. The client
also fetched without a cursor on reload, so the server returned
reset:false and the reducer dropped the new-epoch rows against a stale
cursor — the chat stayed stale until refocus.

Server: reloadAgentSession gains rehydrateFromDisk (used by the refresh
RPC). When set, durable + in-memory timeline are wiped and historyPrimed
is reset, so register mints a new epoch and hydrateTimelineFromProvider
re-streams the freshly read provider history. Voice-mode reloads keep
the existing preserve-timeline semantics.

Client: handleReloadAgent shows a sticky "Reloading agent…" toast,
sends the stale cursor with the fetch so the server returns reset:true,
and only flips to "Reloaded agent" after the response listener has
replaced the visible tail.
This commit is contained in:
Mohamed Boudra
2026-05-09 19:52:40 +07:00
parent c725352888
commit 43f136deba
4 changed files with 191 additions and 5 deletions
@@ -2441,14 +2441,28 @@ function WorkspaceScreenContent({
return;
}
toast.show("Reloading agent…", { durationMs: null });
try {
await client.refreshAgent(agentId);
// Send the existing cursor so the server detects the new epoch and
// returns reset:true. Without a cursor, the server returns reset:false
// and the client takes the incremental path, where new-epoch rows are
// dropped against the stale cursor.
const sessionState = useSessionStore.getState().sessions[normalizedServerId];
const currentCursor = sessionState?.agentTimelineCursor.get(agentId);
await client.fetchAgentTimeline(agentId, {
direction: "tail",
projection: "canonical",
...(currentCursor
? { cursor: { epoch: currentCursor.epoch, seq: currentCursor.endSeq } }
: {}),
});
toast.show("Reloaded agent", { variant: "success" });
} catch (error) {
toast.error(error instanceof Error ? error.message : "Failed to reload agent");
}
},
[client, isConnected, toast],
[client, isConnected, normalizedServerId, toast],
);
const handleCopyWorkspacePath = useCallback(async () => {