1import { RunId, ThreadId, TurnItemId, type OrchestrationV2TurnItem } from "@t3tools/contracts";
2import * as DateTime from "effect/DateTime";
3import { describe, expect, it } from "vite-plus/test";
4
5import { deriveTimelineEntriesFromVisibleTurnItems } from "./session-logic";
6import {
7 deriveMessagesTimelineRows,
8 workEntryDisplayLabel,
9} from "./components/chat/MessagesTimeline.logic";
10
11const retainedMessage =
12 "2026-03-14T16:11:12.550224Z ERROR codex_core::codex: failed to load skill /home/sebherrerabe/repos/devsuite/.agent/skills/monorepo-scaffolding/SKILL.md: invalid YAML: mapping va...";
13const warningSummary =
14 "2026-03-14T16:11:12.550224Z ERROR codex_core::codex: failed to load skill /home/sebherrerabe/repos/devsuite/.agent/sk...";
15const now = DateTime.makeUnsafe("2026-09-05T00:00:00.000Z");
16const baseItem = {
17 id: TurnItemId.make("diagnostic"),
18 threadId: ThreadId.make("thread-1"),
19 runId: RunId.make("run-1"),
20 nodeId: null,
21 providerThreadId: null,
22 providerTurnId: null,
23 nativeItemRef: null,
24 parentItemId: null,
25 ordinal: 0,
26 status: "completed" as const,
27 title: "Runtime error",
28 startedAt: now,
29 completedAt: now,
30 updatedAt: now,
31};
32
33function workEntry(item: OrchestrationV2TurnItem) {
34 const [entry] = deriveTimelineEntriesFromVisibleTurnItems({
35 visibleTurnItems: [
36 {
37 position: 0,
38 visibility: "local",
39 sourceThreadId: item.threadId,
40 sourceItemId: item.id,
41 item,
42 },
43 ],
44 optimisticMessages: [],
45 });
46 if (entry?.kind !== "work") throw new Error("Expected a work-log entry");
47 return entry.entry;
48}
49
50function errorItem(
51 overrides: Partial<Extract<OrchestrationV2TurnItem, { type: "error" }>> = {},
52): Extract<OrchestrationV2TurnItem, { type: "error" }> {
53 return {
54 ...baseItem,
55 type: "error",
56 failure: {
57 class: "provider_error",
58 message: retainedMessage,
59 code: "runtime_error",
60 retryable: false,
61 },
62 ...overrides,
63 };
64}
65
66describe("runtime diagnostics in the v2 work log", () => {
67 it("shows the retained error message in place of a generic row label", () => {
68 const entry = workEntry(errorItem());
69
70 expect(entry).toMatchObject({ label: "Runtime error", detail: retainedMessage });
71 expect(workEntryDisplayLabel(entry, undefined)).toBe(retainedMessage);
72 });
73
74 it("shows the retained diagnostic message beyond its truncated title", () => {
75 const entry = workEntry(errorItem({ title: warningSummary }));
76
77 expect(entry).toMatchObject({ label: warningSummary, detail: retainedMessage });
78 expect(workEntryDisplayLabel(entry, undefined)).toBe(retainedMessage);
79 });
80
81 it("uses the full system notice instead of a truncated warning title", () => {
82 const entry = workEntry({
83 ...baseItem,
84 type: "system_notice",
85 title: warningSummary,
86 message: retainedMessage,
87 });
88
89 expect(entry.label).toBe(retainedMessage);
90 expect(workEntryDisplayLabel(entry, undefined)).toBe(retainedMessage);
91 expect(entry.detail).toBeUndefined();
92 });
93
94 it("keeps retry progress visible while retaining its diagnostic detail", () => {
95 const entry = workEntry(
96 errorItem({
97 title: "Provider retry",
98 status: "running",
99 retry: { attempt: 2, maxAttempts: 5, retryDelayMs: 1_500 },
100 }),
101 );
102
103 expect(workEntryDisplayLabel(entry, undefined)).toBe("Retrying provider (2/5)");
104 expect(entry.detail).toBe(`${retainedMessage} Retrying in 1.5s.`);
105 });
106
107 it("keeps a diagnostic separate from adjacent tool summaries", () => {
108 const entries = deriveTimelineEntriesFromVisibleTurnItems({
109 visibleTurnItems: [
110 {
111 ...baseItem,
112 type: "command_execution" as const,
113 id: TurnItemId.make("command-before"),
114 input: "git status",
115 output: "clean",
116 exitCode: 0,
117 },
118 errorItem(),
119 {
120 ...baseItem,
121 type: "command_execution" as const,
122 id: TurnItemId.make("command-after"),
123 input: "git diff",
124 output: "",
125 exitCode: 0,
126 },
127 ].map((item, position) => ({
128 position,
129 visibility: "local" as const,
130 sourceThreadId: item.threadId,
131 sourceItemId: item.id,
132 item,
133 })),
134 optimisticMessages: [],
135 });
136 const rows = deriveMessagesTimelineRows({
137 timelineEntries: entries,
138 isWorking: true,
139 activeTurnStartedAt: DateTime.formatIso(now),
140 latestRun: {
141 runId: baseItem.runId,
142 status: "running",
143 startedAt: DateTime.formatIso(now),
144 completedAt: null,
145 },
146 turnDiffSummaries: [],
147 supportsConversationRollback: false,
148 });
149 const diagnostic = rows.find((row) => row.id === baseItem.id);
150
151 expect(diagnostic?.kind).toBe("work");
152 if (diagnostic?.kind !== "work") throw new Error("Expected a diagnostic row");
153 expect(diagnostic.isExpandedToolGroup).toBe(false);
154 expect(diagnostic.groupedEntries).toHaveLength(1);
155 expect(workEntryDisplayLabel(diagnostic.groupedEntries[0]!, undefined)).toBe(retainedMessage);
156 });
157
158 it("does not turn an unrelated tool payload message into a diagnostic", () => {
159 const entry = workEntry({
160 ...baseItem,
161 type: "dynamic_tool",
162 title: "Read file",
163 toolName: "read_file",
164 input: {},
165 output: { message: retainedMessage },
166 });
167
168 expect(entry.label).toBe("Read file");
169 expect(entry.detail).toBeUndefined();
170 });
171});