fix(app): paginate canonical timeline catch-up to avoid relay 1009 disconnect loop (#657)
@fireblue diagnosed the production failure mode: long canonical timeline catch-up could exceed the relay WebSocket frame cap, close with 1009, and send clients into a reconnect loop. Their seatbelt fix also removed the `limit: 0` footgun that let app callers request an unbounded frame. This maintainer pass keeps that diagnosis and redirects the UX around bounded projected pages: - initial agent load fetches the latest canonical tail - app resume/reconnect re-fetches the latest tail instead of chaining after-cursor pages - in-stream gaps still use bounded after-cursor catch-up - older history loads explicitly when the user scrolls to the oldest edge - shared page size is TIMELINE_FETCH_PAGE_SIZE = 100 projected timeline items, matching the daemon's canonical projection semantics rather than raw delta chunks Original diagnosis and fix by @fireblue. Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
This commit is contained in:
co-authored by
Mohamed Boudra
parent
906205f97a
commit
654439005a
@@ -268,6 +268,8 @@ export interface SessionState {
|
||||
agentStreamTail: Map<string, StreamItem[]>;
|
||||
agentStreamHead: Map<string, StreamItem[]>;
|
||||
agentTimelineCursor: Map<string, AgentTimelineCursorState>;
|
||||
agentTimelineHasOlder: Map<string, boolean>;
|
||||
agentTimelineOlderFetchInFlight: Map<string, boolean>;
|
||||
historySyncGeneration: number;
|
||||
agentHistorySyncGeneration: Map<string, number>;
|
||||
agentAuthoritativeHistoryApplied: Map<string, boolean>;
|
||||
@@ -351,6 +353,14 @@ interface SessionStoreActions {
|
||||
| Map<string, AgentTimelineCursorState>
|
||||
| ((prev: Map<string, AgentTimelineCursorState>) => Map<string, AgentTimelineCursorState>),
|
||||
) => void;
|
||||
setAgentTimelineHasOlder: (
|
||||
serverId: string,
|
||||
state: Map<string, boolean> | ((prev: Map<string, boolean>) => Map<string, boolean>),
|
||||
) => void;
|
||||
setAgentTimelineOlderFetchInFlight: (
|
||||
serverId: string,
|
||||
state: Map<string, boolean> | ((prev: Map<string, boolean>) => Map<string, boolean>),
|
||||
) => void;
|
||||
bumpHistorySyncGeneration: (serverId: string) => void;
|
||||
markAgentHistorySynchronized: (serverId: string, agentId: string) => void;
|
||||
setAgentAuthoritativeHistoryApplied: (
|
||||
@@ -443,6 +453,8 @@ function createInitialSessionState(serverId: string, client: DaemonClient): Sess
|
||||
agentStreamTail: new Map(),
|
||||
agentStreamHead: new Map(),
|
||||
agentTimelineCursor: new Map(),
|
||||
agentTimelineHasOlder: new Map(),
|
||||
agentTimelineOlderFetchInFlight: new Map(),
|
||||
historySyncGeneration: 0,
|
||||
agentHistorySyncGeneration: new Map(),
|
||||
agentAuthoritativeHistoryApplied: new Map(),
|
||||
@@ -861,6 +873,48 @@ export const useSessionStore = create<SessionStore>()(
|
||||
});
|
||||
},
|
||||
|
||||
setAgentTimelineHasOlder: (serverId, state) => {
|
||||
set((prev) => {
|
||||
const session = prev.sessions[serverId];
|
||||
if (!session) {
|
||||
return prev;
|
||||
}
|
||||
const nextState =
|
||||
typeof state === "function" ? state(session.agentTimelineHasOlder) : state;
|
||||
if (session.agentTimelineHasOlder === nextState) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
sessions: {
|
||||
...prev.sessions,
|
||||
[serverId]: { ...session, agentTimelineHasOlder: nextState },
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
setAgentTimelineOlderFetchInFlight: (serverId, state) => {
|
||||
set((prev) => {
|
||||
const session = prev.sessions[serverId];
|
||||
if (!session) {
|
||||
return prev;
|
||||
}
|
||||
const nextState =
|
||||
typeof state === "function" ? state(session.agentTimelineOlderFetchInFlight) : state;
|
||||
if (session.agentTimelineOlderFetchInFlight === nextState) {
|
||||
return prev;
|
||||
}
|
||||
return {
|
||||
...prev,
|
||||
sessions: {
|
||||
...prev.sessions,
|
||||
[serverId]: { ...session, agentTimelineOlderFetchInFlight: nextState },
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
bumpHistorySyncGeneration: (serverId) => {
|
||||
set((prev) => {
|
||||
const session = prev.sessions[serverId];
|
||||
|
||||
Reference in New Issue
Block a user