1import * as Effect from "effect/Effect";
2import * as Schema from "effect/Schema";
3import { AcpRegistryUrlAuthAction } from "./acpRegistry.ts";
4import {
5 type EnvironmentMachineKind,
6 ExecutionEnvironmentDescriptor,
7 ServerSelfUpdateMethod,
8} from "./environment.ts";
9import { ServerAuthDescriptor } from "./auth.ts";
10import {
11 ForwardCompatibleArray,
12 IsoDateTime,
13 NonNegativeInt,
14 PositiveInt,
15 ProjectId,
16 ThreadId,
17 TrimmedNonEmptyString,
18} from "./baseSchemas.ts";
19import {
20 KeybindingCommand,
21 KeybindingValue,
22 KeybindingWhen,
23 ResolvedKeybindingsConfig,
24} from "./keybindings.ts";
25import { EditorId, FileManagerRevealKind, RemoteOpenTarget } from "./editor.ts";
26import { ModelCapabilities } from "./model.ts";
27import { RuntimeMode } from "./providerPolicy.ts";
28import { ProviderDriverKind, ProviderInstanceId } from "./providerInstance.ts";
29import { ServerProviderUsageLimits, UsageLimitSourceSnapshots } from "./providerUsageLimits.ts";
30import { ServerSettings } from "./settings.ts";
31import { MergebotConfiguration } from "./mergebot.ts";
32
33const KeybindingsMalformedConfigIssue = Schema.Struct({
34 kind: Schema.Literal("keybindings.malformed-config"),
35 message: TrimmedNonEmptyString,
36});
37
38const KeybindingsInvalidEntryIssue = Schema.Struct({
39 kind: Schema.Literal("keybindings.invalid-entry"),
40 message: TrimmedNonEmptyString,
41 index: Schema.Number,
42});
43
44export const ServerConfigIssue = Schema.Union([
45 KeybindingsMalformedConfigIssue,
46 KeybindingsInvalidEntryIssue,
47]);
48export type ServerConfigIssue = typeof ServerConfigIssue.Type;
49
50// Issue kinds grow over time; older clients must not fail the whole config
51// decode over a kind they cannot render.
52const ServerConfigIssues = ForwardCompatibleArray(ServerConfigIssue);
53
54export const ServerProviderState = Schema.Literals(["ready", "warning", "error", "disabled"]);
55export type ServerProviderState = typeof ServerProviderState.Type;
56
57export const ServerProviderAuthStatus = Schema.Literals([
58 "authenticated",
59 "unauthenticated",
60 "unknown",
61]);
62export type ServerProviderAuthStatus = typeof ServerProviderAuthStatus.Type;
63
64export const ServerProviderAuth = Schema.Struct({
65 status: ServerProviderAuthStatus,
66 type: Schema.optional(TrimmedNonEmptyString),
67 label: Schema.optional(TrimmedNonEmptyString),
68 email: Schema.optional(TrimmedNonEmptyString),
69 action: Schema.optional(AcpRegistryUrlAuthAction),
70 canLogout: Schema.optional(Schema.Boolean),
71 subscriptionSharing: Schema.optional(Schema.Boolean),
72 profileId: Schema.optional(TrimmedNonEmptyString),
73 /**
74 * The workspace or organization whose quota the login draws on, such as a
75 * ChatGPT workspace or Claude organization id. One email can belong to
76 * several, each with its own quota.
77 */
78 workspaceId: Schema.optional(TrimmedNonEmptyString),
79});
80export type ServerProviderAuth = typeof ServerProviderAuth.Type;
81
82export const ServerProviderModel = Schema.Struct({
83 slug: TrimmedNonEmptyString,
84 name: TrimmedNonEmptyString,
85 shortName: Schema.optional(TrimmedNonEmptyString),
86 subProvider: Schema.optional(TrimmedNonEmptyString),
87 aliases: Schema.optional(Schema.Array(TrimmedNonEmptyString)),
88 badge: Schema.optional(Schema.Literal("new")),
89 isCustom: Schema.Boolean,
90 isDefault: Schema.optional(Schema.Boolean),
91 isLegacy: Schema.optional(Schema.Boolean),
92 capabilities: Schema.NullOr(ModelCapabilities),
93});
94export type ServerProviderModel = typeof ServerProviderModel.Type;
95
96/**
97 * A model the model manifest announces that the installed provider version is
98 * too old to run. It is never selectable; clients show it so users learn that
99 * updating the provider unlocks it.
100 */
101export const ServerProviderUpdateRequiredModel = Schema.Struct({
102 slug: TrimmedNonEmptyString,
103 name: TrimmedNonEmptyString,
104 badge: Schema.optional(Schema.Literal("new")),
105 minVersion: TrimmedNonEmptyString,
106});
107export type ServerProviderUpdateRequiredModel = typeof ServerProviderUpdateRequiredModel.Type;
108
109export const ServerProviderSlashCommandInput = Schema.Struct({
110 hint: TrimmedNonEmptyString,
111});
112export type ServerProviderSlashCommandInput = typeof ServerProviderSlashCommandInput.Type;
113
114export const ServerProviderSlashCommand = Schema.Struct({
115 name: TrimmedNonEmptyString,
116 description: Schema.optional(TrimmedNonEmptyString),
117 input: Schema.optional(ServerProviderSlashCommandInput),
118});
119export type ServerProviderSlashCommand = typeof ServerProviderSlashCommand.Type;
120
121export const ServerProviderSkill = Schema.Struct({
122 name: TrimmedNonEmptyString,
123 description: Schema.optional(TrimmedNonEmptyString),
124 path: TrimmedNonEmptyString,
125 scope: Schema.optional(TrimmedNonEmptyString),
126 enabled: Schema.Boolean,
127 displayName: Schema.optional(TrimmedNonEmptyString),
128 shortDescription: Schema.optional(TrimmedNonEmptyString),
129 /**
130 * The skill is hidden from the agent's own skill tool, so only the user can
131 * start it — Claude Code's `disable-model-invocation`. Composers must offer
132 * it as a slash command; naming it in prose does nothing.
133 */
134 userInvocationOnly: Schema.optional(Schema.Boolean),
135 /**
136 * The mirror of {@link ServerProviderSkill.userInvocationOnly}: Claude Code's
137 * `user-invocable: false` keeps the skill out of its own slash commands, so
138 * only the agent can start it. Composers must not offer it under `/`.
139 */
140 userInvocable: Schema.optional(Schema.Boolean),
141});
142export type ServerProviderSkill = typeof ServerProviderSkill.Type;
143
144export const ServerProviderWorkspaceSnapshot = Schema.Struct({
145 cwd: TrimmedNonEmptyString,
146 checkedAt: IsoDateTime,
147 slashCommands: Schema.Array(ServerProviderSlashCommand),
148 /** Skills are available, but command discovery still needs a retry. */
149 slashCommandsPending: Schema.optional(Schema.Boolean),
150 skills: Schema.Array(ServerProviderSkill),
151});
152export type ServerProviderWorkspaceSnapshot = typeof ServerProviderWorkspaceSnapshot.Type;
153
154/**
155 * How long a workspace's skill and command scan stays current. Nothing watches
156 * skill directories, so a composer opened after this rescans on use, and the
157 * server answers repeat requests inside the window from its cache.
158 */
159export const PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS = 5 * 60_000;
160
161export function isProviderWorkspaceSnapshotCurrent(
162 snapshot: Pick<ServerProviderWorkspaceSnapshot, "checkedAt">,
163 nowMs: number,
164): boolean {
165 return nowMs - Date.parse(snapshot.checkedAt) < PROVIDER_WORKSPACE_SNAPSHOT_TTL_MS;
166}
167
168/**
169 * Availability of a configured provider instance from the runtime's POV.
170 *
171 * - `available` — the build ships this driver and an instance is wired
172 * up. Default for legacy snapshots produced from the closed
173 * `ServerSettings.providers` map.
174 * - `unavailable` — the user's `ServerSettings.providerInstances` (or a
175 * persisted thread / session binding) references a driver this build
176 * doesn't ship. Common after rolling back from a fork or PR branch
177 * that introduced a new driver. The snapshot is preserved so the UI
178 * can render "missing driver" affordances and so the data round-trips
179 * when the user moves back to the fork.
180 *
181 * Snapshots with `availability: "unavailable"` MUST set
182 * `installed: false` and `enabled: false`; the runtime refuses turn
183 * starts against them with a structured error.
184 */
185export const ServerProviderAvailability = Schema.Literals(["available", "unavailable"]);
186export type ServerProviderAvailability = typeof ServerProviderAvailability.Type;
187
188export const ServerProviderContinuation = Schema.Struct({
189 groupKey: TrimmedNonEmptyString,
190});
191export type ServerProviderContinuation = typeof ServerProviderContinuation.Type;
192
193export const ServerProviderCompatibilityStatus = Schema.Literals([
194 "unknown",
195 "supported",
196 "graceful",
197 "unsupported",
198 "broken",
199]);
200export const ServerProviderCompatibilityAdvisory = Schema.Struct({
201 status: ServerProviderCompatibilityStatus,
202 latestVersionStatus: Schema.optionalKey(ServerProviderCompatibilityStatus),
203 message: Schema.NullOr(TrimmedNonEmptyString),
204 recommendedVersion: Schema.NullOr(TrimmedNonEmptyString),
205 recommendedRange: Schema.NullOr(TrimmedNonEmptyString),
206});
207export type ServerProviderCompatibilityAdvisory = typeof ServerProviderCompatibilityAdvisory.Type;
208
209export const ServerProviderVersionAdvisoryStatus = Schema.Literals([
210 "unknown",
211 "current",
212 "behind_latest",
213]);
214export type ServerProviderVersionAdvisoryStatus = typeof ServerProviderVersionAdvisoryStatus.Type;
215
216export const ServerProviderVersionAdvisory = Schema.Struct({
217 status: ServerProviderVersionAdvisoryStatus,
218 currentVersion: Schema.NullOr(TrimmedNonEmptyString),
219 latestVersion: Schema.NullOr(TrimmedNonEmptyString),
220 updateCommand: Schema.NullOr(TrimmedNonEmptyString),
221 canUpdate: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
222 canInstallVersion: Schema.optionalKey(Schema.Boolean),
223 checkedAt: Schema.NullOr(IsoDateTime),
224 message: Schema.NullOr(TrimmedNonEmptyString),
225});
226export type ServerProviderVersionAdvisory = typeof ServerProviderVersionAdvisory.Type;
227
228export const ServerProviderUpdateStatus = Schema.Literals([
229 "idle",
230 "queued",
231 "running",
232 "succeeded",
233 "failed",
234 "unchanged",
235]);
236export type ServerProviderUpdateStatus = typeof ServerProviderUpdateStatus.Type;
237
238export const ServerProviderUpdateState = Schema.Struct({
239 status: ServerProviderUpdateStatus,
240 startedAt: Schema.NullOr(IsoDateTime),
241 finishedAt: Schema.NullOr(IsoDateTime),
242 message: Schema.NullOr(TrimmedNonEmptyString),
243 output: Schema.NullOr(Schema.String.check(Schema.isMaxLength(10_000))),
244});
245export type ServerProviderUpdateState = typeof ServerProviderUpdateState.Type;
246
247export const ServerProvider = Schema.Struct({
248 // Routing key for the configured instance this snapshot represents. This
249 // is the only stable identity consumers may use for provider routing.
250 instanceId: ProviderInstanceId,
251 // Open driver kind slug that selects the implementation handling this
252 // instance. It is metadata/capability context, not a routing key.
253 driver: ProviderDriverKind,
254 displayName: Schema.optional(TrimmedNonEmptyString),
255 accentColor: Schema.optional(TrimmedNonEmptyString),
256 // Optional visual identity supplied by the owning provider driver. Clients
257 // must still validate remote URLs against that driver's trusted origin.
258 iconUrl: Schema.optional(TrimmedNonEmptyString.check(Schema.isMaxLength(2_048))),
259 badgeLabel: Schema.optional(TrimmedNonEmptyString),
260 continuation: Schema.optional(ServerProviderContinuation),
261 showInteractionModeToggle: Schema.optional(Schema.Boolean),
262 // The driver streams context window usage, so a started thread will have a
263 // meter once its activities load. Clients reserve the meter's space on it.
264 reportsContextWindow: Schema.optional(Schema.Boolean),
265 supportedRuntimeModes: Schema.optional(ForwardCompatibleArray(RuntimeMode)),
266 requiresNewThreadForModelChange: Schema.optional(Schema.Boolean),
267 supportsConversationRollback: Schema.optional(Schema.Boolean),
268 supportsTextGeneration: Schema.optional(Schema.Boolean),
269 setup: Schema.optional(
270 Schema.Struct({
271 canAuthenticate: Schema.Boolean,
272 canInstall: Schema.Boolean,
273 documentationUrl: Schema.optionalKey(TrimmedNonEmptyString.check(Schema.isMaxLength(2_048))),
274 }),
275 ),
276 nativeSessions: Schema.optional(
277 Schema.Struct({
278 canList: Schema.Boolean,
279 canLoad: Schema.Boolean,
280 canResume: Schema.Boolean,
281 canDelete: Schema.optional(Schema.Boolean),
282 }),
283 ),
284 configurableProviders: Schema.optional(Schema.Boolean),
285 runtimePaths: Schema.optionalKey(
286 Schema.Struct({
287 homePath: TrimmedNonEmptyString,
288 shadowHomePath: Schema.NullOr(TrimmedNonEmptyString),
289 }),
290 ),
291 enabled: Schema.Boolean,
292 installed: Schema.Boolean,
293 version: Schema.NullOr(TrimmedNonEmptyString),
294 status: ServerProviderState,
295 auth: ServerProviderAuth,
296 checkedAt: IsoDateTime,
297 message: Schema.optional(TrimmedNonEmptyString),
298 // Optional for back-compat: every legacy producer omits this field and
299 // an absent value is interpreted as `"available"` by consumers (see
300 // `isProviderAvailable`). New `ProviderInstanceRegistry` outputs set it
301 // explicitly so the UI can render unavailable shadows from
302 // `ServerSettings.providerInstances`.
303 availability: Schema.optional(ServerProviderAvailability),
304 // Human-readable reason populated when `availability === "unavailable"`.
305 // Surfaces in the UI alongside the missing-driver affordance.
306 unavailableReason: Schema.optional(TrimmedNonEmptyString),
307 models: Schema.Array(ServerProviderModel),
308 // Kept apart from `models` so clients that predate it never offer them.
309 updateRequiredModels: Schema.optionalKey(Schema.Array(ServerProviderUpdateRequiredModel)),
310 slashCommands: Schema.Array(ServerProviderSlashCommand).pipe(
311 Schema.withDecodingDefault(Effect.succeed([])),
312 ),
313 skills: Schema.Array(ServerProviderSkill).pipe(Schema.withDecodingDefault(Effect.succeed([]))),
314 workspaceSnapshots: Schema.optionalKey(Schema.Array(ServerProviderWorkspaceSnapshot)),
315 // Absent when the driver has no notion of subscription usage.
316 usageLimits: Schema.optional(ServerProviderUsageLimits),
317 versionAdvisory: Schema.optionalKey(ServerProviderVersionAdvisory),
318 compatibilityAdvisory: Schema.optionalKey(ServerProviderCompatibilityAdvisory),
319 updateState: Schema.optionalKey(ServerProviderUpdateState),
320});
321export type ServerProvider = typeof ServerProvider.Type;
322
323// Provider status kinds grow over time (ServerProviderState,
324// ServerProviderAuthStatus, ServerProviderVersionAdvisoryStatus,
325// ServerProviderUpdateStatus); an older client must not fail the whole config
326// decode over one provider it cannot render.
327export const ServerProviders = ForwardCompatibleArray(ServerProvider);
328export type ServerProviders = typeof ServerProviders.Type;
329
330/**
331 * Treat the optional `availability` as "available" when absent. This is
332 * the rule legacy producers (which omit the field) and new producers
333 * (which set it explicitly) agree on so consumers never have to thread
334 * `?? "available"` defaults through their code paths.
335 */
336export const isProviderAvailable = (snapshot: ServerProvider): boolean =>
337 snapshot.availability !== "unavailable";
338
339/**
340 * Treat an absent `supportsTextGeneration` as supported so legacy
341 * producers, which all support application text generation, keep working
342 * without resending the field.
343 */
344export const isProviderTextGenerationCapable = (snapshot: ServerProvider): boolean =>
345 snapshot.supportsTextGeneration !== false;
346
347export const ServerObservability = Schema.Struct({
348 logsDirectoryPath: TrimmedNonEmptyString,
349 localTracingEnabled: Schema.Boolean,
350 otlpTracesUrl: Schema.optional(TrimmedNonEmptyString),
351 otlpTracesEnabled: Schema.Boolean,
352 otlpMetricsUrl: Schema.optional(TrimmedNonEmptyString),
353 otlpMetricsEnabled: Schema.Boolean,
354 otlpLogsUrl: Schema.optional(TrimmedNonEmptyString),
355 // Absent on servers from before the log signal shipped, so a newer client
356 // reads those as having no log export rather than rejecting the whole config.
357 otlpLogsEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
358});
359export type ServerObservability = typeof ServerObservability.Type;
360
361export const OtlpSignal = Schema.Literals(["traces", "metrics", "logs"]);
362export type OtlpSignal = typeof OtlpSignal.Type;
363
364export const OtlpEndpointCheckInput = Schema.Struct({
365 signal: OtlpSignal,
366 // Receivers are HTTP only; other schemes, such as data:, answer without contacting one.
367 url: TrimmedNonEmptyString.check(Schema.isPattern(/^https?:\/\/./i)),
368});
369export type OtlpEndpointCheckInput = typeof OtlpEndpointCheckInput.Type;
370
371/**
372 * What the server saw when it sent an empty export to an endpoint: accepted,
373 * answered with an error status, or never answered.
374 */
375export const OtlpEndpointCheckResult = Schema.Union([
376 Schema.TaggedStruct("Accepted", { latencyMs: Schema.Number }),
377 Schema.TaggedStruct("Rejected", { status: Schema.Number }),
378 Schema.TaggedStruct("Unreachable", { timedOut: Schema.Boolean }),
379]);
380export type OtlpEndpointCheckResult = typeof OtlpEndpointCheckResult.Type;
381
382export const ServerTraceDiagnosticsErrorKind = Schema.Literals([
383 "trace-file-not-found",
384 "trace-file-read-failed",
385]);
386export type ServerTraceDiagnosticsErrorKind = typeof ServerTraceDiagnosticsErrorKind.Type;
387
388export const ServerTraceDiagnosticsSpanSummary = Schema.Struct({
389 name: TrimmedNonEmptyString,
390 count: NonNegativeInt,
391 failureCount: NonNegativeInt,
392 totalDurationMs: Schema.Number,
393 averageDurationMs: Schema.Number,
394 maxDurationMs: Schema.Number,
395});
396export type ServerTraceDiagnosticsSpanSummary = typeof ServerTraceDiagnosticsSpanSummary.Type;
397
398export const ServerTraceDiagnosticsFailureSummary = Schema.Struct({
399 name: TrimmedNonEmptyString,
400 cause: TrimmedNonEmptyString,
401 count: NonNegativeInt,
402 lastSeenAt: Schema.DateTimeUtc,
403 traceId: TrimmedNonEmptyString,
404 spanId: TrimmedNonEmptyString,
405});
406export type ServerTraceDiagnosticsFailureSummary = typeof ServerTraceDiagnosticsFailureSummary.Type;
407
408export const ServerTraceDiagnosticsRecentFailure = Schema.Struct({
409 name: TrimmedNonEmptyString,
410 cause: TrimmedNonEmptyString,
411 durationMs: Schema.Number,
412 endedAt: Schema.DateTimeUtc,
413 traceId: TrimmedNonEmptyString,
414 spanId: TrimmedNonEmptyString,
415});
416export type ServerTraceDiagnosticsRecentFailure = typeof ServerTraceDiagnosticsRecentFailure.Type;
417
418export const ServerTraceDiagnosticsSpanOccurrence = Schema.Struct({
419 name: TrimmedNonEmptyString,
420 durationMs: Schema.Number,
421 endedAt: Schema.DateTimeUtc,
422 traceId: TrimmedNonEmptyString,
423 spanId: TrimmedNonEmptyString,
424});
425export type ServerTraceDiagnosticsSpanOccurrence = typeof ServerTraceDiagnosticsSpanOccurrence.Type;
426
427export const ServerTraceDiagnosticsLogEvent = Schema.Struct({
428 spanName: TrimmedNonEmptyString,
429 level: TrimmedNonEmptyString,
430 message: TrimmedNonEmptyString,
431 seenAt: Schema.DateTimeUtc,
432 traceId: TrimmedNonEmptyString,
433 spanId: TrimmedNonEmptyString,
434});
435export type ServerTraceDiagnosticsLogEvent = typeof ServerTraceDiagnosticsLogEvent.Type;
436
437export const ServerTraceDiagnosticsResult = Schema.Struct({
438 traceFilePath: TrimmedNonEmptyString,
439 scannedFilePaths: Schema.Array(TrimmedNonEmptyString),
440 readAt: Schema.DateTimeUtc,
441 recordCount: NonNegativeInt,
442 parseErrorCount: NonNegativeInt,
443 firstSpanAt: Schema.Option(Schema.DateTimeUtc),
444 lastSpanAt: Schema.Option(Schema.DateTimeUtc),
445 failureCount: NonNegativeInt,
446 interruptionCount: NonNegativeInt,
447 slowSpanThresholdMs: NonNegativeInt,
448 slowSpanCount: NonNegativeInt,
449 logLevelCounts: Schema.Record(TrimmedNonEmptyString, NonNegativeInt),
450 topSpansByCount: Schema.Array(ServerTraceDiagnosticsSpanSummary),
451 slowestSpans: Schema.Array(ServerTraceDiagnosticsSpanOccurrence),
452 commonFailures: Schema.Array(ServerTraceDiagnosticsFailureSummary),
453 latestFailures: Schema.Array(ServerTraceDiagnosticsRecentFailure),
454 latestWarningAndErrorLogs: Schema.Array(ServerTraceDiagnosticsLogEvent),
455 partialFailure: Schema.Option(Schema.Boolean),
456 error: Schema.Option(
457 Schema.Struct({
458 kind: ServerTraceDiagnosticsErrorKind,
459 message: TrimmedNonEmptyString,
460 }),
461 ),
462});
463export type ServerTraceDiagnosticsResult = typeof ServerTraceDiagnosticsResult.Type;
464
465export const ServerProcessSignal = Schema.Literals(["SIGINT", "SIGKILL"]);
466export type ServerProcessSignal = typeof ServerProcessSignal.Type;
467
468export const ServerProcessDiagnosticsEntry = Schema.Struct({
469 pid: PositiveInt,
470 startTimeMs: NonNegativeInt,
471 ppid: NonNegativeInt,
472 pgid: Schema.Option(Schema.Int),
473 status: TrimmedNonEmptyString,
474 cpuPercent: Schema.Number,
475 rssBytes: NonNegativeInt,
476 elapsed: TrimmedNonEmptyString,
477 command: TrimmedNonEmptyString,
478 depth: NonNegativeInt,
479 childPids: Schema.Array(PositiveInt),
480});
481export type ServerProcessDiagnosticsEntry = typeof ServerProcessDiagnosticsEntry.Type;
482
483export const ServerProcessDiagnosticsResult = Schema.Struct({
484 serverPid: PositiveInt,
485 readAt: Schema.DateTimeUtc,
486 processCount: NonNegativeInt,
487 totalRssBytes: NonNegativeInt,
488 totalCpuPercent: Schema.Number,
489 processes: Schema.Array(ServerProcessDiagnosticsEntry),
490 error: Schema.Option(
491 Schema.Struct({
492 message: TrimmedNonEmptyString,
493 }),
494 ),
495});
496export type ServerProcessDiagnosticsResult = typeof ServerProcessDiagnosticsResult.Type;
497
498export const ServerProcessResourceHistoryInput = Schema.Struct({
499 windowMs: NonNegativeInt,
500 bucketMs: NonNegativeInt,
501});
502export type ServerProcessResourceHistoryInput = typeof ServerProcessResourceHistoryInput.Type;
503
504export const ServerProcessResourceHistoryBucket = Schema.Struct({
505 startedAt: Schema.DateTimeUtc,
506 endedAt: Schema.DateTimeUtc,
507 avgCpuPercent: Schema.Number,
508 maxCpuPercent: Schema.Number,
509 maxRssBytes: NonNegativeInt,
510 maxProcessCount: NonNegativeInt,
511});
512export type ServerProcessResourceHistoryBucket = typeof ServerProcessResourceHistoryBucket.Type;
513
514export const ServerProcessResourceHistorySummary = Schema.Struct({
515 processKey: TrimmedNonEmptyString,
516 pid: PositiveInt,
517 ppid: NonNegativeInt,
518 command: TrimmedNonEmptyString,
519 depth: NonNegativeInt,
520 isServerRoot: Schema.Boolean,
521 firstSeenAt: Schema.DateTimeUtc,
522 lastSeenAt: Schema.DateTimeUtc,
523 currentCpuPercent: Schema.Number,
524 avgCpuPercent: Schema.Number,
525 maxCpuPercent: Schema.Number,
526 cpuSecondsApprox: Schema.Number,
527 currentRssBytes: NonNegativeInt,
528 maxRssBytes: NonNegativeInt,
529 sampleCount: NonNegativeInt,
530});
531export type ServerProcessResourceHistorySummary = typeof ServerProcessResourceHistorySummary.Type;
532
533export const ServerProcessResourceHistoryFailureTag = Schema.Literals([
534 "ProcessDiagnosticsQueryTimeoutError",
535 "ProcessDiagnosticsQueryFailedError",
536 "ProcessDiagnosticsServerProcessSignalError",
537 "ProcessDiagnosticsNotDescendantError",
538 "ProcessDiagnosticsSignalFailedError",
539]);
540export type ServerProcessResourceHistoryFailureTag =
541 typeof ServerProcessResourceHistoryFailureTag.Type;
542
543export const ServerProcessResourceHistoryResult = Schema.Struct({
544 readAt: Schema.DateTimeUtc,
545 windowMs: NonNegativeInt,
546 bucketMs: NonNegativeInt,
547 sampleIntervalMs: NonNegativeInt,
548 retainedSampleCount: NonNegativeInt,
549 totalCpuSecondsApprox: Schema.Number,
550 buckets: Schema.Array(ServerProcessResourceHistoryBucket),
551 topProcesses: Schema.Array(ServerProcessResourceHistorySummary),
552 error: Schema.Option(
553 Schema.Struct({
554 failureTag: ServerProcessResourceHistoryFailureTag,
555 message: TrimmedNonEmptyString,
556 }),
557 ),
558});
559export type ServerProcessResourceHistoryResult = typeof ServerProcessResourceHistoryResult.Type;
560
561export const ServerSignalProcessInput = Schema.Struct({
562 pid: PositiveInt,
563 startTimeMs: NonNegativeInt,
564 signal: ServerProcessSignal,
565});
566export type ServerSignalProcessInput = typeof ServerSignalProcessInput.Type;
567
568export const ServerSignalProcessResult = Schema.Struct({
569 pid: PositiveInt,
570 signal: ServerProcessSignal,
571 signaled: Schema.Boolean,
572 message: Schema.Option(TrimmedNonEmptyString),
573});
574export type ServerSignalProcessResult = typeof ServerSignalProcessResult.Type;
575
576/**
577 * A palette the environment's machine publishes for T3 Code to follow, read
578 * from a theme file next to the rest of the environment's state. Two seed
579 * colors rather than a full palette: clients derive the remaining roles with
580 * the same generator the guided theme editor uses, so a desktop theme carries
581 * over as a coherent T3 Code palette instead of a foreign one.
582 */
583export const EnvironmentThemeColor = Schema.String.check(
584 Schema.isPattern(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/),
585);
586export type EnvironmentThemeColor = typeof EnvironmentThemeColor.Type;
587
588/**
589 * Matches the client-side theme id rule, so a published id is selectable.
590 * The appearance keywords are excluded outright: a published `dark.json`
591 * would otherwise capture every client whose stored preference is the stock
592 * `"dark"`, retinting people who never chose it.
593 */
594export const EnvironmentThemeId = Schema.String.check(
595 Schema.isPattern(/^(?!(?:system|light|dark)$)[a-z0-9](?:[a-z0-9-]{0,47})$/),
596);
597export type EnvironmentThemeId = typeof EnvironmentThemeId.Type;
598
599/**
600 * Role colors as published. Values are any CSS color the client's theme
601 * parser accepts (exported theme files use oklch), canonicalized client-side;
602 * roles a build does not know are dropped there, so a machine may publish
603 * roles a newer client added without breaking an older one. Keys must still
604 * be role-shaped and values color-sized, so the record stays open to future
605 * vocabulary without being an arbitrary-payload channel.
606 */
607const EnvironmentThemeColors = Schema.Record(
608 Schema.String.check(Schema.isPattern(/^[a-zA-Z][a-zA-Z0-9]{0,63}$/)),
609 TrimmedNonEmptyString.check(Schema.isMaxLength(64)),
610);
611
612const environmentThemeFields = {
613 /**
614 * Standard exported theme files (the Download button's output) carry
615 * `version: 1`; the seeded short form a desktop generates has no version.
616 */
617 version: Schema.optional(Schema.Literal(1)),
618 /** Shown on the theme card, e.g. the desktop theme's own name. */
619 name: TrimmedNonEmptyString.check(Schema.isMaxLength(48)),
620 appearance: Schema.Literals(["light", "dark"]),
621 /**
622 * Seed colors. When present, clients derive the full palette from them with
623 * the guided theme editor's generator and layer `colors` on top; when
624 * absent, `colors` is the palette, as in an exported theme file.
625 */
626 canvas: Schema.optional(EnvironmentThemeColor),
627 accent: Schema.optional(EnvironmentThemeColor),
628 colors: Schema.optional(EnvironmentThemeColors),
629 /** The other appearance's palette, as exported theme files carry it. */
630 variants: Schema.optional(
631 Schema.Struct({
632 light: Schema.optional(EnvironmentThemeColors),
633 dark: Schema.optional(EnvironmentThemeColors),
634 }),
635 ),
636};
637
638/** One published theme file. The id is the filename, not part of the content,
639 * so a file cannot claim another file's identity; an embedded `id` is ignored. */
640export const EnvironmentThemeFile = Schema.Struct(environmentThemeFields);
641export type EnvironmentThemeFile = typeof EnvironmentThemeFile.Type;
642
643export const EnvironmentTheme = Schema.Struct({
644 /** The publishing filename without its extension, stable across recolors. */
645 id: EnvironmentThemeId,
646 ...environmentThemeFields,
647});
648export type EnvironmentTheme = typeof EnvironmentTheme.Type;
649
650/**
651 * Whether a theme file carries anything to render. A file with neither seeds
652 * nor colors would show as the stock palette wearing a name, which reads as a
653 * bug rather than a theme — the CLI and the server watcher both reject it,
654 * through this one predicate so they cannot drift.
655 */
656export function environmentThemeFileHasColors(file: EnvironmentThemeFile): boolean {
657 return (
658 (file.canvas !== undefined && file.accent !== undefined) ||
659 (file.colors !== undefined && Object.keys(file.colors).length > 0)
660 );
661}
662
663/**
664 * "tailnet" when the address is on this machine's Tailscale interface, "lan"
665 * for any other private address, including other VPNs in 100.64.0.0/10.
666 */
667export const ServerDirectEndpointKind = Schema.Literals(["lan", "tailnet"]);
668export type ServerDirectEndpointKind = typeof ServerDirectEndpointKind.Type;
669
670export const ServerDirectEndpoint = Schema.Struct({
671 kind: ServerDirectEndpointKind,
672 httpBaseUrl: TrimmedNonEmptyString,
673});
674export type ServerDirectEndpoint = typeof ServerDirectEndpoint.Type;
675
676export const ServerConfig = Schema.Struct({
677 devServerStatus: Schema.optionalKey(Schema.Boolean),
678 managedWorkspaces: Schema.optionalKey(Schema.Boolean),
679 mergebot: Schema.optionalKey(MergebotConfiguration),
680 environment: ExecutionEnvironmentDescriptor,
681 auth: ServerAuthDescriptor,
682 cwd: TrimmedNonEmptyString,
683 keybindingsConfigPath: TrimmedNonEmptyString,
684 keybindings: ResolvedKeybindingsConfig,
685 issues: ServerConfigIssues,
686 providers: ServerProviders,
687 // Editor ids grow over time; drop ones this build does not know rather than
688 // failing the whole config decode.
689 availableEditors: ForwardCompatibleArray(EditorId),
690 /**
691 * SSH hosts this environment advertises for remote open-in-editor links.
692 * Absent on servers that predate the feature; empty when the machine has no
693 * sshd or no advertisable name.
694 */
695 remoteOpenTargets: Schema.optionalKey(ForwardCompatibleArray(RemoteOpenTarget)),
696 /**
697 * Direct addresses this server listens on right now (LAN and tailnet), so a
698 * client connected one way can learn the others. Hints only: the client
699 * checks each address answers as this environment before using it. Absent on
700 * servers that predate the feature; empty when bound to loopback only.
701 */
702 directEndpoints: Schema.optionalKey(ForwardCompatibleArray(ServerDirectEndpoint)),
703 observability: ServerObservability,
704 settings: ServerSettings,
705 /** Whether shell subscriptions can emit an opt-in catch-up completion marker. */
706 shellResumeCompletionMarker: Schema.optionalKey(Schema.Boolean),
707 /** Whether shell.openInEditor honors `LaunchEditorInput.reveal` for the
708 file-manager editor. */
709 shellRevealInFileManager: Schema.optionalKey(Schema.Boolean),
710 /** File-manager wording clients should use for reveal actions. */
711 shellRevealInFileManagerKind: Schema.optionalKey(FileManagerRevealKind),
712 /** Whether thread subscriptions can emit an opt-in catch-up completion marker. */
713 threadResumeCompletionMarker: Schema.optionalKey(Schema.Boolean),
714 /**
715 * Whether thread detail reads accept a turn window (`turnLimit`/
716 * `beforeCursor`) and return `page` metadata. Clients must not send window
717 * fields to servers that don't advertise this.
718 */
719 threadSnapshotPagination: Schema.optionalKey(Schema.Boolean),
720 /** Whether thread reads accept the reasoningMessages opt-in. */
721 reasoningMessages: Schema.optionalKey(Schema.Boolean),
722 threadFind: Schema.optionalKey(Schema.Boolean),
723 threadFindProgressive: Schema.optionalKey(Schema.Boolean),
724 /**
725 * Folder behind this environment's Scratch project, for threads that need
726 * no repository. Present only on servers that answer projects.ensureScratch
727 * and whose data dir is outside a Git checkout.
728 */
729 scratchWorkspaceRoot: Schema.optionalKey(TrimmedNonEmptyString),
730 /**
731 * Folder that holds projects started from just a name. Present only on
732 * servers that answer projects.createNew.
733 */
734 newProjectsRoot: Schema.optionalKey(TrimmedNonEmptyString),
735 /**
736 * Palettes published by this environment's machine. Never sent in a config
737 * snapshot: the theme stream emits the current set before any change, so a
738 * snapshot carrying it too would hand every subscriber the same array twice
739 * per connect. Clients populate this by projecting `environmentThemesUpdated`,
740 * and it stays absent for subscribers that did not opt in.
741 */
742 environmentThemes: Schema.optional(Schema.Array(EnvironmentTheme)),
743 /**
744 * Quota reported by configured `usageLimitSources`. Like themes, never in
745 * a snapshot: the source stream emits the current set on subscribe, and it
746 * stays absent for subscribers that did not opt in.
747 */
748 usageLimitSources: Schema.optional(UsageLimitSourceSnapshots),
749});
750export type ServerConfig = typeof ServerConfig.Type;
751
752/**
753 * The machine an environment should be drawn as: the user's pick, else what
754 * the server detected, else a generic server. Settings only exist once
755 * connected; a descriptor alone (relay discovery, before any connection)
756 * still yields the detected kind. A null config (nothing known yet, or an
757 * older server) resolves to the same generic so rows never flicker between
758 * glyphs.
759 */
760export function resolveEnvironmentMachineKind(
761 config: {
762 readonly environment: Pick<ExecutionEnvironmentDescriptor, "platform">;
763 readonly settings?: Pick<ServerSettings, "environmentIcon">;
764 } | null,
765): EnvironmentMachineKind {
766 return config?.settings?.environmentIcon ?? config?.environment.platform.machine ?? "server";
767}
768
769const ServerUpsertKeybindingReplaceTarget = Schema.Struct({
770 key: KeybindingValue,
771 command: KeybindingCommand,
772 when: Schema.optional(KeybindingWhen),
773});
774
775export const ServerUpsertKeybindingInput = Schema.Struct({
776 key: KeybindingValue,
777 command: KeybindingCommand,
778 when: Schema.optional(KeybindingWhen),
779 replace: Schema.optional(ServerUpsertKeybindingReplaceTarget),
780});
781export type ServerUpsertKeybindingInput = typeof ServerUpsertKeybindingInput.Type;
782
783export const ServerRemoveKeybindingInput = ServerUpsertKeybindingReplaceTarget;
784export type ServerRemoveKeybindingInput = typeof ServerRemoveKeybindingInput.Type;
785
786export const ServerUpsertKeybindingResult = Schema.Struct({
787 keybindings: ResolvedKeybindingsConfig,
788 issues: ServerConfigIssues,
789});
790export type ServerUpsertKeybindingResult = typeof ServerUpsertKeybindingResult.Type;
791
792export const ServerRemoveKeybindingResult = ServerUpsertKeybindingResult;
793export type ServerRemoveKeybindingResult = typeof ServerRemoveKeybindingResult.Type;
794
795export const ServerConfigUpdatedPayload = Schema.Struct({
796 issues: ServerConfigIssues,
797 providers: ServerProviders,
798 settings: Schema.optional(ServerSettings),
799});
800export type ServerConfigUpdatedPayload = typeof ServerConfigUpdatedPayload.Type;
801
802export const ServerConfigKeybindingsUpdatedPayload = Schema.Struct({
803 keybindings: ResolvedKeybindingsConfig,
804 issues: ServerConfigIssues,
805});
806export type ServerConfigKeybindingsUpdatedPayload =
807 typeof ServerConfigKeybindingsUpdatedPayload.Type;
808
809export const ServerConfigProviderStatusesPayload = Schema.Struct({
810 providers: ServerProviders,
811});
812export type ServerConfigProviderStatusesPayload = typeof ServerConfigProviderStatusesPayload.Type;
813
814export const ServerConfigSettingsUpdatedPayload = Schema.Struct({
815 settings: ServerSettings,
816});
817export type ServerConfigSettingsUpdatedPayload = typeof ServerConfigSettingsUpdatedPayload.Type;
818
819export const ServerConfigStreamSnapshotEvent = Schema.Struct({
820 version: Schema.Literal(1),
821 type: Schema.Literal("snapshot"),
822 config: ServerConfig,
823});
824export type ServerConfigStreamSnapshotEvent = typeof ServerConfigStreamSnapshotEvent.Type;
825
826export const ServerConfigStreamKeybindingsUpdatedEvent = Schema.Struct({
827 version: Schema.Literal(1),
828 type: Schema.Literal("keybindingsUpdated"),
829 payload: ServerConfigKeybindingsUpdatedPayload,
830});
831export type ServerConfigStreamKeybindingsUpdatedEvent =
832 typeof ServerConfigStreamKeybindingsUpdatedEvent.Type;
833
834export const ServerConfigStreamProviderStatusesEvent = Schema.Struct({
835 version: Schema.Literal(1),
836 type: Schema.Literal("providerStatuses"),
837 payload: ServerConfigProviderStatusesPayload,
838});
839export type ServerConfigStreamProviderStatusesEvent =
840 typeof ServerConfigStreamProviderStatusesEvent.Type;
841
842export const ServerConfigStreamSettingsUpdatedEvent = Schema.Struct({
843 version: Schema.Literal(1),
844 type: Schema.Literal("settingsUpdated"),
845 payload: ServerConfigSettingsUpdatedPayload,
846});
847export type ServerConfigStreamSettingsUpdatedEvent =
848 typeof ServerConfigStreamSettingsUpdatedEvent.Type;
849
850export const ServerConfigEnvironmentThemesUpdatedPayload = Schema.Struct({
851 /** The full published set; empty once the machine publishes none. */
852 themes: Schema.Array(EnvironmentTheme),
853});
854export type ServerConfigEnvironmentThemesUpdatedPayload =
855 typeof ServerConfigEnvironmentThemesUpdatedPayload.Type;
856
857export const ServerConfigStreamEnvironmentThemesUpdatedEvent = Schema.Struct({
858 version: Schema.Literal(1),
859 type: Schema.Literal("environmentThemesUpdated"),
860 payload: ServerConfigEnvironmentThemesUpdatedPayload,
861});
862export type ServerConfigStreamEnvironmentThemesUpdatedEvent =
863 typeof ServerConfigStreamEnvironmentThemesUpdatedEvent.Type;
864
865export const ServerConfigUsageLimitSourcesUpdatedPayload = Schema.Struct({
866 /** The full set; empty once no source is configured. */
867 sources: UsageLimitSourceSnapshots,
868});
869export type ServerConfigUsageLimitSourcesUpdatedPayload =
870 typeof ServerConfigUsageLimitSourcesUpdatedPayload.Type;
871
872export const ServerConfigStreamUsageLimitSourcesUpdatedEvent = Schema.Struct({
873 version: Schema.Literal(1),
874 type: Schema.Literal("usageLimitSourcesUpdated"),
875 payload: ServerConfigUsageLimitSourcesUpdatedPayload,
876});
877export type ServerConfigStreamUsageLimitSourcesUpdatedEvent =
878 typeof ServerConfigStreamUsageLimitSourcesUpdatedEvent.Type;
879
880export const ServerConfigStreamEvent = Schema.Union([
881 ServerConfigStreamSnapshotEvent,
882 ServerConfigStreamKeybindingsUpdatedEvent,
883 ServerConfigStreamProviderStatusesEvent,
884 ServerConfigStreamSettingsUpdatedEvent,
885 ServerConfigStreamEnvironmentThemesUpdatedEvent,
886 ServerConfigStreamUsageLimitSourcesUpdatedEvent,
887]);
888export type ServerConfigStreamEvent = typeof ServerConfigStreamEvent.Type;
889
890/** Terminal selection recorded by the service launcher for one update. */
891export const ServerSelfUpdateOutcome = Schema.Struct({
892 id: TrimmedNonEmptyString,
893 fromVersion: TrimmedNonEmptyString,
894 targetVersion: TrimmedNonEmptyString,
895 status: Schema.Literals(["committed", "rolled-back", "failed"]),
896 reason: Schema.optionalKey(TrimmedNonEmptyString),
897});
898export type ServerSelfUpdateOutcome = typeof ServerSelfUpdateOutcome.Type;
899
900export const ServerLifecycleReadyPayload = Schema.Struct({
901 at: IsoDateTime,
902 environment: ExecutionEnvironmentDescriptor,
903 /** Present when this process resumed a launcher-managed update. */
904 updateOutcome: Schema.optionalKey(ServerSelfUpdateOutcome),
905});
906export type ServerLifecycleReadyPayload = typeof ServerLifecycleReadyPayload.Type;
907
908export const ServerLifecycleWelcomePayload = Schema.Struct({
909 environment: ExecutionEnvironmentDescriptor,
910 cwd: TrimmedNonEmptyString,
911 projectName: TrimmedNonEmptyString,
912 bootstrapStatus: Schema.optional(Schema.Literals(["pending", "complete"])),
913 bootstrapProjectId: Schema.optional(ProjectId),
914 bootstrapThreadId: Schema.optional(ThreadId),
915 bootstrapProjectCreated: Schema.optional(Schema.Boolean),
916 bootstrapThreadCreated: Schema.optional(Schema.Boolean),
917});
918export type ServerLifecycleWelcomePayload = typeof ServerLifecycleWelcomePayload.Type;
919
920export const ServerLifecycleLegacyThreadMigrationPayload = Schema.Struct({
921 status: Schema.Union([Schema.Literal("running"), Schema.Literal("complete")]),
922 totalThreadCount: NonNegativeInt,
923});
924export type ServerLifecycleLegacyThreadMigrationPayload =
925 typeof ServerLifecycleLegacyThreadMigrationPayload.Type;
926
927export const ServerLifecycleStreamWelcomeEvent = Schema.Struct({
928 version: Schema.Literal(1),
929 sequence: NonNegativeInt,
930 type: Schema.Literal("welcome"),
931 payload: ServerLifecycleWelcomePayload,
932});
933export type ServerLifecycleStreamWelcomeEvent = typeof ServerLifecycleStreamWelcomeEvent.Type;
934
935export const ServerLifecycleStreamReadyEvent = Schema.Struct({
936 version: Schema.Literal(1),
937 sequence: NonNegativeInt,
938 type: Schema.Literal("ready"),
939 payload: ServerLifecycleReadyPayload,
940});
941export type ServerLifecycleStreamReadyEvent = typeof ServerLifecycleStreamReadyEvent.Type;
942
943export const ServerLifecycleStreamLegacyThreadMigrationEvent = Schema.Struct({
944 version: Schema.Literal(1),
945 sequence: NonNegativeInt,
946 type: Schema.Literal("legacyThreadMigration"),
947 payload: ServerLifecycleLegacyThreadMigrationPayload,
948});
949export type ServerLifecycleStreamLegacyThreadMigrationEvent =
950 typeof ServerLifecycleStreamLegacyThreadMigrationEvent.Type;
951
952export const ServerLifecycleStreamEvent = Schema.Union([
953 ServerLifecycleStreamWelcomeEvent,
954 ServerLifecycleStreamReadyEvent,
955 ServerLifecycleStreamLegacyThreadMigrationEvent,
956]);
957export type ServerLifecycleStreamEvent = typeof ServerLifecycleStreamEvent.Type;
958
959export const ServerProviderUpdatedPayload = Schema.Struct({
960 providers: ServerProviders,
961});
962export type ServerProviderUpdatedPayload = typeof ServerProviderUpdatedPayload.Type;
963
964export const ServerProviderUpdateInput = Schema.Struct({
965 provider: ProviderDriverKind,
966 targetVersion: Schema.optionalKey(TrimmedNonEmptyString),
967 instanceId: Schema.optionalKey(ProviderInstanceId),
968});
969export type ServerProviderUpdateInput = typeof ServerProviderUpdateInput.Type;
970
971export class ServerProviderUpdateError extends Schema.TaggedError<ServerProviderUpdateError>()(
972 "ServerProviderUpdateError",
973 {
974 provider: ProviderDriverKind,
975 reason: TrimmedNonEmptyString,
976 cause: Schema.optional(Schema.Defect()),
977 },
978) {
979 override get message(): string {
980 return `Provider update failed for ${this.provider}: ${this.reason}`;
981 }
982}
983
984export const ServerSelfUpdateInput = Schema.Struct({
985 /** Exact npm version of the `t3` package to install (never a dist-tag, so
986 the server and the acknowledging client agree on what was requested). */
987 targetVersion: TrimmedNonEmptyString,
988 /** Opt-in recovery for provider turns that are running when the server
989 hands off to its replacement. Missing and false keep restart behavior
990 conservative under version skew. */
991 continueRunningThreads: Schema.optionalKey(Schema.Boolean),
992});
993export type ServerSelfUpdateInput = typeof ServerSelfUpdateInput.Type;
994
995/** Acknowledgement that the update artifact is installed and the server is
996 about to restart into it — the connection will drop moments later. */
997export const ServerSelfUpdateResult = Schema.Struct({
998 targetVersion: TrimmedNonEmptyString,
999 method: ServerSelfUpdateMethod,
1000 /** Launcher-generated correlation ID. Absent when talking to older servers. */
1001 updateId: Schema.optionalKey(TrimmedNonEmptyString),
1002 /** Desktop preparation token. Present only for the desktop-app method. */
1003 desktopUpdateToken: Schema.optionalKey(TrimmedNonEmptyString),
1004});
1005export type ServerSelfUpdateResult = typeof ServerSelfUpdateResult.Type;
1006
1007export const DesktopUpdateCommitInput = Schema.Struct({
1008 requestId: TrimmedNonEmptyString,
1009});
1010export type DesktopUpdateCommitInput = typeof DesktopUpdateCommitInput.Type;
1011
1012export const ServerSelfUpdateProgressStage = Schema.Literals(["downloading", "installing"]);
1013export type ServerSelfUpdateProgressStage = typeof ServerSelfUpdateProgressStage.Type;
1014
1015export const ServerSelfUpdateProgressEvent = Schema.Union([
1016 Schema.Struct({
1017 type: Schema.Literal("progress"),
1018 stage: ServerSelfUpdateProgressStage,
1019 }),
1020 Schema.Struct({
1021 type: Schema.Literal("complete"),
1022 result: ServerSelfUpdateResult,
1023 }),
1024]);
1025export type ServerSelfUpdateProgressEvent = typeof ServerSelfUpdateProgressEvent.Type;
1026
1027export class ServerSelfUpdateError extends Schema.TaggedError<ServerSelfUpdateError>()(
1028 "ServerSelfUpdateError",
1029 {
1030 reason: TrimmedNonEmptyString,
1031 cause: Schema.optional(Schema.Defect()),
1032 },
1033) {
1034 override get message(): string {
1035 return `Server update failed: ${this.reason}`;
1036 }
1037}