| ... | @@ -1,5 +1,22 @@ | ... | @@ -1,5 +1,22 @@ |
| 1 | import { describe, expect, it } from "vite-plus/test"; | 1 | import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { CheckpointRef, MessageId, TurnId } from "@t3tools/contracts"; | 2 | import { |
| | 3 | CheckpointRef, |
| | 4 | EnvironmentId, |
| | 5 | EventId, |
| | 6 | MessageId, |
| | 7 | ProjectId, |
| | 8 | ProviderInstanceId, |
| | 9 | ThreadId, |
| | 10 | TurnId, |
| | 11 | type OrchestrationThread, |
| | 12 | } from "@t3tools/contracts"; |
| | 13 | import { |
| | 14 | applyThreadDetailEvent, |
| | 15 | createEnvironmentThreadDetailAtoms, |
| | 16 | EMPTY_ENVIRONMENT_THREAD_STATE, |
| | 17 | } from "@t3tools/client-runtime/state/threads"; |
| | 18 | import * as Option from "effect/Option"; |
| | 19 | import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; |
| 3 | import { | 20 | import { |
| 4 | computeStableMessagesTimelineRows, | 21 | computeStableMessagesTimelineRows, |
| 5 | computeMessageDurationStart, | 22 | computeMessageDurationStart, |
| ... | @@ -12,6 +29,7 @@ import { | ... | @@ -12,6 +29,7 @@ import { |
| 12 | shouldFollowWorkGroupAppend, | 29 | shouldFollowWorkGroupAppend, |
| 13 | shouldPreserveAssistantLineBreaks, | 30 | shouldPreserveAssistantLineBreaks, |
| 14 | type MessagesTimelineRow, | 31 | type MessagesTimelineRow, |
| | 32 | type MessagesTimelineRowsProjection, |
| 15 | workEntryDisplayLabel, | 33 | workEntryDisplayLabel, |
| 16 | } from "./MessagesTimeline.logic"; | 34 | } from "./MessagesTimeline.logic"; |
| 17 | import { | 35 | import { |
| ... | @@ -19,8 +37,8 @@ import { | ... | @@ -19,8 +37,8 @@ import { |
| 19 | deriveTimelineEntries, | 37 | deriveTimelineEntries, |
| 20 | deriveTimelineEntriesWithState, | 38 | deriveTimelineEntriesWithState, |
| 21 | type WorkLogEntry, | 39 | type WorkLogEntry, |
| | 40 | type TimelineEntriesProjection, |
| 22 | } from "../../session-logic"; | 41 | } from "../../session-logic"; |
| 23 | import { buildRevertTurnCountByUserMessageId } from "../ChatView.logic"; | | |
| 24 | import { isImageAttachment, type ChatMessage, type TurnDiffSummary } from "../../types"; | 42 | import { isImageAttachment, type ChatMessage, type TurnDiffSummary } from "../../types"; |
| 25 | | 43 | |
| 26 | describe("streaming row projection", () => { | 44 | describe("streaming row projection", () => { |
| ... | @@ -97,8 +115,8 @@ describe("streaming row projection", () => { | ... | @@ -97,8 +115,8 @@ describe("streaming row projection", () => { |
| 97 | runningTurnId: turnId, | 115 | runningTurnId: turnId, |
| 98 | isWorking: true, | 116 | isWorking: true, |
| 99 | activeTurnStartedAt: time(5), | 117 | activeTurnStartedAt: time(5), |
| 100 | turnDiffSummaryByAssistantMessageId: new Map<MessageId, TurnDiffSummary>(), | 118 | turnDiffSummaries: [], |
| 101 | revertTurnCountByUserMessageId: new Map<MessageId, number>(), | 119 | supportsConversationRollback: false, |
| 102 | } satisfies Parameters<typeof deriveMessagesTimelineRows>[0]; | 120 | } satisfies Parameters<typeof deriveMessagesTimelineRows>[0]; |
| 103 | return { messages, work, timeline, input, time, turnId, historyTurnId }; | 121 | return { messages, work, timeline, input, time, turnId, historyTurnId }; |
| 104 | } | 122 | } |
| ... | @@ -243,52 +261,54 @@ describe("streaming row projection", () => { | ... | @@ -243,52 +261,54 @@ describe("streaming row projection", () => { |
| 243 | }, | 261 | }, |
| 244 | ); | 262 | ); |
| 245 | | 263 | |
| 246 | it("reuses rows when the revert map is rebuilt from the streamed entries", () => { | 264 | it("owns checkpoint lookups across streaming and equal source snapshots", () => { |
| 247 | const initial = fixture("Partial"); | 265 | const initial = fixture("Partial"); |
| 248 | const inferredCheckpointTurnCountByTurnId = { [initial.historyTurnId]: 1 }; | 266 | let checkpointLookupReads = 0; |
| 249 | const turnDiffSummaryByAssistantMessageId = new Map<MessageId, TurnDiffSummary>([ | 267 | const summary: TurnDiffSummary = { |
| 250 | [ | 268 | turnId: initial.historyTurnId, |
| 251 | MessageId.make("history-assistant"), | 269 | get checkpointTurnCount() { |
| 252 | { | 270 | checkpointLookupReads += 1; |
| 253 | turnId: initial.historyTurnId, | 271 | return 1; |
| 254 | checkpointTurnCount: 1, | 272 | }, |
| 255 | checkpointRef: CheckpointRef.make("refs/t3/checkpoints/history-turn"), | 273 | checkpointRef: CheckpointRef.make("refs/t3/checkpoints/history-turn"), |
| 256 | status: "ready", | 274 | status: "ready", |
| 257 | files: [], | 275 | files: [], |
| 258 | assistantMessageId: MessageId.make("history-assistant"), | 276 | get assistantMessageId() { |
| 259 | completedAt: initial.time(4), | 277 | checkpointLookupReads += 1; |
| 260 | }, | 278 | return MessageId.make("history-assistant"); |
| 261 | ], | 279 | }, |
| 262 | ]); | 280 | get completedAt() { |
| 263 | let revertMap: Map<MessageId, number> | null = null; | 281 | checkpointLookupReads += 1; |
| 264 | // Mirrors ChatView: the map is derived from each delta's entries. | 282 | return initial.time(4); |
| 265 | const build = (timelineEntries: typeof initial.timeline.entries) => { | 283 | }, |
| 266 | revertMap = buildRevertTurnCountByUserMessageId( | | |
| 267 | { | | |
| 268 | supportsConversationRollback: true, | | |
| 269 | timelineEntries, | | |
| 270 | turnDiffSummaryByAssistantMessageId, | | |
| 271 | inferredCheckpointTurnCountByTurnId, | | |
| 272 | }, | | |
| 273 | revertMap, | | |
| 274 | ); | | |
| 275 | return { | | |
| 276 | ...initial.input, | | |
| 277 | timelineEntries, | | |
| 278 | turnDiffSummaryByAssistantMessageId, | | |
| 279 | revertTurnCountByUserMessageId: revertMap, | | |
| 280 | }; | | |
| 281 | }; | 284 | }; |
| 282 | const previous = deriveMessagesTimelineRowsWithState(build(initial.timeline.entries)); | 285 | const input = { |
| | 286 | ...initial.input, |
| | 287 | turnDiffSummaries: [summary], |
| | 288 | supportsConversationRollback: true, |
| | 289 | expandedTurnIds: new Set([initial.historyTurnId]), |
| | 290 | expandedWorkGroupIds: new Set<string>(), |
| | 291 | }; |
| | 292 | const previous = deriveMessagesTimelineRowsWithState(input); |
| | 293 | expect(checkpointLookupReads).toBeGreaterThan(0); |
| 283 | expect(previous.rows.some((row) => row.kind === "message" && row.revertTurnCount === 0)).toBe( | 294 | expect(previous.rows.some((row) => row.kind === "message" && row.revertTurnCount === 0)).toBe( |
| 284 | true, | 295 | true, |
| 285 | ); | 296 | ); |
| 286 | const last = initial.messages.at(-1)!; | 297 | const last = initial.messages.at(-1)!; |
| 287 | const messages = [...initial.messages.slice(0, -1), { ...last, text: "Partial token" }]; | 298 | const messages = [...initial.messages.slice(0, -1), { ...last, text: "Partial token" }]; |
| 288 | const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); | 299 | const timeline = deriveTimelineEntriesWithState(messages, [], initial.work, initial.timeline); |
| 289 | const next = deriveMessagesTimelineRowsWithState(build(timeline.entries), previous); | 300 | const nextInput = { |
| 290 | | 301 | ...input, |
| 291 | expect(next.rows).toEqual(deriveMessagesTimelineRows(build(timeline.entries))); | 302 | timelineEntries: timeline.entries, |
| | 303 | turnDiffSummaries: [...input.turnDiffSummaries], |
| | 304 | latestTurn: { ...input.latestTurn }, |
| | 305 | expandedTurnIds: new Set(input.expandedTurnIds), |
| | 306 | expandedWorkGroupIds: new Set(input.expandedWorkGroupIds), |
| | 307 | }; |
| | 308 | checkpointLookupReads = 0; |
| | 309 | const next = deriveMessagesTimelineRowsWithState(nextInput, previous); |
| | 310 | expect(checkpointLookupReads).toBe(0); |
| | 311 | expect(next.rows).toEqual(deriveMessagesTimelineRows(nextInput)); |
| 292 | for (const [index, row] of previous.rows.entries()) { | 312 | for (const [index, row] of previous.rows.entries()) { |
| 293 | if ((row.kind === "message" || row.kind === "assistant-meta") && row.message === last) { | 313 | if ((row.kind === "message" || row.kind === "assistant-meta") && row.message === last) { |
| 294 | expect(next.rows[index]).toMatchObject({ message: { text: "Partial token" } }); | 314 | expect(next.rows[index]).toMatchObject({ message: { text: "Partial token" } }); |
| ... | @@ -296,6 +316,205 @@ describe("streaming row projection", () => { | ... | @@ -296,6 +316,205 @@ describe("streaming row projection", () => { |
| 296 | expect(next.rows[index]).toBe(row); | 316 | expect(next.rows[index]).toBe(row); |
| 297 | } | 317 | } |
| 298 | } | 318 | } |
| | 319 | |
| | 320 | const changed = deriveMessagesTimelineRowsWithState( |
| | 321 | { ...nextInput, turnDiffSummaries: [{ ...summary, checkpointTurnCount: 3 }] }, |
| | 322 | next, |
| | 323 | ); |
| | 324 | expect( |
| | 325 | changed.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), |
| | 326 | ).toMatchObject({ revertTurnCount: 2 }); |
| | 327 | const unsupported = deriveMessagesTimelineRowsWithState( |
| | 328 | { ...changed.input, supportsConversationRollback: false }, |
| | 329 | changed, |
| | 330 | ); |
| | 331 | expect( |
| | 332 | unsupported.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), |
| | 333 | ).toMatchObject({ revertTurnCount: undefined }); |
| | 334 | expect( |
| | 335 | previous.rows.find((row) => row.kind === "message" && row.message.id === messages[0]?.id), |
| | 336 | ).toMatchObject({ revertTurnCount: 0 }); |
| | 337 | }); |
| | 338 | |
| | 339 | it("reuses long-thread rows through detail events, selectors, and attachment previews", () => { |
| | 340 | const initial = fixture("Partial"); |
| | 341 | let checkpointLookupReads = 0; |
| | 342 | const history = Array.from({ length: 250 }, (_, index) => { |
| | 343 | const turnId = TurnId.make(`older-turn-${index}`); |
| | 344 | const user = { |
| | 345 | ...initial.messages[0]!, |
| | 346 | id: MessageId.make(`older-user-${index}`), |
| | 347 | createdAt: new Date(Date.UTC(2026, 8, 3, 0, 0, index * 5)).toISOString(), |
| | 348 | attachments: [ |
| | 349 | { |
| | 350 | type: "image" as const, |
| | 351 | id: "image", |
| | 352 | name: "image.png", |
| | 353 | mimeType: "image/png", |
| | 354 | sizeBytes: 42, |
| | 355 | }, |
| | 356 | ], |
| | 357 | }; |
| | 358 | const assistant = { |
| | 359 | ...initial.messages[1]!, |
| | 360 | id: MessageId.make(`older-assistant-${index}`), |
| | 361 | turnId, |
| | 362 | createdAt: new Date(Date.UTC(2026, 8, 3, 0, 0, index * 5 + 3)).toISOString(), |
| | 363 | }; |
| | 364 | const checkpoint: TurnDiffSummary = { |
| | 365 | turnId, |
| | 366 | get checkpointTurnCount() { |
| | 367 | checkpointLookupReads += 1; |
| | 368 | return index + 1; |
| | 369 | }, |
| | 370 | checkpointRef: CheckpointRef.make(`refs/t3/checkpoints/older-${index}`), |
| | 371 | status: "ready", |
| | 372 | files: [], |
| | 373 | get assistantMessageId() { |
| | 374 | checkpointLookupReads += 1; |
| | 375 | return assistant.id; |
| | 376 | }, |
| | 377 | get completedAt() { |
| | 378 | checkpointLookupReads += 1; |
| | 379 | return assistant.createdAt; |
| | 380 | }, |
| | 381 | }; |
| | 382 | return { user, assistant, checkpoint }; |
| | 383 | }); |
| | 384 | const liveMessage = initial.messages.at(-1)!; |
| | 385 | let thread: OrchestrationThread = { |
| | 386 | id: ThreadId.make("streaming-thread"), |
| | 387 | projectId: ProjectId.make("project"), |
| | 388 | title: "Long thread", |
| | 389 | modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, |
| | 390 | runtimeMode: "full-access", |
| | 391 | interactionMode: "default", |
| | 392 | branch: null, |
| | 393 | worktreePath: null, |
| | 394 | latestTurn: { |
| | 395 | ...initial.input.latestTurn, |
| | 396 | requestedAt: initial.time(5), |
| | 397 | assistantMessageId: liveMessage.id, |
| | 398 | }, |
| | 399 | createdAt: initial.time(0), |
| | 400 | updatedAt: initial.time(7), |
| | 401 | archivedAt: null, |
| | 402 | settledOverride: null, |
| | 403 | settledAt: null, |
| | 404 | deletedAt: null, |
| | 405 | messages: [ |
| | 406 | ...history.flatMap(({ user, assistant }) => [user, assistant]), |
| | 407 | ...initial.messages, |
| | 408 | ], |
| | 409 | proposedPlans: [], |
| | 410 | activities: [], |
| | 411 | checkpoints: history.map(({ checkpoint }) => checkpoint), |
| | 412 | session: null, |
| | 413 | }; |
| | 414 | const state = Atom.make( |
| | 415 | AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(thread) }), |
| | 416 | ); |
| | 417 | const details = createEnvironmentThreadDetailAtoms(() => state); |
| | 418 | const ref = { environmentId: EnvironmentId.make("local"), threadId: thread.id }; |
| | 419 | const registry = AtomRegistry.make(); |
| | 420 | const unmount = registry.mount(details.detailAtom(ref)); |
| | 421 | const preview = createMessageAttachmentPreviewProjector(); |
| | 422 | let imageUrl = "https://first.test/image"; |
| | 423 | let timeline: TimelineEntriesProjection | null = null; |
| | 424 | let projection: MessagesTimelineRowsProjection | null = null; |
| | 425 | const project = () => { |
| | 426 | const selected = registry.get(details.detailAtom(ref)); |
| | 427 | if (selected === null) throw new Error("Missing thread detail"); |
| | 428 | const messages = selected.messages.map((message) => preview(message, () => imageUrl)); |
| | 429 | timeline = deriveTimelineEntriesWithState( |
| | 430 | messages, |
| | 431 | selected.proposedPlans, |
| | 432 | initial.work, |
| | 433 | timeline, |
| | 434 | ); |
| | 435 | projection = deriveMessagesTimelineRowsWithState( |
| | 436 | { |
| | 437 | timelineEntries: timeline.entries, |
| | 438 | latestTurn: selected.latestTurn, |
| | 439 | runningTurnId: |
| | 440 | selected.latestTurn?.state === "running" ? selected.latestTurn.turnId : null, |
| | 441 | isWorking: selected.latestTurn?.state === "running", |
| | 442 | activeTurnStartedAt: selected.latestTurn?.startedAt ?? null, |
| | 443 | turnDiffSummaries: selected.checkpoints, |
| | 444 | supportsConversationRollback: true, |
| | 445 | }, |
| | 446 | projection, |
| | 447 | ); |
| | 448 | return projection; |
| | 449 | }; |
| | 450 | const send = (text: string, sequence: number, streaming = true) => { |
| | 451 | const result = applyThreadDetailEvent(thread, { |
| | 452 | eventId: EventId.make(`delta-${sequence}`), |
| | 453 | sequence, |
| | 454 | commandId: null, |
| | 455 | causationEventId: null, |
| | 456 | correlationId: null, |
| | 457 | metadata: {}, |
| | 458 | occurredAt: initial.time(8 + sequence), |
| | 459 | aggregateKind: "thread", |
| | 460 | aggregateId: thread.id, |
| | 461 | type: "thread.message-sent", |
| | 462 | payload: { |
| | 463 | threadId: thread.id, |
| | 464 | messageId: liveMessage.id, |
| | 465 | role: "assistant", |
| | 466 | text, |
| | 467 | turnId: initial.turnId, |
| | 468 | streaming, |
| | 469 | createdAt: liveMessage.createdAt, |
| | 470 | updatedAt: initial.time(8 + sequence), |
| | 471 | }, |
| | 472 | }); |
| | 473 | if (result.kind !== "updated") throw new Error("Message event did not update the thread"); |
| | 474 | thread = result.thread; |
| | 475 | registry.set( |
| | 476 | state, |
| | 477 | AsyncResult.success({ ...EMPTY_ENVIRONMENT_THREAD_STATE, data: Option.some(thread) }), |
| | 478 | ); |
| | 479 | return project(); |
| | 480 | }; |
| | 481 | |
| | 482 | try { |
| | 483 | const first = project(); |
| | 484 | const saved = structuredClone(first.rows); |
| | 485 | expect(checkpointLookupReads).toBeGreaterThan(0); |
| | 486 | checkpointLookupReads = 0; |
| | 487 | for (let index = 0; index < 10; index += 1) { |
| | 488 | const next = send(` ${index}`, index + 1); |
| | 489 | for (const [rowIndex, row] of first.rows.entries()) { |
| | 490 | if ( |
| | 491 | (row.kind === "message" || row.kind === "assistant-meta") && |
| | 492 | row.message.id === liveMessage.id |
| | 493 | ) |
| | 494 | continue; |
| | 495 | expect(next.rows[rowIndex]).toBe(row); |
| | 496 | } |
| | 497 | } |
| | 498 | expect(checkpointLookupReads).toBe(0); |
| | 499 | const streamed = project(); |
| | 500 | expect(streamed.rows).toEqual(deriveMessagesTimelineRows(streamed.input)); |
| | 501 | |
| | 502 | imageUrl = "https://renewed.test/image"; |
| | 503 | const renewed = project(); |
| | 504 | expect(renewed.rows[0]).not.toBe(first.rows[0]); |
| | 505 | expect(renewed.rows[0]).toMatchObject({ |
| | 506 | message: { attachments: [{ previewUrl: imageUrl }] }, |
| | 507 | }); |
| | 508 | const completed = send("Complete", 11, false); |
| | 509 | expect(completed.rows).toEqual(deriveMessagesTimelineRows(completed.input)); |
| | 510 | expect( |
| | 511 | completed.rows.find((row) => row.kind === "message" && row.message.id === liveMessage.id), |
| | 512 | ).toMatchObject({ message: { text: "Complete" }, assistantCopyStreaming: false }); |
| | 513 | expect(first.rows).toEqual(saved); |
| | 514 | } finally { |
| | 515 | unmount(); |
| | 516 | registry.dispose(); |
| | 517 | } |
| 299 | }); | 518 | }); |
| 300 | | 519 | |
| 301 | it.each(["completion", "turn", "role", "ordering"] as const)( | 520 | it.each(["completion", "turn", "role", "ordering"] as const)( |
| ... | @@ -407,7 +626,7 @@ describe("streaming row projection", () => { | ... | @@ -407,7 +626,7 @@ describe("streaming row projection", () => { |
| 407 | ? { ...message, role: "user", turnId: null, createdAt: initial.time(0) } | 626 | ? { ...message, role: "user", turnId: null, createdAt: initial.time(0) } |
| 408 | : message, | 627 | : message, |
| 409 | ); | 628 | ); |
| 410 | check({ revertTurnCountByUserMessageId: new Map([[MessageId.make("live-user"), 3]]) }); | 629 | check({ supportsConversationRollback: true }); |
| 411 | }); | 630 | }); |
| 412 | }); | 631 | }); |
| 413 | | 632 | |
| ... | @@ -574,8 +793,8 @@ describe("work entry labels", () => { | ... | @@ -574,8 +793,8 @@ describe("work entry labels", () => { |
| 574 | ], | 793 | ], |
| 575 | isWorking: false, | 794 | isWorking: false, |
| 576 | activeTurnStartedAt: null, | 795 | activeTurnStartedAt: null, |
| 577 | turnDiffSummaryByAssistantMessageId: new Map(), | 796 | turnDiffSummaries: [], |
| 578 | revertTurnCountByUserMessageId: new Map(), | 797 | supportsConversationRollback: false, |
| 579 | }); | 798 | }); |
| 580 | const directRow = rows.find((row) => row.kind === "work"); | 799 | const directRow = rows.find((row) => row.kind === "work"); |
| 581 | expect(directRow).toMatchObject({ | 800 | expect(directRow).toMatchObject({ |
| ... | @@ -887,8 +1106,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -887,8 +1106,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 887 | ], | 1106 | ], |
| 888 | isWorking: false, | 1107 | isWorking: false, |
| 889 | activeTurnStartedAt: null, | 1108 | activeTurnStartedAt: null, |
| 890 | turnDiffSummaryByAssistantMessageId: new Map(), | 1109 | turnDiffSummaries: [], |
| 891 | revertTurnCountByUserMessageId: new Map(), | 1110 | supportsConversationRollback: false, |
| 892 | }); | 1111 | }); |
| 893 | | 1112 | |
| 894 | expect(rows).toEqual([ | 1113 | expect(rows).toEqual([ |
| ... | @@ -950,8 +1169,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -950,8 +1169,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 950 | expandedTurnIds: new Set(["turn-1" as never]), | 1169 | expandedTurnIds: new Set(["turn-1" as never]), |
| 951 | isWorking: false, | 1170 | isWorking: false, |
| 952 | activeTurnStartedAt: null, | 1171 | activeTurnStartedAt: null, |
| 953 | turnDiffSummaryByAssistantMessageId: new Map(), | 1172 | turnDiffSummaries: [], |
| 954 | revertTurnCountByUserMessageId: new Map(), | 1173 | supportsConversationRollback: false, |
| 955 | }); | 1174 | }); |
| 956 | | 1175 | |
| 957 | const assistantRows = rows.filter( | 1176 | const assistantRows = rows.filter( |
| ... | @@ -1004,8 +1223,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1004,8 +1223,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1004 | }, | 1223 | }, |
| 1005 | isWorking: false, | 1224 | isWorking: false, |
| 1006 | activeTurnStartedAt: null, | 1225 | activeTurnStartedAt: null, |
| 1007 | turnDiffSummaryByAssistantMessageId: new Map(), | 1226 | turnDiffSummaries: [], |
| 1008 | revertTurnCountByUserMessageId: new Map(), | 1227 | supportsConversationRollback: false, |
| 1009 | }); | 1228 | }); |
| 1010 | | 1229 | |
| 1011 | const assistantRows = rows.filter( | 1230 | const assistantRows = rows.filter( |
| ... | @@ -1061,10 +1280,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1061,10 +1280,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1061 | ], | 1280 | ], |
| 1062 | isWorking: false, | 1281 | isWorking: false, |
| 1063 | activeTurnStartedAt: null, | 1282 | activeTurnStartedAt: null, |
| 1064 | turnDiffSummaryByAssistantMessageId: new Map([ | 1283 | turnDiffSummaries: [assistantTurnDiffSummary], |
| 1065 | ["assistant-1" as never, assistantTurnDiffSummary], | 1284 | supportsConversationRollback: true, |
| 1066 | ]), | | |
| 1067 | revertTurnCountByUserMessageId: new Map([["user-1" as never, 1]]), | | |
| 1068 | }); | 1285 | }); |
| 1069 | | 1286 | |
| 1070 | const userRow = rows.find( | 1287 | const userRow = rows.find( |
| ... | @@ -1142,8 +1359,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1142,8 +1359,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1142 | timelineEntries, | 1359 | timelineEntries, |
| 1143 | isWorking: false, | 1360 | isWorking: false, |
| 1144 | activeTurnStartedAt: null, | 1361 | activeTurnStartedAt: null, |
| 1145 | turnDiffSummaryByAssistantMessageId: new Map(), | 1362 | turnDiffSummaries: [], |
| 1146 | revertTurnCountByUserMessageId: new Map(), | 1363 | supportsConversationRollback: false, |
| 1147 | }); | 1364 | }); |
| 1148 | | 1365 | |
| 1149 | const foldRow = collapsedRows.find( | 1366 | const foldRow = collapsedRows.find( |
| ... | @@ -1165,8 +1382,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1165,8 +1382,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1165 | expandedTurnIds: new Set(["turn-1" as never]), | 1382 | expandedTurnIds: new Set(["turn-1" as never]), |
| 1166 | isWorking: false, | 1383 | isWorking: false, |
| 1167 | activeTurnStartedAt: null, | 1384 | activeTurnStartedAt: null, |
| 1168 | turnDiffSummaryByAssistantMessageId: new Map(), | 1385 | turnDiffSummaries: [], |
| 1169 | revertTurnCountByUserMessageId: new Map(), | 1386 | supportsConversationRollback: false, |
| 1170 | }); | 1387 | }); |
| 1171 | | 1388 | |
| 1172 | expect(expandedRows.map((row) => row.id)).toEqual([ | 1389 | expect(expandedRows.map((row) => row.id)).toEqual([ |
| ... | @@ -1235,8 +1452,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1235,8 +1452,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1235 | }, | 1452 | }, |
| 1236 | isWorking: false, | 1453 | isWorking: false, |
| 1237 | activeTurnStartedAt: null, | 1454 | activeTurnStartedAt: null, |
| 1238 | turnDiffSummaryByAssistantMessageId: new Map(), | 1455 | turnDiffSummaries: [], |
| 1239 | revertTurnCountByUserMessageId: new Map(), | 1456 | supportsConversationRollback: false, |
| 1240 | }; | 1457 | }; |
| 1241 | const rows = deriveMessagesTimelineRows({ ...input, timelineEntries }); | 1458 | const rows = deriveMessagesTimelineRows({ ...input, timelineEntries }); |
| 1242 | | 1459 | |
| ... | @@ -1318,8 +1535,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1318,8 +1535,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1318 | timelineEntries, | 1535 | timelineEntries, |
| 1319 | isWorking: false, | 1536 | isWorking: false, |
| 1320 | activeTurnStartedAt: null, | 1537 | activeTurnStartedAt: null, |
| 1321 | turnDiffSummaryByAssistantMessageId: new Map(), | 1538 | turnDiffSummaries: [], |
| 1322 | revertTurnCountByUserMessageId: new Map(), | 1539 | supportsConversationRollback: false, |
| 1323 | }); | 1540 | }); |
| 1324 | | 1541 | |
| 1325 | expect(rows.map((row) => row.id)).toEqual(["turn-fold:turn-1", "assistant-final-entry"]); | 1542 | expect(rows.map((row) => row.id)).toEqual(["turn-fold:turn-1", "assistant-final-entry"]); |
| ... | @@ -1421,8 +1638,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1421,8 +1638,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1421 | }, | 1638 | }, |
| 1422 | isWorking: true, | 1639 | isWorking: true, |
| 1423 | activeTurnStartedAt: "2026-01-01T00:00:14Z", | 1640 | activeTurnStartedAt: "2026-01-01T00:00:14Z", |
| 1424 | turnDiffSummaryByAssistantMessageId: new Map(), | 1641 | turnDiffSummaries: [], |
| 1425 | revertTurnCountByUserMessageId: new Map(), | 1642 | supportsConversationRollback: false, |
| 1426 | }); | 1643 | }); |
| 1427 | | 1644 | |
| 1428 | const foldRow = rows.find( | 1645 | const foldRow = rows.find( |
| ... | @@ -1458,8 +1675,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1458,8 +1675,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1458 | }, | 1675 | }, |
| 1459 | isWorking: false, | 1676 | isWorking: false, |
| 1460 | activeTurnStartedAt: null, | 1677 | activeTurnStartedAt: null, |
| 1461 | turnDiffSummaryByAssistantMessageId: new Map(), | 1678 | turnDiffSummaries: [], |
| 1462 | revertTurnCountByUserMessageId: new Map(), | 1679 | supportsConversationRollback: false, |
| 1463 | }); | 1680 | }); |
| 1464 | | 1681 | |
| 1465 | expect(rows).toEqual([ | 1682 | expect(rows).toEqual([ |
| ... | @@ -1526,8 +1743,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1526,8 +1743,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1526 | }, | 1743 | }, |
| 1527 | isWorking: true, | 1744 | isWorking: true, |
| 1528 | activeTurnStartedAt: "2026-01-01T00:01:00Z", | 1745 | activeTurnStartedAt: "2026-01-01T00:01:00Z", |
| 1529 | turnDiffSummaryByAssistantMessageId: new Map(), | 1746 | turnDiffSummaries: [], |
| 1530 | revertTurnCountByUserMessageId: new Map(), | 1747 | supportsConversationRollback: false, |
| 1531 | }); | 1748 | }); |
| 1532 | | 1749 | |
| 1533 | expect(rows.map((row) => row.id)).toEqual([ | 1750 | expect(rows.map((row) => row.id)).toEqual([ |
| ... | @@ -1580,8 +1797,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1580,8 +1797,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1580 | }, | 1797 | }, |
| 1581 | isWorking: true, | 1798 | isWorking: true, |
| 1582 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 1799 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 1583 | turnDiffSummaryByAssistantMessageId: new Map(), | 1800 | turnDiffSummaries: [], |
| 1584 | revertTurnCountByUserMessageId: new Map(), | 1801 | supportsConversationRollback: false, |
| 1585 | }); | 1802 | }); |
| 1586 | | 1803 | |
| 1587 | expect(rows.some((row) => row.kind === "turn-fold")).toBe(false); | 1804 | expect(rows.some((row) => row.kind === "turn-fold")).toBe(false); |
| ... | @@ -1674,8 +1891,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1674,8 +1891,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1674 | }, | 1891 | }, |
| 1675 | isWorking: true, | 1892 | isWorking: true, |
| 1676 | activeTurnStartedAt: "2026-01-01T00:01:00Z", | 1893 | activeTurnStartedAt: "2026-01-01T00:01:00Z", |
| 1677 | turnDiffSummaryByAssistantMessageId: new Map(), | 1894 | turnDiffSummaries: [], |
| 1678 | revertTurnCountByUserMessageId: new Map(), | 1895 | supportsConversationRollback: false, |
| 1679 | }); | 1896 | }); |
| 1680 | | 1897 | |
| 1681 | expect(rows.some((row) => row.kind === "turn-fold")).toBe(false); | 1898 | expect(rows.some((row) => row.kind === "turn-fold")).toBe(false); |
| ... | @@ -1754,8 +1971,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1754,8 +1971,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1754 | }, | 1971 | }, |
| 1755 | isWorking: true, | 1972 | isWorking: true, |
| 1756 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 1973 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 1757 | turnDiffSummaryByAssistantMessageId: new Map(), | 1974 | turnDiffSummaries: [], |
| 1758 | revertTurnCountByUserMessageId: new Map(), | 1975 | supportsConversationRollback: false, |
| 1759 | }); | 1976 | }); |
| 1760 | | 1977 | |
| 1761 | expect(rows.map((row) => row.kind)).toEqual(["working", "work-live"]); | 1978 | expect(rows.map((row) => row.kind)).toEqual(["working", "work-live"]); |
| ... | @@ -1827,8 +2044,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1827,8 +2044,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1827 | }, | 2044 | }, |
| 1828 | isWorking: true, | 2045 | isWorking: true, |
| 1829 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 2046 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 1830 | turnDiffSummaryByAssistantMessageId: new Map(), | 2047 | turnDiffSummaries: [], |
| 1831 | revertTurnCountByUserMessageId: new Map(), | 2048 | supportsConversationRollback: false, |
| 1832 | }); | 2049 | }); |
| 1833 | | 2050 | |
| 1834 | expect(rows.map((row) => row.kind)).toEqual(["working", "work", "message", "work-live"]); | 2051 | expect(rows.map((row) => row.kind)).toEqual(["working", "work", "message", "work-live"]); |
| ... | @@ -1879,8 +2096,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1879,8 +2096,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1879 | expandedTurnIds: new Set([turnId]), | 2096 | expandedTurnIds: new Set([turnId]), |
| 1880 | isWorking: false, | 2097 | isWorking: false, |
| 1881 | activeTurnStartedAt: null, | 2098 | activeTurnStartedAt: null, |
| 1882 | turnDiffSummaryByAssistantMessageId: new Map(), | 2099 | turnDiffSummaries: [], |
| 1883 | revertTurnCountByUserMessageId: new Map(), | 2100 | supportsConversationRollback: false, |
| 1884 | }); | 2101 | }); |
| 1885 | | 2102 | |
| 1886 | expect(rows.find((row) => row.kind === "work")).toMatchObject({ | 2103 | expect(rows.find((row) => row.kind === "work")).toMatchObject({ |
| ... | @@ -1947,8 +2164,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1947,8 +2164,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1947 | }, | 2164 | }, |
| 1948 | isWorking: true, | 2165 | isWorking: true, |
| 1949 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 2166 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 1950 | turnDiffSummaryByAssistantMessageId: new Map(), | 2167 | turnDiffSummaries: [], |
| 1951 | revertTurnCountByUserMessageId: new Map(), | 2168 | supportsConversationRollback: false, |
| 1952 | }); | 2169 | }); |
| 1953 | | 2170 | |
| 1954 | expect(rows.map((row) => row.kind)).toEqual(["working", "work-live", "message", "work-live"]); | 2171 | expect(rows.map((row) => row.kind)).toEqual(["working", "work-live", "message", "work-live"]); |
| ... | @@ -1994,8 +2211,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -1994,8 +2211,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 1994 | latestTurn: null, | 2211 | latestTurn: null, |
| 1995 | isWorking: true, | 2212 | isWorking: true, |
| 1996 | activeTurnStartedAt: "2026-01-01T00:01:00Z", | 2213 | activeTurnStartedAt: "2026-01-01T00:01:00Z", |
| 1997 | turnDiffSummaryByAssistantMessageId: new Map(), | 2214 | turnDiffSummaries: [], |
| 1998 | revertTurnCountByUserMessageId: new Map(), | 2215 | supportsConversationRollback: false, |
| 1999 | }); | 2216 | }); |
| 2000 | | 2217 | |
| 2001 | expect(rows.some((row) => row.kind === "work-live")).toBe(false); | 2218 | expect(rows.some((row) => row.kind === "work-live")).toBe(false); |
| ... | @@ -2055,8 +2272,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2055,8 +2272,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2055 | }, | 2272 | }, |
| 2056 | isWorking: true, | 2273 | isWorking: true, |
| 2057 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 2274 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 2058 | turnDiffSummaryByAssistantMessageId: new Map(), | 2275 | turnDiffSummaries: [], |
| 2059 | revertTurnCountByUserMessageId: new Map(), | 2276 | supportsConversationRollback: false, |
| 2060 | }); | 2277 | }); |
| 2061 | | 2278 | |
| 2062 | expect(rows.filter((row) => row.kind === "work-live").map((row) => row.entry.id)).toEqual([ | 2279 | expect(rows.filter((row) => row.kind === "work-live").map((row) => row.entry.id)).toEqual([ |
| ... | @@ -2100,8 +2317,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2100,8 +2317,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2100 | }, | 2317 | }, |
| 2101 | isWorking: true, | 2318 | isWorking: true, |
| 2102 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 2319 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 2103 | turnDiffSummaryByAssistantMessageId: new Map(), | 2320 | turnDiffSummaries: [], |
| 2104 | revertTurnCountByUserMessageId: new Map(), | 2321 | supportsConversationRollback: false, |
| 2105 | }); | 2322 | }); |
| 2106 | | 2323 | |
| 2107 | const workLiveRow = rows.find((row) => row.kind === "work-live"); | 2324 | const workLiveRow = rows.find((row) => row.kind === "work-live"); |
| ... | @@ -2148,8 +2365,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2148,8 +2365,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2148 | }, | 2365 | }, |
| 2149 | isWorking: true, | 2366 | isWorking: true, |
| 2150 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 2367 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 2151 | turnDiffSummaryByAssistantMessageId: new Map(), | 2368 | turnDiffSummaries: [], |
| 2152 | revertTurnCountByUserMessageId: new Map(), | 2369 | supportsConversationRollback: false, |
| 2153 | }); | 2370 | }); |
| 2154 | | 2371 | |
| 2155 | const initialRows = deriveRows(null); | 2372 | const initialRows = deriveRows(null); |
| ... | @@ -2226,8 +2443,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2226,8 +2443,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2226 | runningTurnId: "turn-2" as never, | 2443 | runningTurnId: "turn-2" as never, |
| 2227 | isWorking: true, | 2444 | isWorking: true, |
| 2228 | activeTurnStartedAt: "2026-01-01T00:01:00Z", | 2445 | activeTurnStartedAt: "2026-01-01T00:01:00Z", |
| 2229 | turnDiffSummaryByAssistantMessageId: new Map(), | 2446 | turnDiffSummaries: [], |
| 2230 | revertTurnCountByUserMessageId: new Map(), | 2447 | supportsConversationRollback: false, |
| 2231 | }); | 2448 | }); |
| 2232 | | 2449 | |
| 2233 | expect(rows.filter((row) => row.kind === "turn-fold").map((row) => row.turnId)).toEqual([ | 2450 | expect(rows.filter((row) => row.kind === "turn-fold").map((row) => row.turnId)).toEqual([ |
| ... | @@ -2271,8 +2488,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2271,8 +2488,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2271 | expandedTurnIds: new Set(["turn-1" as never]), | 2488 | expandedTurnIds: new Set(["turn-1" as never]), |
| 2272 | isWorking: false, | 2489 | isWorking: false, |
| 2273 | activeTurnStartedAt: null, | 2490 | activeTurnStartedAt: null, |
| 2274 | turnDiffSummaryByAssistantMessageId: new Map(), | 2491 | turnDiffSummaries: [], |
| 2275 | revertTurnCountByUserMessageId: new Map(), | 2492 | supportsConversationRollback: false, |
| 2276 | }); | 2493 | }); |
| 2277 | | 2494 | |
| 2278 | const assistantRows = rows.filter( | 2495 | const assistantRows = rows.filter( |
| ... | @@ -2309,8 +2526,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2309,8 +2526,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2309 | }, | 2526 | }, |
| 2310 | isWorking: true, | 2527 | isWorking: true, |
| 2311 | activeTurnStartedAt: "2026-01-01T00:00:00Z", | 2528 | activeTurnStartedAt: "2026-01-01T00:00:00Z", |
| 2312 | turnDiffSummaryByAssistantMessageId: new Map(), | 2529 | turnDiffSummaries: [], |
| 2313 | revertTurnCountByUserMessageId: new Map(), | 2530 | supportsConversationRollback: false, |
| 2314 | }); | 2531 | }); |
| 2315 | | 2532 | |
| 2316 | const assistantRow = rows.find( | 2533 | const assistantRow = rows.find( |
| ... | @@ -2373,8 +2590,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2373,8 +2590,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2373 | timelineEntries, | 2590 | timelineEntries, |
| 2374 | isWorking: false, | 2591 | isWorking: false, |
| 2375 | activeTurnStartedAt: null, | 2592 | activeTurnStartedAt: null, |
| 2376 | turnDiffSummaryByAssistantMessageId: new Map(), | 2593 | turnDiffSummaries: [], |
| 2377 | revertTurnCountByUserMessageId: new Map(), | 2594 | supportsConversationRollback: false, |
| 2378 | }; | 2595 | }; |
| 2379 | const collapsedRows = deriveMessagesTimelineRows(baseInput); | 2596 | const collapsedRows = deriveMessagesTimelineRows(baseInput); |
| 2380 | const expandedRows = deriveMessagesTimelineRows({ | 2597 | const expandedRows = deriveMessagesTimelineRows({ |
| ... | @@ -2467,8 +2684,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2467,8 +2684,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2467 | timelineEntries, | 2684 | timelineEntries, |
| 2468 | isWorking: false, | 2685 | isWorking: false, |
| 2469 | activeTurnStartedAt: null, | 2686 | activeTurnStartedAt: null, |
| 2470 | turnDiffSummaryByAssistantMessageId: new Map(), | 2687 | turnDiffSummaries: [], |
| 2471 | revertTurnCountByUserMessageId: new Map(), | 2688 | supportsConversationRollback: false, |
| 2472 | }); | 2689 | }); |
| 2473 | | 2690 | |
| 2474 | expect(row).toMatchObject({ | 2691 | expect(row).toMatchObject({ |
| ... | @@ -2509,8 +2726,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2509,8 +2726,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2509 | expandedTurnIds: new Set([turnId]), | 2726 | expandedTurnIds: new Set([turnId]), |
| 2510 | runningTurnId: isWorking ? turnId : null, | 2727 | runningTurnId: isWorking ? turnId : null, |
| 2511 | activeTurnStartedAt: isWorking ? createdAt : null, | 2728 | activeTurnStartedAt: isWorking ? createdAt : null, |
| 2512 | turnDiffSummaryByAssistantMessageId: new Map(), | 2729 | turnDiffSummaries: [], |
| 2513 | revertTurnCountByUserMessageId: new Map(), | 2730 | supportsConversationRollback: false, |
| 2514 | }; | 2731 | }; |
| 2515 | const expandedRows = deriveMessagesTimelineRows({ | 2732 | const expandedRows = deriveMessagesTimelineRows({ |
| 2516 | ...input, | 2733 | ...input, |
| ... | @@ -2549,8 +2766,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2549,8 +2766,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2549 | timelineEntries, | 2766 | timelineEntries, |
| 2550 | isWorking: false, | 2767 | isWorking: false, |
| 2551 | activeTurnStartedAt: null, | 2768 | activeTurnStartedAt: null, |
| 2552 | turnDiffSummaryByAssistantMessageId: new Map(), | 2769 | turnDiffSummaries: [], |
| 2553 | revertTurnCountByUserMessageId: new Map(), | 2770 | supportsConversationRollback: false, |
| 2554 | }); | 2771 | }); |
| 2555 | | 2772 | |
| 2556 | expect(rows.find((row) => row.kind === "work-toggle")).toMatchObject({ | 2773 | expect(rows.find((row) => row.kind === "work-toggle")).toMatchObject({ |
| ... | @@ -2596,8 +2813,8 @@ describe("deriveMessagesTimelineRows", () => { | ... | @@ -2596,8 +2813,8 @@ describe("deriveMessagesTimelineRows", () => { |
| 2596 | timelineEntries, | 2813 | timelineEntries, |
| 2597 | isWorking: false, | 2814 | isWorking: false, |
| 2598 | activeTurnStartedAt: null, | 2815 | activeTurnStartedAt: null, |
| 2599 | turnDiffSummaryByAssistantMessageId: new Map(), | 2816 | turnDiffSummaries: [], |
| 2600 | revertTurnCountByUserMessageId: new Map(), | 2817 | supportsConversationRollback: false, |
| 2601 | }); | 2818 | }); |
| 2602 | | 2819 | |
| 2603 | expect(rows.find((row) => row.kind === "work-toggle")).toMatchObject({ | 2820 | expect(rows.find((row) => row.kind === "work-toggle")).toMatchObject({ |
| ... | @@ -2653,8 +2870,8 @@ describe("computeStableMessagesTimelineRows", () => { | ... | @@ -2653,8 +2870,8 @@ describe("computeStableMessagesTimelineRows", () => { |
| 2653 | runningTurnId: turnId, | 2870 | runningTurnId: turnId, |
| 2654 | isWorking: true, | 2871 | isWorking: true, |
| 2655 | activeTurnStartedAt: startedAt, | 2872 | activeTurnStartedAt: startedAt, |
| 2656 | turnDiffSummaryByAssistantMessageId: new Map(), | 2873 | turnDiffSummaries: [], |
| 2657 | revertTurnCountByUserMessageId: new Map(), | 2874 | supportsConversationRollback: false, |
| 2658 | }; | 2875 | }; |
| 2659 | const assistantEntry = { | 2876 | const assistantEntry = { |
| 2660 | id: "assistant-entry", | 2877 | id: "assistant-entry", |
| ... | @@ -2731,8 +2948,8 @@ describe("computeStableMessagesTimelineRows", () => { | ... | @@ -2731,8 +2948,8 @@ describe("computeStableMessagesTimelineRows", () => { |
| 2731 | ], | 2948 | ], |
| 2732 | isWorking: false, | 2949 | isWorking: false, |
| 2733 | activeTurnStartedAt: null, | 2950 | activeTurnStartedAt: null, |
| 2734 | turnDiffSummaryByAssistantMessageId: new Map(), | 2951 | turnDiffSummaries: [], |
| 2735 | revertTurnCountByUserMessageId: new Map(), | 2952 | supportsConversationRollback: false, |
| 2736 | }); | 2953 | }); |
| 2737 | | 2954 | |
| 2738 | const initial = computeStableMessagesTimelineRows(rows, { | 2955 | const initial = computeStableMessagesTimelineRows(rows, { |
| ... | @@ -2780,8 +2997,8 @@ describe("computeStableMessagesTimelineRows", () => { | ... | @@ -2780,8 +2997,8 @@ describe("computeStableMessagesTimelineRows", () => { |
| 2780 | ], | 2997 | ], |
| 2781 | isWorking: false, | 2998 | isWorking: false, |
| 2782 | activeTurnStartedAt: null, | 2999 | activeTurnStartedAt: null, |
| 2783 | turnDiffSummaryByAssistantMessageId: new Map(), | 3000 | turnDiffSummaries: [], |
| 2784 | revertTurnCountByUserMessageId: new Map(), | 3001 | supportsConversationRollback: false, |
| 2785 | }); | 3002 | }); |
| 2786 | | 3003 | |
| 2787 | const firstRows = createRows(); | 3004 | const firstRows = createRows(); |
| ... | @@ -2836,8 +3053,8 @@ describe("computeStableMessagesTimelineRows", () => { | ... | @@ -2836,8 +3053,8 @@ describe("computeStableMessagesTimelineRows", () => { |
| 2836 | ], | 3053 | ], |
| 2837 | isWorking: false, | 3054 | isWorking: false, |
| 2838 | activeTurnStartedAt: null, | 3055 | activeTurnStartedAt: null, |
| 2839 | turnDiffSummaryByAssistantMessageId: new Map(), | 3056 | turnDiffSummaries: [], |
| 2840 | revertTurnCountByUserMessageId: new Map(), | 3057 | supportsConversationRollback: false, |
| 2841 | }); | 3058 | }); |
| 2842 | | 3059 | |
| 2843 | const initial = computeStableMessagesTimelineRows(firstRows, { | 3060 | const initial = computeStableMessagesTimelineRows(firstRows, { |