1import {
2 ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE,
3 RunId,
4 ThreadId,
5 TurnItemId,
6 type OrchestrationV2Run,
7 type OrchestrationV2TurnItem,
8} from "@t3tools/contracts";
9import * as DateTime from "effect/DateTime";
10import { describe, expect, it } from "vite-plus/test";
11
12import {
13 turnItemIsWorkspacePreparation,
14 workspacePreparationRetryRunIds,
15} from "./turnItemPresentation.ts";
16
17function command(input: string): OrchestrationV2TurnItem {
18 const now = DateTime.makeUnsafe("2026-08-03T00:00:00.000Z");
19 return {
20 id: TurnItemId.make("item-command"),
21 threadId: ThreadId.make("thread-1"),
22 runId: RunId.make("run-1"),
23 nodeId: null,
24 providerThreadId: null,
25 providerTurnId: null,
26 nativeItemRef: null,
27 parentItemId: null,
28 ordinal: 1,
29 status: "completed",
30 title: "Workspace ready",
31 startedAt: now,
32 completedAt: now,
33 updatedAt: now,
34 type: "command_execution",
35 input,
36 output: "Workspace preparation completed.",
37 exitCode: 0,
38 };
39}
40
41function preparationFailure(
42 status: OrchestrationV2TurnItem["status"],
43 code: string | null = ORCHESTRATION_V2_WORKSPACE_PREPARATION_FAILURE_CODE,
44): OrchestrationV2TurnItem {
45 const {
46 input: _input,
47 output: _output,
48 exitCode: _exitCode,
49 ...base
50 } = command("") as Extract<OrchestrationV2TurnItem, { type: "command_execution" }>;
51 return {
52 ...base,
53 id: TurnItemId.make("item-error"),
54 status,
55 title: "Workspace preparation failed",
56 type: "error",
57 failure: { class: "validation_error", message: "fetch failed", code, retryable: false },
58 };
59}
60
61const worktree = { type: "worktree", baseRef: "main" } as const;
62function run(
63 status: OrchestrationV2Run["status"],
64 workspacePreparation?: typeof worktree,
65): Pick<OrchestrationV2Run, "id" | "status" | "workspacePreparation"> {
66 return {
67 id: RunId.make("run-1"),
68 status,
69 ...(workspacePreparation === undefined ? {} : { workspacePreparation }),
70 };
71}
72
73describe("turnItemIsWorkspacePreparation", () => {
74 it("identifies the synthetic workspace preparation command", () => {
75 expect(turnItemIsWorkspacePreparation(command("Preparing workspace"))).toBe(true);
76 expect(turnItemIsWorkspacePreparation(command("prepare workspace"))).toBe(false);
77 });
78
79 it("hides a preparation failure only once a retry cancelled it", () => {
80 expect(turnItemIsWorkspacePreparation(preparationFailure("failed"))).toBe(false);
81 expect(turnItemIsWorkspacePreparation(preparationFailure("cancelled"))).toBe(true);
82 expect(turnItemIsWorkspacePreparation(preparationFailure("cancelled", null))).toBe(false);
83 });
84});
85
86describe("workspacePreparationRetryRunIds", () => {
87 it("offers a retry while the run still ends in its failed preparation", () => {
88 const failure = [preparationFailure("failed")];
89 expect([...workspacePreparationRetryRunIds([run("failed", worktree)], failure)]).toEqual([
90 RunId.make("run-1"),
91 ]);
92 // Retried: the run is preparing again and the old failure is cancelled.
93 expect(workspacePreparationRetryRunIds([run("preparing", worktree)], failure).size).toBe(0);
94 // An older server records no preparation, so there is nothing to repeat.
95 expect(workspacePreparationRetryRunIds([run("failed")], failure).size).toBe(0);
96 // A provider error on a run that did reach the provider is not a preparation failure.
97 expect(
98 workspacePreparationRetryRunIds(
99 [run("failed", worktree)],
100 [preparationFailure("failed", "provider_crashed")],
101 ).size,
102 ).toBe(0);
103 });
104});