Reduce workspace, agent, and chat sync traffic (#2028)

* feat(sync): keep live data scoped and current

Only viewed chats receive live timeline rows, while directory state now uses one subscribed bootstrap followed by ordered deltas. Legacy clients and daemons retain their existing behavior through centralized compatibility gates.

* fix(sync): preserve selective delivery boundaries

Keep selective timeline capability app-owned, union viewed sets across shared sockets, and reconcile directory side effects from accepted state. Visibility and archive suppression now follow their existing authoritative boundaries.

* test(app): align browser fixtures with runtime contracts

* fix(sync): preserve mixed-client delivery guarantees

* fix(sync): finish paged history before advancing

* fix(sync): replay live deltas after refresh failures

* test(server): model socket capability lookup

* test(app): use platform shortcut for split-pane coverage

* fix(app): keep hidden timelines dormant

* test(app): normalize split-pane shortcut setup

* fix(app): preserve sync state across retries

* fix(app): preserve background sync state across races

* fix directory bootstrap reconciliation edge cases

* fix selective timeline compatibility races

* fix superseded directory sync races

* fix(sync): centralize directory replica ordering

Own agent and workspace refresh transactions in HostRuntime so reconnect epochs, buffered updates, and lifecycle invalidation share one ordering boundary. Route timeline metadata and mixed-capability stream delivery through their authoritative runtime sources.

* fix(app): keep timeline requests runtime-scoped

* fix(app): close directory bootstrap races
This commit is contained in:
Mohamed Boudra
2026-07-17 17:03:34 +02:00
committed by GitHub
parent 6c99efae52
commit 1977d330ed
57 changed files with 5783 additions and 1343 deletions
+92 -1
View File
@@ -76,7 +76,7 @@ function createMockTransport() {
return {
transport,
sent,
triggerOpen: (options?: { preserveSent?: boolean }) => {
triggerOpen: (options?: { preserveSent?: boolean; features?: Record<string, boolean> }) => {
onOpen();
if (!options?.preserveSent) {
// Ignore HELLO handshake payloads in assertions.
@@ -92,6 +92,7 @@ function createMockTransport() {
serverId: `srv_test_${serverInfoOrdinal++}`,
hostname: null,
version: null,
...(options?.features ? { features: options.features } : {}),
},
},
}),
@@ -185,6 +186,7 @@ test("does not infer browser automation capabilities from Electron runtime", asy
})
.parse(JSON.parse(assertStr(mock.sent[0])));
expect(hello.capabilities[CLIENT_CAPS.browserHost]).toBeUndefined();
expect(hello.capabilities[CLIENT_CAPS.selectiveAgentTimeline]).toBeUndefined();
});
test("advertises consumer-provided browser automation capabilities", async () => {
@@ -219,6 +221,95 @@ test("advertises consumer-provided browser automation capabilities", async () =>
});
});
test("sets the complete viewed timeline subscription only when the daemon supports it", async () => {
const supportedTransport = createMockTransport();
const supportedClient = new DaemonClient({
url: "ws://test",
clientId: "timeline_supported",
transportFactory: () => supportedTransport.transport,
reconnect: { enabled: false },
});
const legacyTransport = createMockTransport();
const legacyClient = new DaemonClient({
url: "ws://test",
clientId: "timeline_legacy",
transportFactory: () => legacyTransport.transport,
reconnect: { enabled: false },
});
clients.push(supportedClient, legacyClient);
const supportedConnect = supportedClient.connect();
supportedTransport.triggerOpen({ features: { selectiveAgentTimeline: true } });
await supportedConnect;
const legacyConnect = legacyClient.connect();
legacyTransport.triggerOpen();
await legacyConnect;
expect(supportedClient.getLastServerInfoMessage()?.features).toEqual({
selectiveAgentTimeline: true,
});
const setPromise = supportedClient.setAgentTimelineSubscription(["agent-b", "agent-a"]);
await Promise.resolve();
const request = parseSentFrame(supportedTransport.sent[0]);
supportedTransport.triggerMessage(
wrapSessionMessage({
type: "agent.timeline.set_subscription.response",
payload: {
requestId: request.requestId,
agentIds: ["agent-a", "agent-b"],
},
}),
);
await setPromise;
await legacyClient.setAgentTimelineSubscription(["agent-a"]);
expect({ request, legacyFrames: legacyTransport.sent }).toEqual({
request: {
type: "agent.timeline.set_subscription.request",
requestId: expect.any(String),
agentIds: ["agent-a", "agent-b"],
},
legacyFrames: [],
});
});
test("normalizes legacy and dedicated agent attention notifications", async () => {
const mock = createMockTransport();
const client = new DaemonClient({
url: "ws://test",
clientId: "attention_normalization",
transportFactory: () => mock.transport,
reconnect: { enabled: false },
});
clients.push(client);
const connect = client.connect();
mock.triggerOpen();
await connect;
const notifications: unknown[] = [];
client.onAgentAttentionRequired((notification) => notifications.push(notification));
const payload = {
agentId: "agent-a",
reason: "finished",
timestamp: "2026-07-12T00:00:00.000Z",
shouldNotify: true,
} as const;
mock.triggerMessage(
wrapSessionMessage({
type: "agent_stream",
payload: {
agentId: payload.agentId,
timestamp: payload.timestamp,
event: { type: "attention_required", provider: "codex", ...payload },
},
}),
);
mock.triggerMessage(wrapSessionMessage({ type: "agent_attention_required", payload }));
expect(notifications).toEqual([payload, payload]);
});
const noopLogger: Logger = {
debug: () => {},
info: () => {},
+63
View File
@@ -1,5 +1,6 @@
import type { z } from "zod";
import { CLIENT_CAPS, type ClientCapability } from "@getpaseo/protocol/client-capabilities";
import type { AgentAttentionNotificationPayload } from "@getpaseo/protocol/agent-attention-notification";
import {
AgentCreateFailedStatusPayloadSchema,
AgentCreatedStatusPayloadSchema,
@@ -317,6 +318,14 @@ export interface SendMessageOptions {
attachments?: SendAgentMessageRequest["attachments"];
}
export interface AgentAttentionRequiredNotification {
agentId: string;
reason: "finished" | "error" | "permission";
timestamp: string;
shouldNotify: boolean;
notification?: AgentAttentionNotificationPayload;
}
type AgentConfigOverrides = Partial<Omit<AgentSessionConfig, "provider" | "cwd">>;
export interface CreateAgentRequestOptions extends AgentConfigOverrides {
@@ -1478,6 +1487,31 @@ export class DaemonClient {
};
}
onAgentAttentionRequired(
handler: (notification: AgentAttentionRequiredNotification) => void,
): () => void {
const unsubscribeLegacy = this.on("agent_stream", (message) => {
if (message.payload.event.type !== "attention_required") {
return;
}
const event = message.payload.event;
handler({
agentId: message.payload.agentId,
reason: event.reason,
timestamp: event.timestamp,
shouldNotify: event.shouldNotify,
...(event.notification ? { notification: event.notification } : {}),
});
});
const unsubscribeDedicated = this.on("agent_attention_required", (message) => {
handler(message.payload);
});
return () => {
unsubscribeLegacy();
unsubscribeDedicated();
};
}
// ============================================================================
// Core Send Helpers
// ============================================================================
@@ -2687,6 +2721,35 @@ export class DaemonClient {
return payload;
}
async setAgentTimelineSubscription(agentIds: string[]): Promise<void> {
// COMPAT(selectiveAgentTimeline): added in v0.1.106. Old daemons keep their
// legacy global stream and do not understand this RPC. Remove after
// 2027-01-12 once the supported daemon floor is >= v0.1.106.
if (!this.lastServerInfoMessage?.features?.selectiveAgentTimeline) {
return;
}
const requestId = this.createRequestId();
const normalizedAgentIds = [...new Set(agentIds)].sort();
const message = SessionInboundMessageSchema.parse({
type: "agent.timeline.set_subscription.request",
agentIds: normalizedAgentIds,
requestId,
});
await this.sendRequest({
requestId,
message,
options: { skipQueue: true },
select: (response) => {
if (response.type !== "agent.timeline.set_subscription.response") {
return null;
}
return response.payload.requestId === requestId ? response.payload : null;
},
});
}
async buildAgentForkContext(
agentId: string,
options: AgentForkContextOptions = {},