fix(timeline): preserve activity through turn handoff

Correlate lifecycle events and snapshots with daemon-owned turn identity, and keep optimistic submissions active until their canonical row arrives. This prevents stale updates and RPC timing from exposing a false idle state.
This commit is contained in:
Mohamed Boudra
2026-08-01 00:43:38 +02:00
parent d17194c611
commit 4e34668a19
64 changed files with 2513 additions and 638 deletions
@@ -144,6 +144,47 @@ describe("agent feature schemas", () => {
expect(parsed.features?.[0]?.id).toBe("fast_mode");
});
it("accepts optional active turn identity without requiring it from old daemons", () => {
const base = {
id: "agent-turn-identity",
provider: "codex",
cwd: "/tmp/project",
model: "gpt-5",
createdAt: "2026-07-31T12:00:00.000Z",
updatedAt: "2026-07-31T12:00:01.000Z",
lastUserMessageAt: "2026-07-31T12:00:00.000Z",
status: "running",
capabilities: {
supportsStreaming: true,
supportsSessionPersistence: true,
supportsDynamicModes: true,
supportsMcpServers: true,
supportsReasoningStream: true,
supportsToolInvocations: true,
},
currentModeId: null,
availableModes: [],
pendingPermissions: [],
persistence: null,
title: null,
labels: {},
};
expect(AgentSnapshotPayloadSchema.parse(base).activeTurn).toBeUndefined();
expect(
AgentSnapshotPayloadSchema.parse({
...base,
activeTurn: {
turnId: "turn-1",
startedAt: "2026-07-31T12:00:00.000Z",
},
}).activeTurn,
).toEqual({
turnId: "turn-1",
startedAt: "2026-07-31T12:00:00.000Z",
});
});
it("defaults missing rewind capabilities to false", () => {
const parsed = AgentSnapshotPayloadSchema.parse({
id: "agent-123",
+12
View File
@@ -612,15 +612,18 @@ export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("turn_started"),
provider: AgentProviderSchema,
turnId: z.string().optional(),
}),
z.object({
type: z.literal("turn_completed"),
provider: AgentProviderSchema,
turnId: z.string().optional(),
usage: AgentUsageSchema.optional(),
}),
z.object({
type: z.literal("turn_failed"),
provider: AgentProviderSchema,
turnId: z.string().optional(),
error: z.string(),
code: z.string().optional(),
diagnostic: z.string().optional(),
@@ -628,6 +631,7 @@ export const AgentStreamEventPayloadSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("turn_canceled"),
provider: AgentProviderSchema,
turnId: z.string().optional(),
reason: z.string(),
}),
z.object({
@@ -685,6 +689,11 @@ const AgentRuntimeInfoSchema: z.ZodType<AgentRuntimeInfo> = z.object({
extra: z.record(z.string(), z.unknown()).optional(),
});
const AgentActiveTurnPayloadSchema = z.object({
turnId: z.string(),
startedAt: z.string().nullable(),
});
export const AgentSnapshotPayloadSchema = z.object({
id: z.string(),
provider: AgentProviderSchema,
@@ -698,6 +707,7 @@ export const AgentSnapshotPayloadSchema = z.object({
updatedAt: z.string(),
lastUserMessageAt: z.string().nullable(),
status: AgentStatusSchema,
activeTurn: AgentActiveTurnPayloadSchema.nullable().optional(),
capabilities: AgentCapabilityFlagsSchema,
currentModeId: z.string().nullable(),
availableModes: z.array(AgentModeSchema),
@@ -2830,6 +2840,8 @@ export const ServerInfoStatusPayloadSchema = z
selectiveAgentTimeline: z.boolean().optional(),
// COMPAT(canonicalSubmittedPrompts): added in v0.2.6, remove gate after 2027-01-30.
canonicalSubmittedPrompts: z.boolean().optional(),
// COMPAT(agentTurnIdentity): accept peers that observed pre-release v0.2.6 through 2027-01-31.
agentTurnIdentity: z.boolean().optional(),
// COMPAT(stableProjectIdentity): added in v0.1.109, remove gate after 2027-01-15.
stableProjectIdentity: z.boolean().optional(),
// COMPAT(workspaceScriptManagement): added in v0.1.105, remove gate after 2027-01-10.