import { MessageId, NodeId, PlanId, ProviderInstanceId, ProviderDriverKind, ProviderThreadId, RunId, RunAttemptId, ThreadId, TurnItemId, type OrchestrationV2RunAttempt, type OrchestrationV2ProjectedTurnItem, type OrchestrationV2TurnItem,} from "@t3tools/contracts";import * as DateTime from "effect/DateTime";import { describe, expect, it } from "vite-plus/test"; import { buildThreadFeed, deriveThreadFeedPresentation, threadFeedActivityIsVisible, threadFeedRunIsUnsettled, type ThreadFeedActivity, type ThreadFeedEntry, togglePendingUserInputOptionSelection, setPendingUserInputCustomAnswer, isPendingUserInputOptionSelected, buildPendingUserInputAnswers,} from "./threadActivity"; const threadId = ThreadId.make("thread-1");const sourceThreadId = ThreadId.make("thread-source");const runId = RunId.make("run-1"); it("keeps historical plan detail accessible from its paged turn item", () => { const item = { ...base("historical-plan", "2026-08-29T00:00:00.000Z", 1), type: "proposed_plan", planId: "plan-historical", markdown: "Full historical plan text", streaming: false, } as OrchestrationV2TurnItem; const entries = buildThreadFeed([projected(item, 0)]); const activity = entries.flatMap((entry) => entry.type === "activity-group" ? entry.activities : [], )[0]; expect(activity?.detail).toBe("Full historical plan text"); expect(activity?.getFullDetail()).toContain("Full historical plan text");}); function base(id: string, updatedAt: string, ordinal: number) { const timestamp = DateTime.makeUnsafe(updatedAt); return { id: TurnItemId.make(id), threadId, runId, nodeId: null, providerThreadId: null, providerTurnId: null, nativeItemRef: null, parentItemId: null, ordinal, status: "completed" as const, title: null, startedAt: timestamp, completedAt: timestamp, updatedAt: timestamp, };} function projected( item: OrchestrationV2TurnItem, position: number, visibility: OrchestrationV2ProjectedTurnItem["visibility"] = "local",): OrchestrationV2ProjectedTurnItem { return { position, visibility, sourceThreadId: visibility === "local" ? threadId : sourceThreadId, sourceItemId: item.id, item, };} function userMessage(updatedAt = "2026-06-20T00:00:01.000Z") { return { ...base("item-user", updatedAt, 0), type: "user_message" as const, messageId: MessageId.make("message-user"), createdBy: "user" as const, creationSource: "mobile" as const, inputIntent: "turn_start" as const, text: "Run checks", attachments: [], };} function command(updatedAt = "2026-06-20T00:00:02.000Z") { return { ...base("item-command", updatedAt, 1), type: "command_execution" as const, input: "vp check", output: "ok", exitCode: 0, };} function assistantMessage(updatedAt = "2026-06-20T00:00:03.000Z") { return { ...base("item-assistant", updatedAt, 2), type: "assistant_message" as const, messageId: MessageId.make("message-assistant"), text: "Done", streaming: false, };} describe("buildThreadFeed", () => { it("adds local feedback messages to an otherwise server-authored feed", () => { const feed = buildThreadFeed([], { localMessages: [ { id: MessageId.make("feedback-local"), role: "assistant", text: "Feedback sent to OpenAI.\n\nThread ID: `codex-thread-1`", turnId: null, streaming: false, createdAt: "2026-08-29T00:00:00.000Z", updatedAt: "2026-08-29T00:00:00.000Z", }, ], }); expect(feed).toHaveLength(1); expect(feed[0]).toMatchObject({ type: "message", message: { id: "feedback-local", role: "assistant", text: expect.stringContaining("codex-thread-1"), }, }); }); it("anchors feedback before later committed turns and appends true optimistic messages", () => { const laterUser = { ...userMessage("2026-08-29T00:00:05.000Z"), id: TurnItemId.make("item-later-user"), messageId: MessageId.make("message-later-user"), ordinal: 2, text: "Later user turn", }; const laterAssistant = { ...assistantMessage("2026-08-29T00:00:04.000Z"), id: TurnItemId.make("item-later-assistant"), messageId: MessageId.make("message-later-assistant"), ordinal: 3, text: "Later assistant turn", }; const localMessage = (id: string, role: "user" | "assistant") => ({ id: MessageId.make(id), role, text: id, turnId: null, streaming: false, createdAt: "2026-08-29T00:00:03.000Z", updatedAt: "2026-08-29T00:00:03.000Z", }); const feed = buildThreadFeed( [ projected(userMessage("2026-08-29T00:00:01.000Z"), 0), projected(laterUser, 1), projected(laterAssistant, 2), ], { anchoredMessages: [ localMessage("feedback-user", "user"), localMessage("feedback-assistant", "assistant"), localMessage("message-later-user", "user"), ], localMessages: [ { ...localMessage("optimistic-user", "user"), createdAt: "2026-08-29T00:00:00.000Z", }, ], }, ); const messages = feed.filter((entry) => entry.type === "message"); expect(messages.map((entry) => entry.id)).toEqual([ "message-user", "feedback-user", "feedback-assistant", "message-later-user", "message-later-assistant", "optimistic-user", ]); expect( messages .filter((entry) => entry.id.startsWith("feedback-")) .every((entry) => entry.message.projectedItem === undefined), ).toBe(true); }); it("keeps prominent activity visible while it is running", () => { expect( threadFeedActivityIsVisible({ prominent: true, status: "neutral", toolLike: true }), ).toBe(true); expect( threadFeedActivityIsVisible({ prominent: false, status: "neutral", toolLike: true }), ).toBe(false); }); it("keeps provider notices visible outside completed work folds without failure styling", () => { const message = "Safeguards flagged this message. Switched to Opus 4.8."; const item = { ...base("item-system-notice", "2026-06-20T00:00:02.000Z", 1), type: "system_notice" as const, message, }; const feed = buildThreadFeed([projected(item, 0)]); const presented = deriveThreadFeedPresentation(feed, null, new Set()); const activities = presented.flatMap((entry) => entry.type === "activity-group" ? entry.activities : [], ); expect(activities).toHaveLength(1); expect(activities[0]).toMatchObject({ summary: message, detail: message, prominent: true, toolLike: false, status: null, icon: "warning", workEntry: { tone: "info", itemType: "system_notice" }, }); expect(presented.some((entry) => entry.type === "run-fold")).toBe(false); }); it("presents provider retries as visible work-log activity", () => { const retryBase = { ...base("item-provider-retry", "2026-06-20T00:00:02.000Z", 1), type: "error" as const, failure: { class: "transport_error" as const, message: "The response stream disconnected.", code: "responseStreamDisconnected", retryable: true, }, retry: { attempt: 2, maxAttempts: 5, retryDelayMs: null, }, }; const runningFeed = buildThreadFeed([ projected( { ...retryBase, status: "running", title: "Provider retry", completedAt: null, }, 0, ), ]); const recoveredFeed = buildThreadFeed([ projected( { ...retryBase, status: "completed", title: "Provider recovered", }, 0, ), ]); const failedFeed = buildThreadFeed([ projected( { ...retryBase, status: "failed", title: "Provider retry failed", }, 0, ), projected(command("2026-06-20T00:00:03.000Z"), 1), ]); const runningActivity = runningFeed.find((entry) => entry.type === "activity-group") ?.activities[0]; const recoveredActivity = recoveredFeed.find((entry) => entry.type === "activity-group") ?.activities[0]; if (runningActivity === undefined || recoveredActivity === undefined) { throw new Error("Expected provider retry work-log activities."); } expect(runningActivity).toMatchObject({ summary: "Provider retry", status: "neutral", toolLike: false, }); expect(threadFeedActivityIsVisible(runningActivity)).toBe(true); expect(recoveredActivity).toMatchObject({ summary: "Provider recovered", status: "success", toolLike: false, }); const failedPresentation = deriveThreadFeedPresentation( failedFeed, { runId, status: "running", startedAt: null, completedAt: null }, new Set(), ); expect(failedPresentation.map((entry) => entry.type)).toEqual([ "activity-group", "work-toggle", ]); expect( failedPresentation[0]?.type === "activity-group" ? failedPresentation[0].activities[0]?.summary : null, ).toBe("Provider retry failed"); }); it.each(["pending", "running", "completed"] as const)( "omits %s task progress without hiding adjacent conversation items", (stepStatus) => { const todoItem = { ...base("item-tasks", "2026-06-20T00:00:02.500Z", 2), type: "todo_list" as const, planId: PlanId.make("plan-tasks"), steps: [{ id: "step-1", text: "Verify the change", status: stepStatus }], } satisfies OrchestrationV2TurnItem; const user = projected(userMessage(), 0); const tool = projected(command(), 1); const assistant = projected(assistantMessage(), 3); expect(buildThreadFeed([user, tool, projected(todoItem, 2), assistant])).toEqual( buildThreadFeed([user, tool, assistant]), ); }, ); it("hides synthetic workspace preparation activity", () => { const workspacePreparation = projected( { ...command(), title: "Workspace ready", input: "Preparing workspace", output: "Workspace preparation completed.", }, 0, ); expect(buildThreadFeed([workspacePreparation])).toEqual([]); }); it("does not treat a queued-only run as live feed activity", () => { expect( threadFeedRunIsUnsettled({ runId, status: "queued", startedAt: null, completedAt: null, }), ).toBe(false); expect( threadFeedRunIsUnsettled({ runId, status: "running", startedAt: "2026-06-20T00:00:01.000Z", completedAt: null, }), ).toBe(true); expect( threadFeedRunIsUnsettled({ runId, status: "completed", startedAt: "2026-06-20T00:00:01.000Z", completedAt: null, }), ).toBe(true); expect( threadFeedRunIsUnsettled({ runId, status: "waiting", startedAt: "2026-06-20T00:00:01.000Z", completedAt: null, }), ).toBe(true); }); it("adds queued input only after dispatch creates its turn item", () => { const dispatchedRunId = RunId.make("run-dispatched-queued"); const dispatchedMessageId = MessageId.make("message-dispatched-queued"); expect(buildThreadFeed([])).toEqual([]); const promotedEntries = buildThreadFeed([ projected( { ...userMessage(), id: TurnItemId.make("item-dispatched-queued"), runId: dispatchedRunId, messageId: dispatchedMessageId, inputIntent: "turn_start", }, 0, ), ]); expect(promotedEntries.map((entry) => entry.id)).toEqual([dispatchedMessageId]); expect( promotedEntries[0]?.type === "message" ? promotedEntries[0].message.inputIntent : undefined, ).toBe("turn_start"); }); it("hides the interruption request and keeps the terminal result", () => { const request = projected( { ...base("item-interrupt-request", "2026-06-20T00:00:02.000Z", 1), type: "run_interrupt_request", message: "Interrupt requested", }, 0, ); const result = projected( { ...base("item-interrupt-result", "2026-06-20T00:00:03.000Z", 2), type: "run_interrupt_result", message: "Run interrupted before provider start", }, 1, ); const activities = buildThreadFeed([request, result]).flatMap((entry) => entry.type === "activity-group" ? entry.activities : [], ); expect(activities).toHaveLength(1); expect(activities[0]?.summary).toBe("Run interrupted"); expect(activities[0]?.detail).toBe("Run interrupted before provider start"); expect( deriveThreadFeedPresentation( buildThreadFeed([request, result]), { runId, status: "interrupted", startedAt: "2026-06-20T00:00:01.000Z", completedAt: "2026-06-20T00:00:03.000Z", }, new Set(), ).some((entry) => entry.type === "run-fold"), ).toBe(false); }); it("preserves authoritative V2 order instead of sorting reconstructed collections", () => { const rows = [ projected(userMessage("2026-06-20T00:00:03.000Z"), 0), projected(command("2026-06-20T00:00:01.000Z"), 1), projected(assistantMessage("2026-06-20T00:00:02.000Z"), 2), ]; const feed = buildThreadFeed(rows); expect(feed.map((entry) => entry.type)).toEqual(["message", "activity-group", "message"]); expect(feed.map((entry) => entry.id)).toEqual([ "message-user", "local:thread-1:item-command", "message-assistant", ]); const activity = feed.find((entry) => entry.type === "activity-group")?.activities[0]; expect(activity?.projectedItem).toBe(rows[1]); expect(activity?.getFullDetail()).toContain('"input": "vp check"'); }); it("keeps adjacent work from different V2 attempts in separate groups", () => { const firstRootNodeId = NodeId.make("node-attempt-1"); const secondRootNodeId = NodeId.make("node-attempt-2"); const firstCommand = { ...command(), nodeId: firstRootNodeId }; const secondCommand = { ...command("2026-06-20T00:00:03.000Z"), id: TurnItemId.make("item-command-retry"), ordinal: 2, nodeId: secondRootNodeId, }; const attempts = [ { id: RunAttemptId.make("attempt-1"), runId, attemptOrdinal: 1, rootNodeId: firstRootNodeId, providerInstanceId: ProviderInstanceId.make("provider-instance-1"), providerThreadId: ProviderThreadId.make("provider-thread-1"), providerTurnId: null, reason: "initial", status: "completed", startedAt: DateTime.makeUnsafe("2026-06-20T00:00:01.000Z"), completedAt: DateTime.makeUnsafe("2026-06-20T00:00:02.000Z"), }, { id: RunAttemptId.make("attempt-2"), runId,Lines 1–500 of 1291. Load more
import { MessageId, NodeId, PlanId, ProviderInstanceId, ProviderDriverKind, ProviderThreadId, RunId, RunAttemptId, ThreadId, TurnItemId, type OrchestrationV2RunAttempt, type OrchestrationV2ProjectedTurnItem, type OrchestrationV2TurnItem, } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; import { describe, expect, it } from "vite-plus/test"; import { buildThreadFeed, deriveThreadFeedPresentation, threadFeedActivityIsVisible, threadFeedRunIsUnsettled, type ThreadFeedActivity, type ThreadFeedEntry, togglePendingUserInputOptionSelection, setPendingUserInputCustomAnswer, isPendingUserInputOptionSelected, buildPendingUserInputAnswers, } from "./threadActivity"; const threadId = ThreadId.make("thread-1"); const sourceThreadId = ThreadId.make("thread-source"); const runId = RunId.make("run-1"); it("keeps historical plan detail accessible from its paged turn item", () => { const item = { ...base("historical-plan", "2026-08-29T00:00:00.000Z", 1), type: "proposed_plan", planId: "plan-historical", markdown: "Full historical plan text", streaming: false, } as OrchestrationV2TurnItem; const entries = buildThreadFeed([projected(item, 0)]); const activity = entries.flatMap((entry) => entry.type === "activity-group" ? entry.activities : [], )[0]; expect(activity?.detail).toBe("Full historical plan text"); expect(activity?.getFullDetail()).toContain("Full historical plan text"); }); function base(id: string, updatedAt: string, ordinal: number) { const timestamp = DateTime.makeUnsafe(updatedAt); return { id: TurnItemId.make(id), threadId, runId, nodeId: null, providerThreadId: null, providerTurnId: null, nativeItemRef: null, parentItemId: null, ordinal, status: "completed" as const, title: null, startedAt: timestamp, completedAt: timestamp, updatedAt: timestamp, }; } function projected( item: OrchestrationV2TurnItem, position: number, visibility: OrchestrationV2ProjectedTurnItem["visibility"] = "local", ): OrchestrationV2ProjectedTurnItem { return { position, visibility, sourceThreadId: visibility === "local" ? threadId : sourceThreadId, sourceItemId: item.id, item, }; } function userMessage(updatedAt = "2026-06-20T00:00:01.000Z") { return { ...base("item-user", updatedAt, 0), type: "user_message" as const, messageId: MessageId.make("message-user"), createdBy: "user" as const, creationSource: "mobile" as const, inputIntent: "turn_start" as const, text: "Run checks", attachments: [], }; } function command(updatedAt = "2026-06-20T00:00:02.000Z") { return { ...base("item-command", updatedAt, 1), type: "command_execution" as const, input: "vp check", output: "ok", exitCode: 0, }; } function assistantMessage(updatedAt = "2026-06-20T00:00:03.000Z") { return { ...base("item-assistant", updatedAt, 2), type: "assistant_message" as const, messageId: MessageId.make("message-assistant"), text: "Done", streaming: false, }; } describe("buildThreadFeed", () => { it("adds local feedback messages to an otherwise server-authored feed", () => { const feed = buildThreadFeed([], { localMessages: [ { id: MessageId.make("feedback-local"), role: "assistant", text: "Feedback sent to OpenAI.\n\nThread ID: `codex-thread-1`", turnId: null, streaming: false, createdAt: "2026-08-29T00:00:00.000Z", updatedAt: "2026-08-29T00:00:00.000Z", }, ], }); expect(feed).toHaveLength(1); expect(feed[0]).toMatchObject({ type: "message", message: { id: "feedback-local", role: "assistant", text: expect.stringContaining("codex-thread-1"), }, }); }); it("anchors feedback before later committed turns and appends true optimistic messages", () => { const laterUser = { ...userMessage("2026-08-29T00:00:05.000Z"), id: TurnItemId.make("item-later-user"), messageId: MessageId.make("message-later-user"), ordinal: 2, text: "Later user turn", }; const laterAssistant = { ...assistantMessage("2026-08-29T00:00:04.000Z"), id: TurnItemId.make("item-later-assistant"), messageId: MessageId.make("message-later-assistant"), ordinal: 3, text: "Later assistant turn", }; const localMessage = (id: string, role: "user" | "assistant") => ({ id: MessageId.make(id), role, text: id, turnId: null, streaming: false, createdAt: "2026-08-29T00:00:03.000Z", updatedAt: "2026-08-29T00:00:03.000Z", }); const feed = buildThreadFeed( [ projected(userMessage("2026-08-29T00:00:01.000Z"), 0), projected(laterUser, 1), projected(laterAssistant, 2), ], { anchoredMessages: [ localMessage("feedback-user", "user"), localMessage("feedback-assistant", "assistant"), localMessage("message-later-user", "user"), ], localMessages: [ { ...localMessage("optimistic-user", "user"), createdAt: "2026-08-29T00:00:00.000Z", }, ], }, ); const messages = feed.filter((entry) => entry.type === "message"); expect(messages.map((entry) => entry.id)).toEqual([ "message-user", "feedback-user", "feedback-assistant", "message-later-user", "message-later-assistant", "optimistic-user", ]); expect( messages .filter((entry) => entry.id.startsWith("feedback-")) .every((entry) => entry.message.projectedItem === undefined), ).toBe(true); }); it("keeps prominent activity visible while it is running", () => { expect( threadFeedActivityIsVisible({ prominent: true, status: "neutral", toolLike: true }), ).toBe(true); expect( threadFeedActivityIsVisible({ prominent: false, status: "neutral", toolLike: true }), ).toBe(false); }); it("keeps provider notices visible outside completed work folds without failure styling", () => { const message = "Safeguards flagged this message. Switched to Opus 4.8."; const item = { ...base("item-system-notice", "2026-06-20T00:00:02.000Z", 1), type: "system_notice" as const, message, }; const feed = buildThreadFeed([projected(item, 0)]); const presented = deriveThreadFeedPresentation(feed, null, new Set()); const activities = presented.flatMap((entry) => entry.type === "activity-group" ? entry.activities : [], ); expect(activities).toHaveLength(1); expect(activities[0]).toMatchObject({ summary: message, detail: message, prominent: true, toolLike: false, status: null, icon: "warning", workEntry: { tone: "info", itemType: "system_notice" }, }); expect(presented.some((entry) => entry.type === "run-fold")).toBe(false); }); it("presents provider retries as visible work-log activity", () => { const retryBase = { ...base("item-provider-retry", "2026-06-20T00:00:02.000Z", 1), type: "error" as const, failure: { class: "transport_error" as const, message: "The response stream disconnected.", code: "responseStreamDisconnected", retryable: true, }, retry: { attempt: 2, maxAttempts: 5, retryDelayMs: null, }, }; const runningFeed = buildThreadFeed([ projected( { ...retryBase, status: "running", title: "Provider retry", completedAt: null, }, 0, ), ]); const recoveredFeed = buildThreadFeed([ projected( { ...retryBase, status: "completed", title: "Provider recovered", }, 0, ), ]); const failedFeed = buildThreadFeed([ projected( { ...retryBase, status: "failed", title: "Provider retry failed", }, 0, ), projected(command("2026-06-20T00:00:03.000Z"), 1), ]); const runningActivity = runningFeed.find((entry) => entry.type === "activity-group") ?.activities[0]; const recoveredActivity = recoveredFeed.find((entry) => entry.type === "activity-group") ?.activities[0]; if (runningActivity === undefined || recoveredActivity === undefined) { throw new Error("Expected provider retry work-log activities."); } expect(runningActivity).toMatchObject({ summary: "Provider retry", status: "neutral", toolLike: false, }); expect(threadFeedActivityIsVisible(runningActivity)).toBe(true); expect(recoveredActivity).toMatchObject({ summary: "Provider recovered", status: "success", toolLike: false, }); const failedPresentation = deriveThreadFeedPresentation( failedFeed, { runId, status: "running", startedAt: null, completedAt: null }, new Set(), ); expect(failedPresentation.map((entry) => entry.type)).toEqual([ "activity-group", "work-toggle", ]); expect( failedPresentation[0]?.type === "activity-group" ? failedPresentation[0].activities[0]?.summary : null, ).toBe("Provider retry failed"); }); it.each(["pending", "running", "completed"] as const)( "omits %s task progress without hiding adjacent conversation items", (stepStatus) => { const todoItem = { ...base("item-tasks", "2026-06-20T00:00:02.500Z", 2), type: "todo_list" as const, planId: PlanId.make("plan-tasks"), steps: [{ id: "step-1", text: "Verify the change", status: stepStatus }], } satisfies OrchestrationV2TurnItem; const user = projected(userMessage(), 0); const tool = projected(command(), 1); const assistant = projected(assistantMessage(), 3); expect(buildThreadFeed([user, tool, projected(todoItem, 2), assistant])).toEqual( buildThreadFeed([user, tool, assistant]), ); }, ); it("hides synthetic workspace preparation activity", () => { const workspacePreparation = projected( { ...command(), title: "Workspace ready", input: "Preparing workspace", output: "Workspace preparation completed.", }, 0, ); expect(buildThreadFeed([workspacePreparation])).toEqual([]); }); it("does not treat a queued-only run as live feed activity", () => { expect( threadFeedRunIsUnsettled({ runId, status: "queued", startedAt: null, completedAt: null, }), ).toBe(false); expect( threadFeedRunIsUnsettled({ runId, status: "running", startedAt: "2026-06-20T00:00:01.000Z", completedAt: null, }), ).toBe(true); expect( threadFeedRunIsUnsettled({ runId, status: "completed", startedAt: "2026-06-20T00:00:01.000Z", completedAt: null, }), ).toBe(true); expect( threadFeedRunIsUnsettled({ runId, status: "waiting", startedAt: "2026-06-20T00:00:01.000Z", completedAt: null, }), ).toBe(true); }); it("adds queued input only after dispatch creates its turn item", () => { const dispatchedRunId = RunId.make("run-dispatched-queued"); const dispatchedMessageId = MessageId.make("message-dispatched-queued"); expect(buildThreadFeed([])).toEqual([]); const promotedEntries = buildThreadFeed([ projected( { ...userMessage(), id: TurnItemId.make("item-dispatched-queued"), runId: dispatchedRunId, messageId: dispatchedMessageId, inputIntent: "turn_start", }, 0, ), ]); expect(promotedEntries.map((entry) => entry.id)).toEqual([dispatchedMessageId]); expect( promotedEntries[0]?.type === "message" ? promotedEntries[0].message.inputIntent : undefined, ).toBe("turn_start"); }); it("hides the interruption request and keeps the terminal result", () => { const request = projected( { ...base("item-interrupt-request", "2026-06-20T00:00:02.000Z", 1), type: "run_interrupt_request", message: "Interrupt requested", }, 0, ); const result = projected( { ...base("item-interrupt-result", "2026-06-20T00:00:03.000Z", 2), type: "run_interrupt_result", message: "Run interrupted before provider start", }, 1, ); const activities = buildThreadFeed([request, result]).flatMap((entry) => entry.type === "activity-group" ? entry.activities : [], ); expect(activities).toHaveLength(1); expect(activities[0]?.summary).toBe("Run interrupted"); expect(activities[0]?.detail).toBe("Run interrupted before provider start"); expect( deriveThreadFeedPresentation( buildThreadFeed([request, result]), { runId, status: "interrupted", startedAt: "2026-06-20T00:00:01.000Z", completedAt: "2026-06-20T00:00:03.000Z", }, new Set(), ).some((entry) => entry.type === "run-fold"), ).toBe(false); }); it("preserves authoritative V2 order instead of sorting reconstructed collections", () => { const rows = [ projected(userMessage("2026-06-20T00:00:03.000Z"), 0), projected(command("2026-06-20T00:00:01.000Z"), 1), projected(assistantMessage("2026-06-20T00:00:02.000Z"), 2), ]; const feed = buildThreadFeed(rows); expect(feed.map((entry) => entry.type)).toEqual(["message", "activity-group", "message"]); expect(feed.map((entry) => entry.id)).toEqual([ "message-user", "local:thread-1:item-command", "message-assistant", ]); const activity = feed.find((entry) => entry.type === "activity-group")?.activities[0]; expect(activity?.projectedItem).toBe(rows[1]); expect(activity?.getFullDetail()).toContain('"input": "vp check"'); }); it("keeps adjacent work from different V2 attempts in separate groups", () => { const firstRootNodeId = NodeId.make("node-attempt-1"); const secondRootNodeId = NodeId.make("node-attempt-2"); const firstCommand = { ...command(), nodeId: firstRootNodeId }; const secondCommand = { ...command("2026-06-20T00:00:03.000Z"), id: TurnItemId.make("item-command-retry"), ordinal: 2, nodeId: secondRootNodeId, }; const attempts = [ { id: RunAttemptId.make("attempt-1"), runId, attemptOrdinal: 1, rootNodeId: firstRootNodeId, providerInstanceId: ProviderInstanceId.make("provider-instance-1"), providerThreadId: ProviderThreadId.make("provider-thread-1"), providerTurnId: null, reason: "initial", status: "completed", startedAt: DateTime.makeUnsafe("2026-06-20T00:00:01.000Z"), completedAt: DateTime.makeUnsafe("2026-06-20T00:00:02.000Z"), }, { id: RunAttemptId.make("attempt-2"), runId,