1import * as Effect from "effect/Effect";
2import * as Schema from "effect/Schema";
3
4import {
5 EnvironmentId,
6 ForwardCompatibleOptional,
7 ProjectId,
8 ThreadId,
9 TrimmedNonEmptyString,
10} from "./baseSchemas.ts";
11
12/** Wire version for orchestration snapshots, streams, commands, and RPC payloads. */
13export const ORCHESTRATION_PROTOCOL_VERSION = 2;
14export const ORCHESTRATION_PROTOCOL_VERSION_TEXT = "2";
15export const ORCHESTRATION_PROTOCOL_QUERY_PARAM = "orchestrationProtocol";
16export const ORCHESTRATION_PROTOCOL_HEADER = "x-t3-orchestration-protocol";
17
18export const ExecutionEnvironmentPlatformOs = Schema.Literals([
19 "darwin",
20 "linux",
21 "windows",
22 "unknown",
23]);
24export type ExecutionEnvironmentPlatformOs = typeof ExecutionEnvironmentPlatformOs.Type;
25
26export const ExecutionEnvironmentPlatformArch = Schema.Literals(["arm64", "x64", "other"]);
27export type ExecutionEnvironmentPlatformArch = typeof ExecutionEnvironmentPlatformArch.Type;
28
29/**
30 * The curated set of machine shapes and OS identities an environment can wear as its icon.
31 * Servers detect one from the hardware they run on (`platform.machine`), and
32 * the `environmentIcon` server setting lets a user pick one instead.
33 */
34export const ENVIRONMENT_MACHINE_KINDS = [
35 "server",
36 "cloud",
37 "linux",
38 "desktop",
39 "laptop",
40 "mac-mini",
41 "mac-studio",
42] as const;
43export const EnvironmentMachineKind = Schema.Literals(ENVIRONMENT_MACHINE_KINDS);
44export type EnvironmentMachineKind = typeof EnvironmentMachineKind.Type;
45export const isEnvironmentMachineKind = Schema.is(EnvironmentMachineKind);
46
47export const ExecutionEnvironmentPlatform = Schema.Struct({
48 os: ExecutionEnvironmentPlatformOs,
49 arch: ExecutionEnvironmentPlatformArch,
50 /** Hardware shape detected at startup. Absent when the host gives no usable
51 signal (containers, Windows, unknown DMI), on servers that predate it, or
52 when a newer server names a kind this build cannot draw. */
53 machine: ForwardCompatibleOptional(EnvironmentMachineKind),
54});
55
56/**
57 * Where a new thread runs: the project's current checkout ("local") or a
58 * fresh git worktree ("worktree"). Lives here (not settings.ts) so
59 * orchestration contracts can reference it without an import cycle.
60 */
61export const ThreadEnvMode = Schema.Literals(["local", "worktree"]);
62export type ThreadEnvMode = typeof ThreadEnvMode.Type;
63
64/**
65 * How a new worktree populates git submodules: every nested level, only the
66 * ones this repository declares, or not at all.
67 */
68export const WorktreeSubmodules = Schema.Literals(["recursive", "top-level", "none"]);
69export type WorktreeSubmodules = typeof WorktreeSubmodules.Type;
70export type ExecutionEnvironmentPlatform = typeof ExecutionEnvironmentPlatform.Type;
71
72/** How a server can replace itself with another version when asked over RPC.
73 New servers only advertise the stable launcher-backed "boot-service" path;
74 "respawn" remains decodable for compatibility with older servers.
75 "desktop-app" means the supervising desktop app updated and relaunched
76 itself, bringing the server back with it. */
77export const ServerSelfUpdateMethod = Schema.Literals(["boot-service", "respawn", "desktop-app"]);
78export type ServerSelfUpdateMethod = typeof ServerSelfUpdateMethod.Type;
79
80/** Proven ownership for a manual update; unknown installs omit this descriptor. */
81export const ServerInstallation = Schema.Union([
82 Schema.Struct({ kind: Schema.Literals(["npx", "pnpm-dlx", "bunx"]) }),
83 Schema.Struct({ kind: Schema.Literal("npm-global"), prefix: TrimmedNonEmptyString }),
84]);
85export type ServerInstallation = typeof ServerInstallation.Type;
86
87/** What update path a client should offer for a server: one of the RPC
88 self-update methods above, or "desktop-managed" when the backend's
89 version belongs to the T3 Code desktop app supervising it — updating the
90 app on that machine is the only way to update the server. */
91export const ServerSelfUpdateCapability = Schema.Literals([
92 "boot-service",
93 "respawn",
94 "desktop-managed",
95]);
96export type ServerSelfUpdateCapability = typeof ServerSelfUpdateCapability.Type;
97
98export const ExecutionEnvironmentCapabilities = Schema.Struct({
99 uiSettingsSync: Schema.optionalKey(Schema.Boolean),
100 repositoryIdentity: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
101 connectionProbe: Schema.optionalKey(Schema.Boolean),
102 /** Missing on older servers, which still accept inline image attachments. */
103 attachmentUploads: Schema.optionalKey(Schema.Boolean),
104 /** Uploaded files may accompany question answers. */
105 questionAttachments: Schema.optionalKey(Schema.Boolean),
106 /** Missing on servers that only accept image attachments. */
107 fileAttachments: Schema.optionalKey(
108 Schema.Struct({
109 maxUploadBytes: Schema.Int.check(Schema.isGreaterThanOrEqualTo(1)),
110 }),
111 ),
112 /** Server exposes the pull-request list, detail, activity, diff, and mutation APIs. Absent on
113 servers from before the pull-request workspace shipped, so clients must not probe them. */
114 pullRequests: Schema.optionalKey(Schema.Boolean),
115 pullRequestChecks: Schema.optionalKey(Schema.Boolean),
116 /** Server understands canonical inline context links plus their message context records.
117 Absent on servers from before inline context shipped, which drop the records and forward
118 the links as literal text -- so a client must serialize context the legacy way for them. */
119 inlineMessageContext: Schema.optionalKey(Schema.Boolean),
120 /** Server rejects required worktrees instead of falling back to the project checkout. */
121 requiredWorktreeBootstrap: Schema.optionalKey(Schema.Boolean),
122 /** Server understands thread.settle / thread.unsettle commands. Absent on
123 pre-settlement servers, so clients treat missing as unsupported and
124 never send the commands under version skew. */
125 threadSettlement: Schema.optionalKey(Schema.Boolean),
126 /** Server evaluates merge and inactivity settlement without a client. */
127 threadAutoSettlement: Schema.optionalKey(Schema.Boolean),
128 storageCleanup: Schema.optionalKey(Schema.Boolean),
129 storageCleanupRun: Schema.optionalKey(Schema.Boolean),
130 /** Server checks OTLP endpoints for the telemetry export settings. */
131 otlpEndpointCheck: Schema.optionalKey(Schema.Boolean),
132 projectWorktreeCleanup: Schema.optionalKey(Schema.Boolean),
133 settledWorktreeCleanup: Schema.optionalKey(Schema.Boolean),
134 /** Server honors the `worktreesDirectory` setting. */
135 worktreesDirectory: Schema.optionalKey(Schema.Boolean),
136 /** Server can exclude PR-linked threads from inactivity settlement. */
137 threadAutoSettlementScope: Schema.optionalKey(Schema.Boolean),
138 /** Server persists the opt-in for continuing interrupted threads after restarts. */
139 threadRestartContinuation: Schema.optionalKey(Schema.Boolean),
140 /** Server resolves `projectSettingsOverrides`; older servers ignore the key. */
141 projectSettingsOverrides: Schema.optionalKey(Schema.Boolean),
142 /** Server resolves a project's `providerInstances` override entry. Older
143 servers decode the key away (unknown fields are stripped, not
144 rejected), so the per-project provider switch would silently do
145 nothing; clients hide it instead of sending a patch that goes nowhere. */
146 projectProviderInstanceOverrides: Schema.optionalKey(Schema.Boolean),
147 /** Server understands thread.snooze / thread.unsnooze commands. Same
148 version-skew contract as threadSettlement. */
149 threadSnooze: Schema.optionalKey(Schema.Boolean),
150 /** Server streams themes an environment publishes. Absent on servers from
151 before environment themes shipped, which never emit the events -- so a
152 client reconnecting to one must drop published themes rather than keep
153 showing a set nothing will ever update. */
154 environmentThemes: Schema.optionalKey(Schema.Boolean),
155 /** Server streams quota from configured usage-limit sources. Same
156 version-skew contract as environmentThemes. */
157 usageLimitSources: Schema.optionalKey(Schema.Boolean),
158 /** Server persists custom model rates and applies them to usage summaries. */
159 usagePriceOverrides: Schema.optionalKey(Schema.Boolean),
160 /** Server persists model mappings and folds mapped usage into the target model. */
161 usageModelAliases: Schema.optionalKey(Schema.Boolean),
162 /** Server understands thread.pin / thread.unpin commands. Same
163 version-skew contract as threadSettlement. */
164 threadPinning: Schema.optionalKey(Schema.Boolean),
165 /** Server understands thread.pin.reorder (and orderKey on thread.pin).
166 Same version-skew contract as threadSettlement. */
167 threadPinReorder: Schema.optionalKey(Schema.Boolean),
168 /** Server persists manual Active order through thread.active.reorder. */
169 threadActiveReorder: Schema.optionalKey(Schema.Boolean),
170 /** Server understands thread.auto-settle.set (per-thread auto-settle off).
171 Same version-skew contract as threadSettlement. */
172 threadAutoSettleOptOut: Schema.optionalKey(Schema.Boolean),
173 /** Server understands regenerateTitle on thread.meta.update. Absent on
174 older servers, so clients hide the action instead of sending it. */
175 threadTitleRegeneration: Schema.optionalKey(Schema.Boolean),
176 /** Server understands thread.visit / thread.mark-unread commands and
177 projects lastVisitedAt on thread shells. Same version-skew contract as
178 threadSettlement: clients keep their local visited state against
179 servers that lack this. */
180 threadVisitedTracking: Schema.optionalKey(Schema.Boolean),
181 /** Server persists a pull request reference on thread.meta.update. */
182 threadPullRequestLinking: Schema.optionalKey(Schema.Boolean),
183 /** Server resolves message delivery and model-selection context and validates
184 identified rollback readiness. Clients retain projection-based command
185 shaping and validation when this is absent. */
186 serverResolvedCommandContext: Schema.optionalKey(Schema.Boolean),
187 threadPullRequests: Schema.optionalKey(Schema.Boolean),
188 /** Server understands thread.pull-request.watch and wakes agents on pull request changes. */
189 threadPullRequestWatch: Schema.optionalKey(Schema.Boolean),
190 pullRequestStackActions: Schema.optionalKey(Schema.Boolean),
191 /** The update path clients should offer for this server. Absent on
192 servers that must be relaunched manually (dev checkouts, Windows
193 foreground runs, pre-update servers). */
194 serverSelfUpdate: Schema.optionalKey(ServerSelfUpdateCapability),
195 /** Manual commands must update this install, not the host's default global prefix. */
196 serverInstallation: ForwardCompatibleOptional(ServerInstallation),
197 /** Server can stream self-update progress before acknowledging the
198 restart. Clients fall back to server.updateServer when absent. */
199 serverSelfUpdateProgress: Schema.optionalKey(Schema.Boolean),
200 /** Server can durably mark running provider turns before a self-update and
201 continue them after the replacement process starts. */
202 serverUpdateThreadContinuation: Schema.optionalKey(Schema.Boolean),
203 /** Agent-activity publishes (push notifications and Live Activities)
204 currently leave this environment: the publish opt-in is enabled and the
205 relay link credentials exist. Clients skip seeding a Live Activity when
206 this is false — no update would ever repaint it. Absent on older
207 servers, which may still publish, so only an explicit false skips. */
208 agentActivityPublishing: Schema.optionalKey(Schema.Boolean),
209 /** Server runs repository clones for new projects in the background and
210 streams their progress (`projectClone.*`, `subscribeProjectClones`).
211 Absent on older servers, where clients must clone with the blocking
212 `sourceControl.cloneRepository` call instead. */
213 projectCloneTracking: Schema.optionalKey(Schema.Boolean),
214 /** Server detects `platform.machine` and persists the `environmentIcon`
215 setting. Older servers drop the key on write, so clients show the
216 picker inert rather than offering a choice that would never stick. */
217 environmentIcon: Schema.optionalKey(Schema.Boolean),
218 /** The desktop app supervising this server can be driven over RPC:
219 server.updateServer runs its check -> download -> relaunch. Absent on
220 desktop servers whose app predates the remote trigger, where clients
221 must keep telling the user to update the app on that machine. */
222 desktopAppUpdate: Schema.optionalKey(Schema.Boolean),
223 /** Server hosts preview tabs in its own headless Chromium (`runtime:
224 "server"`) and streams them over `/api/preview-stream`. Clients
225 without a local browser runtime open server tabs here. */
226 serverBrowser: Schema.optionalKey(Schema.Boolean),
227});
228export type ExecutionEnvironmentCapabilities = typeof ExecutionEnvironmentCapabilities.Type;
229
230export const ExecutionEnvironmentDescriptor = Schema.Struct({
231 environmentId: EnvironmentId,
232 label: TrimmedNonEmptyString,
233 platform: ExecutionEnvironmentPlatform,
234 serverVersion: TrimmedNonEmptyString,
235 /** Absent on hosts from before explicit orchestration protocol negotiation. */
236 orchestrationProtocolVersion: Schema.optionalKey(Schema.Int),
237 capabilities: ExecutionEnvironmentCapabilities,
238});
239export type ExecutionEnvironmentDescriptor = typeof ExecutionEnvironmentDescriptor.Type;
240
241export const RepositoryIdentityLocator = Schema.Struct({
242 source: Schema.Literal("git-remote"),
243 remoteName: TrimmedNonEmptyString,
244 remoteUrl: TrimmedNonEmptyString,
245});
246export type RepositoryIdentityLocator = typeof RepositoryIdentityLocator.Type;
247
248/**
249 * The checkout's own remote when it names a different repository than the canonical one, such as
250 * a fork that tracks its upstream. Clients group and label by it so a fork stays distinct from the
251 * repository it forked, while pull request features keep the canonical identity.
252 */
253export const RepositoryOrigin = Schema.Struct({
254 canonicalKey: TrimmedNonEmptyString,
255 displayName: Schema.optionalKey(TrimmedNonEmptyString),
256});
257export type RepositoryOrigin = typeof RepositoryOrigin.Type;
258
259export const RepositoryIdentity = Schema.Struct({
260 canonicalKey: TrimmedNonEmptyString,
261 locator: RepositoryIdentityLocator,
262 /** Repository browser URL resolved from the server's configured hosting account. */
263 webUrl: Schema.optionalKey(TrimmedNonEmptyString),
264 rootPath: Schema.optionalKey(TrimmedNonEmptyString),
265 displayName: Schema.optionalKey(TrimmedNonEmptyString),
266 provider: Schema.optionalKey(TrimmedNonEmptyString),
267 owner: Schema.optionalKey(TrimmedNonEmptyString),
268 name: Schema.optionalKey(TrimmedNonEmptyString),
269 origin: Schema.optionalKey(RepositoryOrigin),
270});
271export type RepositoryIdentity = typeof RepositoryIdentity.Type;
272
273/** Key clients group checkouts by: a fork's own remote, otherwise the canonical repository. */
274export function repositoryGroupingKeyOf(identity: RepositoryIdentity): string {
275 return identity.origin?.canonicalKey ?? identity.canonicalKey;
276}
277
278/** Label clients show for a checkout's repository, matching `repositoryGroupingKeyOf`. */
279export function repositoryGroupingDisplayNameOf(identity: RepositoryIdentity): string | undefined {
280 return identity.origin
281 ? (identity.origin.displayName ?? identity.origin.canonicalKey)
282 : identity.displayName;
283}
284
285export const ScopedProjectRef = Schema.Struct({
286 environmentId: EnvironmentId,
287 projectId: ProjectId,
288});
289export type ScopedProjectRef = typeof ScopedProjectRef.Type;
290
291export const ScopedThreadRef = Schema.Struct({
292 environmentId: EnvironmentId,
293 threadId: ThreadId,
294});
295export type ScopedThreadRef = typeof ScopedThreadRef.Type;