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.
This commit is contained in:
Mohamed Boudra
2026-07-17 11:12:45 +02:00
parent bce2c50b9e
commit 95f68ef829
37 changed files with 3762 additions and 814 deletions
@@ -1,4 +1,8 @@
export const CLIENT_CAPS = {
// COMPAT(selectiveAgentTimeline): added in v0.1.106. Capable clients receive
// agent streams only for their explicit viewed set. Remove after 2027-01-12
// once the supported client floor is >= v0.1.106.
selectiveAgentTimeline: "selective_agent_timeline",
reasoningMergeEnum: "reasoning_merge_enum",
// COMPAT(customModeIcons): added in v0.1.84. Old clients pin AgentModeIcon to
// a closed enum and crash rendering unknown values; daemon downgrades icons
+32
View File
@@ -437,3 +437,35 @@ describe("daemon update messages", () => {
});
});
});
describe("viewed timeline subscription messages", () => {
test("parses a complete viewed-agent set and its acknowledgement", () => {
const request = SessionInboundMessageSchema.parse({
type: "agent.timeline.set_subscription.request",
agentIds: ["agent-a", "agent-b"],
requestId: "timeline-subscription-1",
});
const response = SessionOutboundMessageSchema.parse({
type: "agent.timeline.set_subscription.response",
payload: {
agentIds: ["agent-a", "agent-b"],
requestId: "timeline-subscription-1",
},
});
expect({ request, response }).toEqual({
request: {
type: "agent.timeline.set_subscription.request",
agentIds: ["agent-a", "agent-b"],
requestId: "timeline-subscription-1",
},
response: {
type: "agent.timeline.set_subscription.response",
payload: {
agentIds: ["agent-a", "agent-b"],
requestId: "timeline-subscription-1",
},
},
});
});
});
+41
View File
@@ -1369,6 +1369,12 @@ export const ProviderSubagentTimelineRequestMessageSchema = z.object({
limit: z.number().int().nonnegative().optional(),
});
export const SetAgentTimelineSubscriptionRequestMessageSchema = z.object({
type: z.literal("agent.timeline.set_subscription.request"),
agentIds: z.array(z.string()),
requestId: z.string(),
});
export const AgentForkContextRequestMessageSchema = z.object({
type: z.literal("agent.fork_context.request"),
agentId: z.string(),
@@ -2359,6 +2365,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
FetchAgentTimelineRequestMessageSchema,
ProviderSubagentListRequestMessageSchema,
ProviderSubagentTimelineRequestMessageSchema,
SetAgentTimelineSubscriptionRequestMessageSchema,
AgentForkContextRequestMessageSchema,
SetAgentModeRequestMessageSchema,
SetAgentModelRequestMessageSchema,
@@ -2678,6 +2685,8 @@ export const ServerInfoStatusPayloadSchema = z
// Daemon advertises pluggable non-GitHub forge support (the forge registry);
// the client gates non-GitHub setup UI on it.
forgeProviders: z.boolean().optional(),
// COMPAT(selectiveAgentTimeline): added in v0.1.106, remove after 2027-01-12.
selectiveAgentTimeline: z.boolean().optional(),
})
.optional(),
})
@@ -3425,6 +3434,35 @@ export const ProviderSubagentUpdateMessageSchema = z.object({
]),
});
export const SetAgentTimelineSubscriptionResponseMessageSchema = z.object({
type: z.literal("agent.timeline.set_subscription.response"),
payload: z.object({
agentIds: z.array(z.string()),
requestId: z.string(),
}),
});
export const AgentAttentionRequiredMessageSchema = z.object({
type: z.literal("agent_attention_required"),
payload: z.object({
agentId: z.string(),
reason: z.enum(["finished", "error", "permission"]),
timestamp: z.string(),
shouldNotify: z.boolean(),
notification: z
.object({
title: z.string(),
body: z.string(),
data: z.object({
serverId: z.string(),
agentId: z.string(),
reason: z.enum(["finished", "error", "permission"]),
}),
})
.optional(),
}),
});
export const AgentForkContextResponseMessageSchema = z.object({
type: z.literal("agent.fork_context.response"),
payload: z.object({
@@ -4832,6 +4870,8 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
ProviderSubagentListResponseMessageSchema,
ProviderSubagentTimelineResponseMessageSchema,
ProviderSubagentUpdateMessageSchema,
SetAgentTimelineSubscriptionResponseMessageSchema,
AgentAttentionRequiredMessageSchema,
AgentForkContextResponseMessageSchema,
CancelAgentResponseMessageSchema,
ClearAgentAttentionResponseMessageSchema,
@@ -5365,6 +5405,7 @@ export const WSHelloMessageSchema = z.object({
voice: z.boolean().optional(),
pushNotifications: z.boolean().optional(),
[CLIENT_CAPS.reasoningMergeEnum]: z.boolean().optional(),
[CLIENT_CAPS.selectiveAgentTimeline]: z.boolean().optional(),
[CLIENT_CAPS.customModeIcons]: z.boolean().optional(),
[CLIENT_CAPS.terminalReflowableSnapshot]: z.boolean().optional(),
[CLIENT_CAPS.providerSubagents]: z.boolean().optional(),