fix(sync): replay live deltas after refresh failures

This commit is contained in:
Mohamed Boudra
2026-07-12 23:53:45 +02:00
parent f9254c1066
commit 406f476297
5 changed files with 23 additions and 2 deletions

View File

@@ -593,7 +593,10 @@ function SessionProviderInternal({ children, serverId, client }: SessionProvider
isCurrent: () => workspaceHydrationRef.current?.id === transaction.id,
});
} catch (error) {
if (workspaceHydrationRef.current === transaction) workspaceHydrationRef.current = null;
if (workspaceHydrationRef.current === transaction) {
workspaceHydrationRef.current = null;
for (const delta of transaction.deltas) applyWorkspaceUpdatePayload(delta);
}
throw error;
}
if (!snapshot || options?.isCancelled?.()) {

View File

@@ -2289,6 +2289,14 @@ export class HostRuntimeStore {
});
} catch (error) {
if (!(error instanceof AgentDirectoryRefreshSupersededError)) {
if (
this.agentDirectoryTransactions.get(input.serverId)?.id === transaction.id &&
hasMatchingSession()
) {
for (const delta of transaction.deltas) {
this.applyAgentDirectoryDelta(input.serverId, delta);
}
}
controller.markAgentDirectorySyncError(toErrorMessage(error));
}
throw error;

View File

@@ -6,6 +6,7 @@ import pino from "pino";
import { afterEach, describe, expect, test, vi } from "vitest";
import { PARENT_AGENT_ID_LABEL } from "@getpaseo/protocol/agent-labels";
import { CLIENT_CAPS } from "@getpaseo/protocol/client-capabilities";
import type { WorkspaceDescriptorPayload } from "@getpaseo/protocol/messages";
import {
decodeFileTransferFrame,
@@ -4247,6 +4248,8 @@ test("unions viewed timelines across socket sources and removes detached sources
const legacySocket = {};
session.updateClientCapabilities(null, legacySocket);
expect(session.supportsForSource(CLIENT_CAPS.selectiveAgentTimeline, legacySocket)).toBe(false);
expect(session.supportsForSource(CLIENT_CAPS.selectiveAgentTimeline, firstSocket)).toBe(true);
messages.length = 0;
forward({
type: "agent_stream",

View File

@@ -976,6 +976,13 @@ export class Session {
return this.clientCapabilities.has(capability);
}
supportsForSource(capability: ClientCapability, source: object): boolean {
if (capability === CLIENT_CAPS.selectiveAgentTimeline) {
return this.selectiveTimelineCapabilityBySource.get(source) ?? this.supports(capability);
}
return this.supports(capability);
}
async syncWorkspaceGitObserverForWorkspace(workspace: PersistedWorkspaceRecord): Promise<void> {
await this.workspaceGitObserver.syncObserverForWorkspace(workspace);
}

View File

@@ -2010,7 +2010,7 @@ export class VoiceAssistantWebSocketServer {
notification,
};
const message = wrapSessionMessage(
connection?.session.supports(CLIENT_CAPS.selectiveAgentTimeline)
connection?.session.supportsForSource(CLIENT_CAPS.selectiveAgentTimeline, ws)
? {
type: "agent_attention_required",
payload: attentionPayload,