Skip to content
Source
Sign inSign up

orchestrationV2Projection.test.ts

819 lines26.2 KB

1import { describe, expect, it } from "vite-plus/test";
2import {
3 type ThreadPullRequestLink,
4 type OrchestrationV2DomainEvent,
5 type OrchestrationV2Run,
6 type OrchestrationV2ThreadProjection,
7 type OrchestrationV2TurnItem,
8 MessageId,
9 ProjectId,
10 ProviderInstanceId,
11 NodeId,
12 ProviderThreadId,
13 ProviderTurnId,
14 RunId,
15 ThreadId,
16 TurnItemId,
17} from "@t3tools/contracts";
18import * as DateTime from "effect/DateTime";
19​
20import { applyOrchestrationV2ProjectionEvent } from "./orchestrationV2Projection.ts";
21​
22const now = DateTime.makeUnsafe("2026-06-20T00:00:00.000Z");
23const threadId = ThreadId.make("thread-reducer");
24const runId = RunId.make("run-reducer");
25const run = {
26 id: runId,
27 threadId,
28 ordinal: 1,
29 providerInstanceId: ProviderInstanceId.make("codex"),
30 modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },
31 providerThreadId: null,
32 userMessageId: MessageId.make("message-reducer"),
33 rootNodeId: null,
34 activeAttemptId: null,
35 status: "completed",
36 requestedAt: now,
37 startedAt: now,
38 completedAt: now,
39 checkpointId: null,
40 contextHandoffId: null,
41} satisfies OrchestrationV2Run;
42​
43function commandItem(id: string, output = "done", ordinal = 1): OrchestrationV2TurnItem {
44 return {
45 id: TurnItemId.make(id),
46 threadId,
47 runId,
48 nodeId: null,
49 providerThreadId: null,
50 providerTurnId: null,
51 nativeItemRef: null,
52 parentItemId: null,
53 ordinal,
54 status: "completed",
55 title: null,
56 startedAt: now,
57 completedAt: now,
58 updatedAt: now,
59 type: "command_execution",
60 input: "pwd",
61 output,
62 exitCode: 0,
63 };
65const emptyProjection = {
66 thread: {
67 id: threadId,
68 projectId: ProjectId.make("project-reducer"),
69 title: "Reducer",
70 providerInstanceId: ProviderInstanceId.make("codex"),
71 modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" },
72 runtimeMode: "full-access",
73 interactionMode: "default",
74 branch: null,
75 worktreePath: null,
76 activeProviderThreadId: null,
77 lineage: { rootThreadId: threadId, parentThreadId: null, relationshipToParent: null },
78 forkedFrom: null,
79 createdBy: "user",
80 creationSource: "web",
81 createdAt: now,
82 updatedAt: now,
83 archivedAt: null,
84 settledOverride: null,
85 settledAt: null,
86 lastVisitedAt: null,
87 deletedAt: null,
88 },
89 runs: [],
90 attempts: [],
91 nodes: [],
92 subagents: [],
93 providerSessions: [],
94 providerThreads: [],
95 providerTurns: [],
96 runtimeRequests: [],
97 messages: [],
98 plans: [],
99 turnItems: [],
100 checkpointScopes: [],
101 checkpoints: [],
102 contextHandoffs: [],
103 contextTransfers: [],
104 visibleTurnItems: [],
105 updatedAt: now,
106} as OrchestrationV2ThreadProjection;
107​
108describe("applyOrchestrationV2ProjectionEvent", () => {
109 it("applies a compact PR sync without replacing other links or thread metadata", () => {
110 const link = (number: number): ThreadPullRequestLink => ({
111 host: "github.com",
112 repository: "pingdotgg/t3code",
113 number,
114 url: `https://github.com/pingdotgg/t3code/pull/${number}`,
115 source: "manual",
116 linkedAt: DateTime.formatIso(now),
117 snapshot: null,
118 stack: null,
119 });
120 const links = [link(1), link(2)];
121 const projection = {
122 ...emptyProjection,
123 thread: { ...emptyProjection.thread, pullRequests: links },
124 };
125 const updated = { ...links[1]!, source: "agent" as const };
126 const next = applyOrchestrationV2ProjectionEvent(projection, {
127 id: "compact-sync" as OrchestrationV2DomainEvent["id"],
128 type: "thread.pull-request-link-updated",
129 threadId,
130 occurredAt: now,
131 payload: updated,
132 });
133 expect(next?.thread).toEqual({ ...projection.thread, pullRequests: [links[0], updated] });
134 expect(next?.thread.pullRequests?.[0]).toBe(links[0]);
135 });
136​
137 it("keeps live token usage when the terminal provider turn omits it", () => {
138 const providerTurnId = ProviderTurnId.make("provider-turn-reducer");
139 const running = {
140 id: providerTurnId,
141 providerThreadId: ProviderThreadId.make("provider-thread-reducer"),
142 nodeId: NodeId.make("provider-node-reducer"),
143 runAttemptId: null,
144 nativeTurnRef: null,
145 ordinal: 1,
146 status: "running" as const,
147 startedAt: now,
148 completedAt: null,
149 tokenUsage: {
150 usedTokens: 50_000,
151 maxTokens: 200_000,
152 updatedAt: "2026-08-29T00:00:00.000Z",
153 },
154 };
155 const projection = { ...emptyProjection, providerTurns: [running] };
156 const event = {
157 id: "event-provider-turn-terminal",
158 type: "provider-turn.updated",
159 threadId,
160 driver: "codex",
161 occurredAt: now,
162 payload: {
163 ...running,
164 status: "completed",
165 completedAt: now,
166 tokenUsage: undefined,
167 },
168 } as OrchestrationV2DomainEvent;
169​
170 const next = applyOrchestrationV2ProjectionEvent(projection, event);
171​
172 expect(next?.providerTurns[0]?.status).toBe("completed");
173 expect(next?.providerTurns[0]?.tokenUsage).toEqual(running.tokenUsage);
174 });
175​
176 it("applies thread lifecycle payloads instead of leaving stale metadata", () => {
177 const archivedAt = DateTime.makeUnsafe("2026-06-20T01:00:00.000Z");
178 const event = {
179 id: "event-archive",
180 type: "thread.archived",
181 threadId,
182 occurredAt: archivedAt,
183 payload: { ...emptyProjection.thread, archivedAt, updatedAt: archivedAt },
184 } as OrchestrationV2DomainEvent;
185​
186 const next = applyOrchestrationV2ProjectionEvent(emptyProjection, event);
187 expect(next?.thread.archivedAt).toEqual(archivedAt);
188 expect(next?.updatedAt).toEqual(archivedAt);
189 });
190​
191 it("preserves visible row identity when run updates do not change membership", () => {
192 const item = commandItem("item-stable");
193 const visibleTurnItems = [
194 {
195 position: 0,
196 visibility: "local" as const,
197 sourceThreadId: threadId,
198 sourceItemId: item.id,
199 item,
200 },
201 ];
202 const projection = {
203 ...emptyProjection,
204 runs: [run],
205 turnItems: [item],
206 visibleTurnItems,
207 };
208 const event = {
209 id: "event-run-update",
210 type: "run.updated",
211 threadId,
212 runId,
213 occurredAt: now,
214 payload: { ...run, status: "completed" },
215 } as OrchestrationV2DomainEvent;
216​
217 const next = applyOrchestrationV2ProjectionEvent(projection, event);
218 expect(next?.visibleTurnItems).toBe(visibleTurnItems);
219 expect(next?.visibleTurnItems[0]).toBe(visibleTurnItems[0]);
220 });
221​
222 it("replaces only the updated visible item and retains its deferred work cursor", () => {
223 const deferredWork = { cursor: "remaining-work", itemCount: 100, lastOrdinal: 101 };
224 const first = { ...commandItem("item-first", "first"), deferredWork };
225 const second = commandItem("item-second", "second");
226 const firstRow = {
227 position: 0,
228 visibility: "local" as const,
229 sourceThreadId: threadId,
230 sourceItemId: first.id,
231 item: first,
232 };
233 const secondRow = {
234 position: 1,
235 visibility: "local" as const,
236 sourceThreadId: threadId,
237 sourceItemId: second.id,
238 item: second,
239 };
240 const updated = commandItem("item-first", "streamed output");
241 const projection = {
242 ...emptyProjection,
243 runs: [run],
244 turnItems: [first, second],
245 visibleTurnItems: [firstRow, secondRow],
246 };
247 const event = {
248 id: "event-item-update",
249 type: "turn-item.updated",
250 threadId,
251 runId,
252 occurredAt: now,
253 payload: updated,
254 } as OrchestrationV2DomainEvent;
255​
256 const next = applyOrchestrationV2ProjectionEvent(projection, event);
257 expect(next?.visibleTurnItems[0]?.item.deferredWork).toEqual(deferredWork);
258 expect(next?.visibleTurnItems).not.toBe(projection.visibleTurnItems);
259 expect(next?.visibleTurnItems[0]).not.toBe(firstRow);
260 expect(next?.visibleTurnItems[0]?.item).toEqual({ ...updated, deferredWork });
261 expect(next?.visibleTurnItems[1]).toBe(secondRow);
262 });
263​
264 it("inserts live turn items by authoritative ordinal", () => {
265 const queuedFuture = commandItem("item-queued-future", "queued", 300);
266 const activeAssistant = commandItem("item-active-assistant", "done", 201);
267 const queuedRow = {
268 position: 0,
269 visibility: "local" as const,
270 sourceThreadId: threadId,
271 sourceItemId: queuedFuture.id,
272 item: queuedFuture,
273 };
274 const projection = {
275 ...emptyProjection,
276 runs: [run],
277 turnItems: [queuedFuture],
278 visibleTurnItems: [queuedRow],
279 };
280 const event = {
281 id: "event-active-assistant",
282 type: "turn-item.updated",
283 threadId,
284 runId,
285 occurredAt: now,
286 payload: activeAssistant,
287 } as OrchestrationV2DomainEvent;
288​
289 const next = applyOrchestrationV2ProjectionEvent(projection, event);
290 expect(next?.visibleTurnItems.map((row) => row.item.id)).toEqual([
291 activeAssistant.id,
292 queuedFuture.id,
293 ]);
294 expect(next?.visibleTurnItems.map((row) => row.position)).toEqual([0, 1]);
295 });
296​
297 it("moves an updated item to its new ordinal and renumbers the rows it passes", () => {
298 const inherited = {
299 ...commandItem("item-inherited", "parent", 0),
300 threadId: ThreadId.make("parent"),
301 };
302 const items = [
303 commandItem("item-a", "a", 1),
304 commandItem("item-b", "b", 2),
305 commandItem("item-c", "c", 2),
306 ];
307 const rows = [
308 {
309 position: 0,
310 visibility: "inherited" as const,
311 sourceThreadId: inherited.threadId,
312 sourceItemId: inherited.id,
313 item: inherited,
314 },
315 ...items.map((item, index) => ({
316 position: index + 1,
317 visibility: "local" as const,
318 sourceThreadId: threadId,
319 sourceItemId: item.id,
320 item,
321 })),
322 ];
323 const moved = commandItem("item-a", "moved", 2);
324 const next = applyOrchestrationV2ProjectionEvent(
325 { ...emptyProjection, runs: [run], turnItems: items, visibleTurnItems: rows },
326 {
327 id: "event-move",
328 type: "turn-item.updated",
329 threadId,
330 runId,
331 occurredAt: now,
332 payload: moved,
333 } as OrchestrationV2DomainEvent,
334 );
335​
336 // Equal ordinals order by id, so item-a stays ahead of item-b.
337 expect(next?.visibleTurnItems.map((row) => [row.item.id, row.position])).toEqual([
338 ["item-inherited", 0],
339 ["item-a", 1],
340 ["item-b", 2],
341 ["item-c", 3],
342 ]);
343 expect(next?.visibleTurnItems[0]).toBe(rows[0]);
344 expect(next?.visibleTurnItems[1]?.item).toBe(moved);
345 expect(next?.turnItems.map((item) => item.id)).toEqual(["item-a", "item-b", "item-c"]);
346​
347 const later = commandItem("item-a", "later", 3);
348 const last = applyOrchestrationV2ProjectionEvent(next, {
349 id: "event-move-later",
350 type: "turn-item.updated",
351 threadId,
352 runId,
353 occurredAt: now,
354 payload: later,
355 } as OrchestrationV2DomainEvent);
356 expect(last?.visibleTurnItems.map((row) => [row.item.id, row.position])).toEqual([
357 ["item-inherited", 0],
358 ["item-b", 1],
359 ["item-c", 2],
360 ["item-a", 3],
361 ]);
362 expect(last?.turnItems.at(0)).toBe(later);
363 });
364​
365 describe("partial timeline", () => {
366 const queuedRunId = RunId.make("run-queued");
367 const steerRunId = RunId.make("run-steer");
368 const queuedRun = { ...run, id: queuedRunId, ordinal: 2, status: "queued" } as const;
369 const steerRun = { ...run, id: steerRunId, ordinal: 3, status: "completed" } as const;
370 const runItem = (id: string, itemRunId: RunId, ordinal: number) => ({
371 ...commandItem(id, id, ordinal),
372 runId: itemRunId,
373 });
374 const windowOf = (
375 runs: ReadonlyArray<OrchestrationV2Run>,
376 items: ReadonlyArray<OrchestrationV2TurnItem>,
377 ): OrchestrationV2ThreadProjection => ({
378 ...emptyProjection,
379 runs,
380 turnItems: items,
381 visibleTurnItems: items.map((item, position) => ({
382 position,
383 visibility: "local" as const,
384 sourceThreadId: threadId,
385 sourceItemId: item.id,
386 item,
387 })),
388 });
389 const itemEvent = (payload: OrchestrationV2TurnItem) =>
390 ({
391 id: `event-${payload.id}`,
392 type: "turn-item.updated",
393 threadId,
394 occurredAt: now,
395 payload,
396 }) as OrchestrationV2DomainEvent;
397 const runEvent = (payload: OrchestrationV2Run) =>
398 ({
399 id: `event-${payload.id}-${payload.status}`,
400 type: "run.updated",
401 threadId,
402 occurredAt: now,
403 payload,
404 }) as OrchestrationV2DomainEvent;
405​
406 it("shows a queued run that starts after a newer steer raised the watermark", () => {
407 const steerItems = [
408 runItem("item-steer-user", steerRunId, 3_000_001),
409 runItem("item-steer-reply", steerRunId, 3_000_002),
410 ];
411 let projection: OrchestrationV2ThreadProjection | null = windowOf(
412 [run, queuedRun, steerRun],
413 steerItems,
414 );
415 let latestLocalTurnOrdinal = 3_000_002;
416 const queuedUser = runItem("item-queued-user", queuedRunId, 2_000_001);
417 const queuedReply = runItem("item-queued-reply", queuedRunId, 2_000_002);
418 for (const event of [
419 itemEvent(queuedUser),
420 runEvent({ ...queuedRun, status: "running" }),
421 itemEvent(queuedReply),
422 runEvent({ ...queuedRun, status: "completed" }),
423 ]) {
424 projection = applyOrchestrationV2ProjectionEvent(projection, event, {
425 partialTimeline: true,
426 latestLocalTurnOrdinal,
427 });
428 if (event.type === "turn-item.updated") {
429 latestLocalTurnOrdinal = Math.max(latestLocalTurnOrdinal, event.payload.ordinal);
430 }
431 }
432​
433 expect(projection?.visibleTurnItems.map((row) => row.item.id)).toEqual([
434 queuedUser.id,
435 queuedReply.id,
436 ...steerItems.map((item) => item.id),
437 ]);
438 expect(projection?.turnItems.map((item) => item.id)).toContain(queuedReply.id);
439 });
440​
441 it("keeps a running run's item when the window starts after its earlier items", () => {
442 const runningRun = { ...queuedRun, status: "running" } as const;
443 const steerItems = [
444 runItem("item-steer-user", steerRunId, 3_000_001),
445 runItem("item-steer-reply", steerRunId, 3_000_002),
446 ];
447 const projection = windowOf([run, runningRun, steerRun], steerItems);
448 const reply = runItem("item-running-reply", queuedRunId, 2_000_003);
449​
450 const next = applyOrchestrationV2ProjectionEvent(projection, itemEvent(reply), {
451 partialTimeline: true,
452 latestLocalTurnOrdinal: 3_000_002,
453 });
454​
455 expect(next?.visibleTurnItems.map((row) => row.item.id)).toEqual([
456 reply.id,
457 ...steerItems.map((item) => item.id),
458 ]);
459 });
460​
461 it.each([
462 { name: "keeps an item of a run already in the window", itemRunId: steerRunId, kept: true },
463 { name: "drops an item of a finished run outside the window", itemRunId: runId, kept: false },
464 ])("$name that arrives below the watermark", ({ itemRunId, kept }) => {
465 const first = runItem("item-first", steerRunId, 3_000_001);
466 const later = runItem("item-later", steerRunId, 3_000_003);
467 const late = runItem("item-late", itemRunId, 3_000_002);
468 const projection = windowOf([run, steerRun], [first, later]);
469​
470 const next = applyOrchestrationV2ProjectionEvent(projection, itemEvent(late), {
471 partialTimeline: true,
472 latestLocalTurnOrdinal: later.ordinal,
473 });
474 if (kept) {
475 expect(next?.visibleTurnItems.map((row) => row.item.id)).toEqual([
476 first.id,
477 late.id,
478 later.id,
479 ]);
480 } else {
481 expect(next).toBe(projection);
482 }
483 });
484 });
485​
486 it("hides a steer that fell back to the queue until its run starts", () => {
487 const queuedRunId = RunId.make("run-requeued-steer");
488 const queuedRun = { ...run, id: queuedRunId, ordinal: 2, status: "queued" } as const;
489 const reply = commandItem("item-reply", "reply", 102);
490 const steer: OrchestrationV2TurnItem = {
491 id: TurnItemId.make("item-steer"),
492 threadId,
493 runId,
494 nodeId: null,
495 providerThreadId: null,
496 providerTurnId: null,
497 nativeItemRef: null,
498 parentItemId: null,
499 ordinal: 101,
500 status: "completed",
501 title: null,
502 startedAt: now,
503 completedAt: now,
504 updatedAt: now,
505 type: "user_message",
506 messageId: MessageId.make("message-steer"),
507 inputIntent: "steer",
508 text: "steer",
509 attachments: [],
510 createdBy: "user",
511 creationSource: "web",
512 };
513 const event = (type: "run.updated" | "turn-item.updated", payload: unknown) =>
514 ({
515 id: `event-${type}`,
516 type,
517 threadId,
518 occurredAt: now,
519 payload,
520 }) as OrchestrationV2DomainEvent;
521 const apply = (
522 projection: OrchestrationV2ThreadProjection | null,
523 events: ReadonlyArray<OrchestrationV2DomainEvent>,
524 options?: Parameters<typeof applyOrchestrationV2ProjectionEvent>[2],
525 ) =>
526 events.reduce(
527 (next, item) => applyOrchestrationV2ProjectionEvent(next, item, options),
528 projection,
529 );
530 const visibleIds = (projection: OrchestrationV2ThreadProjection | null) =>
531 projection?.visibleTurnItems.map((row) => row.item.id);
532​
533 const accepted = apply(emptyProjection, [
534 event("run.updated", { ...run, status: "running" }),
535 event("turn-item.updated", steer),
536 event("turn-item.updated", reply),
537 ]);
538 expect(visibleIds(accepted)).toEqual([steer.id, reply.id]);
539​
540 const queuedSteer = event("turn-item.updated", {
541 ...steer,
542 runId: queuedRunId,
543 inputIntent: "queued_turn",
544 status: "pending",
545 startedAt: null,
546 completedAt: null,
547 });
548 const requeued = apply(accepted, [event("run.updated", queuedRun), queuedSteer]);
549 expect(visibleIds(requeued)).toEqual([reply.id]);
550​
551 // Servers publish the start's user item and run update in either order.
552 const starting = event("run.updated", { ...queuedRun, status: "starting" });
553 const dispatchedSteer = event("turn-item.updated", {
554 ...steer,
555 runId: queuedRunId,
556 inputIntent: "queued_turn",
557 });
558 expect(visibleIds(apply(requeued, [dispatchedSteer, starting]))).toEqual([steer.id, reply.id]);
559 expect(visibleIds(apply(requeued, [starting, dispatchedSteer]))).toEqual([steer.id, reply.id]);
560 const cancelled = event("run.updated", { ...queuedRun, status: "cancelled" });
561 expect(visibleIds(apply(requeued, [cancelled]))).toEqual([reply.id]);
562​
563 // A bounded snapshot taken while queued omits the hidden item, and the
564 // newer reply puts it below the watermark.
565 const bounded = {
566 ...requeued!,
567 turnItems: requeued!.turnItems.filter((item) => item.id !== steer.id),
568 };
569 const partial = { partialTimeline: true, latestLocalTurnOrdinal: reply.ordinal };
570 const failed = event("run.updated", { ...queuedRun, status: "failed" });
571 const failedSteer = event("turn-item.updated", {
572 ...steer,
573 runId: queuedRunId,
574 inputIntent: "queued_turn",
575 status: "failed",
576 startedAt: null,
577 });
578 const retained = apply(bounded, [failedSteer], partial);
579 expect(visibleIds(retained)).toEqual([reply.id]);
580 expect(visibleIds(apply(retained, [failed], partial))).toEqual([steer.id, reply.id]);
581 // Leaving the queue never invents an item the client did not receive.
582 expect(visibleIds(apply(bounded, [failed], partial))).toEqual([reply.id]);
583 });
584​
585 it("removes only hidden local items while preserving inherited rows", () => {
586 const inherited = commandItem("item-inherited");
587 const local = commandItem("item-local");
588 const inheritedRow = {
589 position: 0,
590 visibility: "inherited" as const,
591 sourceThreadId: ThreadId.make("thread-source"),
592 sourceItemId: inherited.id,
593 item: inherited,
594 };
595 const localRow = {
596 position: 1,
597 visibility: "local" as const,
598 sourceThreadId: threadId,
599 sourceItemId: local.id,
600 item: local,
601 };
602 const projection = {
603 ...emptyProjection,
604 runs: [run],
605 turnItems: [local],
606 visibleTurnItems: [inheritedRow, localRow],
607 };
608 const event = {
609 id: "event-run-rollback",
610 type: "run.updated",
611 threadId,
612 runId,
613 occurredAt: now,
614 payload: { ...run, status: "rolled_back" },
615 } as OrchestrationV2DomainEvent;
616​
617 const next = applyOrchestrationV2ProjectionEvent(projection, event);
618 expect(next?.visibleTurnItems).toEqual([inheritedRow]);
619 expect(next?.visibleTurnItems[0]).toBe(inheritedRow);
620 });
621});
622​
623it("does not scan every row against every run for a streaming item update", () => {
624 let runReads = 0;
625 const runs = Array.from({ length: 100 }, (_, index) => ({
626 ...run,
627 get id() {
628 runReads++;
629 return RunId.make(`run-${index}`);
630 },
631 }));
632 const items = Array.from({ length: 1000 }, (_, index) =>
633 commandItem(`item-${index}`, "before", index),
634 );
635 const projection = {
636 ...emptyProjection,
637 runs,
638 turnItems: items,
639 visibleTurnItems: items.map((item, position) => ({
640 item,
641 position,
642 visibility: "local" as const,
643 sourceThreadId: threadId,
644 sourceItemId: item.id,
645 })),
646 };
647 const payload = commandItem("item-999", "after", 999);
648 const next = applyOrchestrationV2ProjectionEvent(projection, {
649 id: "stream-update",
650 type: "turn-item.updated",
651 threadId,
652 occurredAt: now,
653 payload,
654 } as OrchestrationV2DomainEvent);
655 expect(next?.visibleTurnItems.at(-1)?.item).toBe(payload);
656 expect(next?.visibleTurnItems[0]).toBe(projection.visibleTurnItems[0]);
657 expect(runReads).toBeLessThanOrEqual(100);
658});
659​
660it("does not scan ordering or positions for a streamed replacement", () => {
661 const size = 100;
662 let ordinalReads = 0;
663 let positionReads = 0;
664 const items = Array.from({ length: size }, (_, index) => ({
665 ...commandItem(`item-${index}`, "before", index),
666 get ordinal() {
667 ordinalReads++;
668 return index;
669 },
670 }));
671 const rows = items.map((item, index) => ({
672 item,
673 get position() {
674 positionReads++;
675 return index;
676 },
677 visibility: "local" as const,
678 sourceThreadId: threadId,
679 sourceItemId: item.id,
680 }));
681 const payload = commandItem(`item-${size - 1}`, "after", size - 1);
682 const next = applyOrchestrationV2ProjectionEvent(
683 { ...emptyProjection, runs: [run], turnItems: items, visibleTurnItems: rows },
684 {
685 id: "stable-replacement",
686 type: "turn-item.updated",
687 threadId,
688 occurredAt: now,
689 payload,
690 } as OrchestrationV2DomainEvent,
691 );
692​
693 expect(ordinalReads).toBeLessThanOrEqual(2);
694 expect(positionReads).toBeLessThanOrEqual(2);
695 expect(next?.visibleTurnItems.at(-1)).toMatchObject({ item: payload, position: size - 1 });
696 expect(next?.visibleTurnItems[0]).toBe(rows[0]);
697 expect(items.at(-1)).toMatchObject({ output: "before" });
698 expect(rows.at(-1)?.item).toBe(items.at(-1));
699});
700​
701it("rechecks visibility when an existing item changes runs without changing ordinal", () => {
702 const first = commandItem("item-first", "first", 1);
703 const second = commandItem("item-second", "second", 2);
704 const rolledBackRun = { ...run, id: RunId.make("rolled-back"), status: "rolled_back" as const };
705 const projection = {
706 ...emptyProjection,
707 runs: [run, rolledBackRun],
708 turnItems: [first, second],
709 visibleTurnItems: [first, second].map((item, position) => ({
710 item,
711 position,
712 visibility: "local" as const,
713 sourceThreadId: threadId,
714 sourceItemId: item.id,
715 })),
716 };
717 const update = (payload: OrchestrationV2TurnItem) =>
718 ({
719 id: "visibility-update",
720 type: "turn-item.updated",
721 threadId,
722 occurredAt: now,
723 payload,
724 }) as OrchestrationV2DomainEvent;
725 const hidden = applyOrchestrationV2ProjectionEvent(
726 projection,
727 update({
728 ...first,
729 runId: rolledBackRun.id,
730 }),
731 );
732 expect(hidden?.visibleTurnItems.map((row) => [row.item.id, row.position])).toEqual([
733 [second.id, 0],
734 ]);
735 const restored = applyOrchestrationV2ProjectionEvent(hidden, update(first));
736 expect(restored?.visibleTurnItems.map((row) => [row.item.id, row.position])).toEqual([
737 [first.id, 0],
738 [second.id, 1],
739 ]);
740 expect(projection.visibleTurnItems[0]?.item).toBe(first);
741});
742​
743it("restores deduped bodies from the records it already holds, also in a partial timeline", () => {
744 const messageId = MessageId.make("message-deduped");
745 const subagentId = NodeId.make("node-deduped");
746 const reply = {
747 ...commandItem("item-reply", "", 400),
748 type: "assistant_message",
749 messageId,
750 text: "the full reply",
751 streaming: false,
752 } as unknown as OrchestrationV2TurnItem;
753 const subagent = {
754 id: subagentId,
755 threadId,
756 runId,
757 parentNodeId: NodeId.make("node-root"),
758 origin: "app_owned",
759 createdBy: "agent",
760 driver: "codex",
761 providerInstanceId: ProviderInstanceId.make("codex"),
762 providerThreadId: null,
763 childThreadId: null,
764 nativeTaskRef: null,
765 prompt: "the task brief",
766 title: null,
767 model: null,
768 status: "running",
769 result: null,
770 startedAt: now,
771 completedAt: null,
772 updatedAt: now,
773 } as OrchestrationV2ThreadProjection["subagents"][number];
774 const projection = {
775 ...emptyProjection,
776 runs: [run],
777 messages: [
778 {
779 id: messageId,
780 threadId,
781 runId,
782 nodeId: null,
783 role: "assistant",
784 text: "the full reply",
785 attachments: [],
786 streaming: false,
787 createdBy: "agent",
788 creationSource: "provider",
789 createdAt: now,
790 updatedAt: now,
791 },
792 ],
793 subagents: [subagent],
794 } as OrchestrationV2ThreadProjection;
795 const event = (type: string, payload: unknown) =>
796 ({
797 id: `event-${type}`,
798 type,
799 threadId,
800 runId,
801 occurredAt: now,
802 payload,
803 }) as OrchestrationV2DomainEvent;
804​
805 for (const options of [undefined, { partialTimeline: true, latestLocalTurnOrdinal: 300 }]) {
806 const withReply = applyOrchestrationV2ProjectionEvent(
807 projection,
808 event("turn-item.updated", { ...reply, text: "", textOmitted: true }),
809 options,
810 );
811 expect(withReply?.turnItems).toEqual([reply]);
812 expect(withReply?.visibleTurnItems.map((row) => row.item)).toEqual([reply]);
813 }
814 const progressed = applyOrchestrationV2ProjectionEvent(
815 projection,
816 event("subagent.updated", { ...subagent, prompt: "", promptOmitted: true, progress: "step 2" }),
817 );
818 expect(progressed?.subagents).toEqual([{ ...subagent, progress: "step 2" }]);
819});