Fix mobile agent timeline catch-up
This commit is contained in:
@@ -14,7 +14,12 @@ import {
|
||||
type TimelineReducerSideEffect,
|
||||
} from "@/timeline/session-stream-reducers";
|
||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||
import { TIMELINE_FETCH_PAGE_SIZE } from "@/timeline/timeline-fetch-policy";
|
||||
import {
|
||||
isTimelineCatchUpComplete,
|
||||
planResumeTimelineSync,
|
||||
planTimelineCatchUpAfter,
|
||||
planTimelineCatchUpFollowUp,
|
||||
} from "@/timeline/timeline-sync-plan";
|
||||
import type { AgentAttachment, SessionOutboundMessage } from "@getpaseo/protocol/messages";
|
||||
import { parseServerInfoStatusPayload } from "@getpaseo/protocol/messages";
|
||||
import {
|
||||
@@ -500,6 +505,7 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
const revalidationTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const revalidationInFlightRef = useRef<Promise<void> | null>(null);
|
||||
const revalidationQueuedRef = useRef(false);
|
||||
const timelineCatchUpInFlightRef = useRef<Set<string>>(new Set());
|
||||
const wasConnectedRef = useRef(isConnected);
|
||||
const audioOutputBuffersRef = useRef<Map<string, BufferedAudioChunk[]>>(new Map());
|
||||
const activeAudioGroupsRef = useRef<Set<string>>(new Set());
|
||||
@@ -746,6 +752,27 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
}, AUTHORITATIVE_REVALIDATION_DEBOUNCE_MS);
|
||||
}, [client, flushAuthoritativeRevalidation, isConnected]);
|
||||
|
||||
const requestCanonicalCatchUp = useCallback(
|
||||
(agentId: string, cursor: { epoch: string; endSeq: number }) => {
|
||||
const request = planTimelineCatchUpAfter({ epoch: cursor.epoch, seq: cursor.endSeq });
|
||||
const key = `${agentId}:${request.cursor.epoch}:${request.cursor.seq}`;
|
||||
const inFlight = timelineCatchUpInFlightRef.current;
|
||||
if (inFlight.has(key)) {
|
||||
return;
|
||||
}
|
||||
inFlight.add(key);
|
||||
void client
|
||||
.fetchAgentTimeline(agentId, request)
|
||||
.catch((error) => {
|
||||
console.warn("[Session] failed to fetch canonical catch-up timeline", agentId, error);
|
||||
})
|
||||
.finally(() => {
|
||||
inFlight.delete(key);
|
||||
});
|
||||
},
|
||||
[client],
|
||||
);
|
||||
|
||||
const handleAppResumed = useCallback(
|
||||
(awayMs: number) => {
|
||||
scheduleAuthoritativeRevalidation();
|
||||
@@ -754,15 +781,19 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
const session = useSessionStore.getState().sessions[serverId];
|
||||
const agentId = session?.focusedAgentId;
|
||||
if (agentId) {
|
||||
void client
|
||||
.fetchAgentTimeline(agentId, {
|
||||
direction: "tail",
|
||||
limit: TIMELINE_FETCH_PAGE_SIZE,
|
||||
projection: "canonical",
|
||||
})
|
||||
.catch((error) => {
|
||||
const plan = planResumeTimelineSync({
|
||||
cursor: session?.agentTimelineCursor.get(agentId),
|
||||
});
|
||||
if (plan.direction === "after") {
|
||||
requestCanonicalCatchUp(agentId, {
|
||||
epoch: plan.cursor.epoch,
|
||||
endSeq: plan.cursor.seq,
|
||||
});
|
||||
} else {
|
||||
void client.fetchAgentTimeline(agentId, plan).catch((error) => {
|
||||
console.warn("[Session] failed to fetch tail timeline on resume", agentId, error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -771,7 +802,13 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
}
|
||||
bumpHistorySyncGeneration(serverId);
|
||||
},
|
||||
[bumpHistorySyncGeneration, client, scheduleAuthoritativeRevalidation, serverId],
|
||||
[
|
||||
bumpHistorySyncGeneration,
|
||||
client,
|
||||
requestCanonicalCatchUp,
|
||||
scheduleAuthoritativeRevalidation,
|
||||
serverId,
|
||||
],
|
||||
);
|
||||
|
||||
// Client activity tracking (heartbeat, push token registration)
|
||||
@@ -1018,22 +1055,6 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
[serverId, upsertWorkspaceSetupProgress],
|
||||
);
|
||||
|
||||
const requestCanonicalCatchUp = useCallback(
|
||||
(agentId: string, cursor: { epoch: string; endSeq: number }) => {
|
||||
void client
|
||||
.fetchAgentTimeline(agentId, {
|
||||
direction: "after",
|
||||
cursor: { epoch: cursor.epoch, seq: cursor.endSeq },
|
||||
limit: TIMELINE_FETCH_PAGE_SIZE,
|
||||
projection: "canonical",
|
||||
})
|
||||
.catch((error) => {
|
||||
console.warn("[Session] failed to fetch canonical catch-up timeline", agentId, error);
|
||||
});
|
||||
},
|
||||
[client],
|
||||
);
|
||||
|
||||
const applyTimelineResponse = useCallback(
|
||||
(
|
||||
payload: Extract<
|
||||
@@ -1043,8 +1064,13 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
) => {
|
||||
const agentId = payload.agentId;
|
||||
const initKey = getInitKey(serverId, agentId);
|
||||
const catchUpComplete = isTimelineCatchUpComplete({
|
||||
direction: payload.direction,
|
||||
hasNewer: payload.hasNewer,
|
||||
error: payload.error,
|
||||
});
|
||||
const shouldMarkAuthoritativeHistoryApplied =
|
||||
payload.direction === "tail" || payload.direction === "after";
|
||||
payload.direction === "tail" || (payload.direction === "after" && catchUpComplete);
|
||||
|
||||
// Read current store state
|
||||
const session = useSessionStore.getState().sessions[serverId];
|
||||
@@ -1114,6 +1140,19 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
|
||||
applyAgentUpdatePayload,
|
||||
});
|
||||
|
||||
const followUp = planTimelineCatchUpFollowUp({
|
||||
direction: payload.direction,
|
||||
hasNewer: payload.hasNewer,
|
||||
endCursor: payload.endCursor,
|
||||
error: payload.error,
|
||||
});
|
||||
if (followUp?.direction === "after") {
|
||||
requestCanonicalCatchUp(agentId, {
|
||||
epoch: followUp.cursor.epoch,
|
||||
endSeq: followUp.cursor.seq,
|
||||
});
|
||||
}
|
||||
|
||||
finalizeTimelineApplication({
|
||||
result,
|
||||
agentId,
|
||||
|
||||
Reference in New Issue
Block a user