1import * as Schema from "effect/Schema";
2
3import { IsoDateTime, NonNegativeInt, ThreadId, TrimmedNonEmptyString } from "./baseSchemas.ts";
4
5/**
6 * Live progress for a thread whose first turn is creating a worktree. The
7 * server keeps this in memory only; a client that reconnects mid-setup gets a
8 * fresh snapshot, and a finished setup is dropped once its turn starts.
9 */
10/** Producers clamp free text to these before publishing so encoding never fails. */
11export const WORKTREE_SETUP_DETAIL_MAX_LENGTH = 200;
12export const WORKTREE_SETUP_TAIL_LINE_MAX_LENGTH = 400;
13export const WORKTREE_SETUP_ERROR_MAX_LENGTH = 1000;
14
15export const WorktreeSetupStageId = Schema.Literals([
16 "fetch",
17 "checkout",
18 "submodules",
19 "setup-script",
20 "agent",
21]);
22export type WorktreeSetupStageId = typeof WorktreeSetupStageId.Type;
23
24export const WorktreeSetupStageStatus = Schema.Literals([
25 "pending",
26 "running",
27 "done",
28 "skipped",
29 "warning",
30 "failed",
31]);
32export type WorktreeSetupStageStatus = typeof WorktreeSetupStageStatus.Type;
33
34export const WorktreeSetupStage = Schema.Struct({
35 id: WorktreeSetupStageId,
36 status: WorktreeSetupStageStatus,
37 startedAt: Schema.NullOr(IsoDateTime),
38 endedAt: Schema.NullOr(IsoDateTime),
39 /** Only the checkout stage reports a real percentage, parsed from git's `Updating files` lines. */
40 percent: Schema.NullOr(Schema.Int.check(Schema.isBetween({ minimum: 0, maximum: 100 }))),
41 /** Short trailing text for the row: a file count, an exit code, a submodule name. */
42 detail: Schema.NullOr(Schema.String.check(Schema.isMaxLength(WORKTREE_SETUP_DETAIL_MAX_LENGTH))),
43 /** Last few output lines from the setup script, ANSI stripped, newest last. */
44 tail: Schema.Array(Schema.String.check(Schema.isMaxLength(WORKTREE_SETUP_TAIL_LINE_MAX_LENGTH))),
45});
46export type WorktreeSetupStage = typeof WorktreeSetupStage.Type;
47
48export const WorktreeSetupPhase = Schema.Literals(["running", "done", "failed", "cancelled"]);
49export type WorktreeSetupPhase = typeof WorktreeSetupPhase.Type;
50
51export const WorktreeSetupSnapshot = Schema.Struct({
52 threadId: ThreadId,
53 phase: WorktreeSetupPhase,
54 startedAt: IsoDateTime,
55 endedAt: Schema.NullOr(IsoDateTime),
56 branch: Schema.NullOr(TrimmedNonEmptyString),
57 baseRef: Schema.NullOr(TrimmedNonEmptyString),
58 worktreePath: Schema.NullOr(TrimmedNonEmptyString),
59 /** Display name plus command of the setup script from t3.json, when one runs. */
60 setupScript: Schema.NullOr(
61 Schema.Struct({
62 name: TrimmedNonEmptyString,
63 command: TrimmedNonEmptyString,
64 terminalId: TrimmedNonEmptyString,
65 }),
66 ),
67 stages: Schema.Array(WorktreeSetupStage),
68 /** Human readable reason when phase is failed. */
69 error: Schema.NullOr(Schema.String.check(Schema.isMaxLength(WORKTREE_SETUP_ERROR_MAX_LENGTH))),
70 sequence: NonNegativeInt,
71});
72export type WorktreeSetupSnapshot = typeof WorktreeSetupSnapshot.Type;
73
74/**
75 * Thread activity that carries a `WorktreeSetupSnapshot` as its payload. The
76 * bootstrap writes it under a fixed id once the thread exists (phase running)
77 * and again when the setup settles, so the projection always holds the
78 * latest known state: a client attaches the live stream while it says
79 * running and renders the outcome from it afterwards, on any device or
80 * after a reload.
81 */
82export const WORKTREE_SETUP_ACTIVITY_KIND = "worktree-setup";
83
84export const WorktreeSetupSubscribeInput = Schema.Struct({
85 threadId: ThreadId,
86});
87export type WorktreeSetupSubscribeInput = typeof WorktreeSetupSubscribeInput.Type;
88
89/** Null means no setup is tracked for that thread. Sent first, then after every change. */
90export const WorktreeSetupStreamEvent = Schema.NullOr(WorktreeSetupSnapshot);
91export type WorktreeSetupStreamEvent = typeof WorktreeSetupStreamEvent.Type;
92
93export const WorktreeSetupCancelInput = Schema.Struct({
94 threadId: ThreadId,
95});
96export type WorktreeSetupCancelInput = typeof WorktreeSetupCancelInput.Type;
97
98export const WorktreeSetupCancelResult = Schema.Struct({
99 cancelled: Schema.Boolean,
100});
101export type WorktreeSetupCancelResult = typeof WorktreeSetupCancelResult.Type;
102
103export const WORKTREE_SETUP_STAGE_ORDER: ReadonlyArray<WorktreeSetupStageId> = [
104 "fetch",
105 "checkout",
106 "submodules",
107 "setup-script",
108 "agent",
109];
110
111export function worktreeSetupStageLabel(id: WorktreeSetupStageId): string {
112 switch (id) {
113 case "fetch":
114 return "Fetch base branch";
115 case "checkout":
116 return "Check out files";
117 case "submodules":
118 return "Init submodules";
119 case "setup-script":
120 return "Run setup script";
121 case "agent":
122 return "Start agent";
123 }
124}