import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import { type EnvironmentMachineKind, ExecutionEnvironmentDescriptor, ServerSelfUpdateMethod, } from "./environment.ts"; import { ServerAuthDescriptor } from "./auth.ts"; import { ForwardCompatibleArray, IsoDateTime, NonNegativeInt, PositiveInt, ProjectId, ThreadId, TrimmedNonEmptyString, } from "./baseSchemas.ts"; import { KeybindingCommand, KeybindingValue, KeybindingWhen, ResolvedKeybindingsConfig, } from "./keybindings.ts"; import { EditorId, FileManagerRevealKind, RemoteOpenTarget } from "./editor.ts"; import { ModelCapabilities } from "./model.ts"; import { ProviderDriverKind, ProviderInstanceId } from "./providerInstance.ts"; import { ServerProviderUsageLimits, UsageLimitSourceSnapshots } from "./providerUsageLimits.ts"; import { ServerSettings } from "./settings.ts"; const KeybindingsMalformedConfigIssue = Schema.Struct({ kind: Schema.Literal("keybindings.malformed-config"), message: TrimmedNonEmptyString, }); const KeybindingsInvalidEntryIssue = Schema.Struct({ kind: Schema.Literal("keybindings.invalid-entry"), message: TrimmedNonEmptyString, index: Schema.Number, }); export const ServerConfigIssue = Schema.Union([ KeybindingsMalformedConfigIssue, KeybindingsInvalidEntryIssue, ]); export type ServerConfigIssue = typeof ServerConfigIssue.Type; // Issue kinds grow over time; older clients must not fail the whole config // decode over a kind they cannot render. const ServerConfigIssues = ForwardCompatibleArray(ServerConfigIssue); export const ServerProviderState = Schema.Literals(["ready", "warning", "error", "disabled"]); export type ServerProviderState = typeof ServerProviderState.Type; export const ServerProviderAuthStatus = Schema.Literals([ "authenticated", "unauthenticated", "unknown", ]); export type ServerProviderAuthStatus = typeof ServerProviderAuthStatus.Type; export const ServerProviderAuth = Schema.Struct({ status: ServerProviderAuthStatus, type: Schema.optional(TrimmedNonEmptyString), label: Schema.optional(TrimmedNonEmptyString), email: Schema.optional(TrimmedNonEmptyString), }); export type ServerProviderAuth = typeof ServerProviderAuth.Type; export const ServerProviderModel = Schema.Struct({ slug: TrimmedNonEmptyString, name: TrimmedNonEmptyString, shortName: Schema.optional(TrimmedNonEmptyString), subProvider: Schema.optional(TrimmedNonEmptyString), aliases: Schema.optional(Schema.Array(TrimmedNonEmptyString)), badge: Schema.optional(Schema.Literal("new")), isCustom: Schema.Boolean, isDefault: Schema.optional(Schema.Boolean), isLegacy: Schema.optional(Schema.Boolean), capabilities: Schema.NullOr(ModelCapabilities), }); export type ServerProviderModel = typeof ServerProviderModel.Type; export const ServerProviderSlashCommandInput = Schema.Struct({ hint: TrimmedNonEmptyString, }); export type ServerProviderSlashCommandInput = typeof ServerProviderSlashCommandInput.Type; export const ServerProviderSlashCommand = Schema.Struct({ name: TrimmedNonEmptyString, description: Schema.optional(TrimmedNonEmptyString), input: Schema.optional(ServerProviderSlashCommandInput), }); export type ServerProviderSlashCommand = typeof ServerProviderSlashCommand.Type; export const ServerProviderSkill = Schema.Struct({ name: TrimmedNonEmptyString, description: Schema.optional(TrimmedNonEmptyString), path: TrimmedNonEmptyString, scope: Schema.optional(TrimmedNonEmptyString), enabled: Schema.Boolean, displayName: Schema.optional(TrimmedNonEmptyString), shortDescription: Schema.optional(TrimmedNonEmptyString), /** * The skill is hidden from the agent's own skill tool, so only the user can * start it — Claude Code's `disable-model-invocation`. Composers must offer * it as a slash command; naming it in prose does nothing. */ userInvocationOnly: Schema.optional(Schema.Boolean), /** * The mirror of {@link ServerProviderSkill.userInvocationOnly}: Claude Code's * `user-invocable: false` keeps the skill out of its own slash commands, so * only the agent can start it. Composers must not offer it under `/`. */ userInvocable: Schema.optional(Schema.Boolean), }); export type ServerProviderSkill = typeof ServerProviderSkill.Type; export const ServerProviderWorkspaceSnapshot = Schema.Struct({ cwd: TrimmedNonEmptyString, checkedAt: IsoDateTime, slashCommands: Schema.Array(ServerProviderSlashCommand), skills: Schema.Array(ServerProviderSkill), }); export type ServerProviderWorkspaceSnapshot = typeof ServerProviderWorkspaceSnapshot.Type; /** * Availability of a configured provider instance from the runtime's POV. * * - `available` — the build ships this driver and an instance is wired * up. Default for legacy snapshots produced from the closed * `ServerSettings.providers` map. * - `unavailable` — the user's `ServerSettings.providerInstances` (or a * persisted thread / session binding) references a driver this build * doesn't ship. Common after rolling back from a fork or PR branch * that introduced a new driver. The snapshot is preserved so the UI * can render "missing driver" affordances and so the data round-trips * when the user moves back to the fork. * * Snapshots with `availability: "unavailable"` MUST set * `installed: false` and `enabled: false`; the runtime refuses turn * starts against them with a structured error. */ export const ServerProviderAvailability = Schema.Literals(["available", "unavailable"]); export type ServerProviderAvailability = typeof ServerProviderAvailability.Type; export const ServerProviderContinuation = Schema.Struct({ groupKey: TrimmedNonEmptyString, }); export type ServerProviderContinuation = typeof ServerProviderContinuation.Type; export const ServerProviderVersionAdvisoryStatus = Schema.Literals([ "unknown", "current", "behind_latest", ]); export type ServerProviderVersionAdvisoryStatus = typeof ServerProviderVersionAdvisoryStatus.Type; export const ServerProviderVersionAdvisory = Schema.Struct({ status: ServerProviderVersionAdvisoryStatus, currentVersion: Schema.NullOr(TrimmedNonEmptyString), latestVersion: Schema.NullOr(TrimmedNonEmptyString), updateCommand: Schema.NullOr(TrimmedNonEmptyString), canUpdate: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), checkedAt: Schema.NullOr(IsoDateTime), message: Schema.NullOr(TrimmedNonEmptyString), }); export type ServerProviderVersionAdvisory = typeof ServerProviderVersionAdvisory.Type; export const ServerProviderUpdateStatus = Schema.Literals([ "idle", "queued", "running", "succeeded", "failed", "unchanged", ]); export type ServerProviderUpdateStatus = typeof ServerProviderUpdateStatus.Type; export const ServerProviderUpdateState = Schema.Struct({ status: ServerProviderUpdateStatus, startedAt: Schema.NullOr(IsoDateTime), finishedAt: Schema.NullOr(IsoDateTime), message: Schema.NullOr(TrimmedNonEmptyString), output: Schema.NullOr(Schema.String.check(Schema.isMaxLength(10_000))), }); export type ServerProviderUpdateState = typeof ServerProviderUpdateState.Type; export const ServerProvider = Schema.Struct({ // Routing key for the configured instance this snapshot represents. This // is the only stable identity consumers may use for provider routing. instanceId: ProviderInstanceId, // Open driver kind slug that selects the implementation handling this // instance. It is metadata/capability context, not a routing key. driver: ProviderDriverKind, displayName: Schema.optional(TrimmedNonEmptyString), accentColor: Schema.optional(TrimmedNonEmptyString), badgeLabel: Schema.optional(TrimmedNonEmptyString), continuation: Schema.optional(ServerProviderContinuation), showInteractionModeToggle: Schema.optional(Schema.Boolean), // The driver streams context window usage, so a started thread will have a // meter once its activities load. Clients reserve the meter's space on it. reportsContextWindow: Schema.optional(Schema.Boolean), requiresNewThreadForModelChange: Schema.optional(Schema.Boolean), supportsConversationRollback: Schema.optional(Schema.Boolean), supportsTextGeneration: Schema.optional(Schema.Boolean), setup: Schema.optional( Schema.Struct({ canAuthenticate: Schema.Boolean, canInstall: Schema.Boolean, }), ), enabled: Schema.Boolean, installed: Schema.Boolean, version: Schema.NullOr(TrimmedNonEmptyString), status: ServerProviderState, auth: ServerProviderAuth, checkedAt: IsoDateTime, message: Schema.optional(TrimmedNonEmptyString), // Optional for back-compat: every legacy producer omits this field and // an absent value is interpreted as `"available"` by consumers (see // `isProviderAvailable`). New `ProviderInstanceRegistry` outputs set it // explicitly so the UI can render unavailable shadows from // `ServerSettings.providerInstances`. availability: Schema.optional(ServerProviderAvailability), // Human-readable reason populated when `availability === "unavailable"`. // Surfaces in the UI alongside the missing-driver affordance. unavailableReason: Schema.optional(TrimmedNonEmptyString), models: Schema.Array(ServerProviderModel), slashCommands: Schema.Array(ServerProviderSlashCommand).pipe( Schema.withDecodingDefault(Effect.succeed([])), ), skills: Schema.Array(ServerProviderSkill).pipe(Schema.withDecodingDefault(Effect.succeed([]))), workspaceSnapshots: Schema.optionalKey(Schema.Array(ServerProviderWorkspaceSnapshot)), // Absent when the driver has no notion of subscription usage. usageLimits: Schema.optional(ServerProviderUsageLimits), versionAdvisory: Schema.optionalKey(ServerProviderVersionAdvisory), updateState: Schema.optionalKey(ServerProviderUpdateState), }); export type ServerProvider = typeof ServerProvider.Type; // Provider status kinds grow over time (ServerProviderState, // ServerProviderAuthStatus, ServerProviderVersionAdvisoryStatus, // ServerProviderUpdateStatus); an older client must not fail the whole config // decode over one provider it cannot render. export const ServerProviders = ForwardCompatibleArray(ServerProvider); export type ServerProviders = typeof ServerProviders.Type; /** * Treat the optional `availability` as "available" when absent. This is * the rule legacy producers (which omit the field) and new producers * (which set it explicitly) agree on so consumers never have to thread * `?? "available"` defaults through their code paths. */ export const isProviderAvailable = (snapshot: ServerProvider): boolean => snapshot.availability !== "unavailable"; export const ServerObservability = Schema.Struct({ logsDirectoryPath: TrimmedNonEmptyString, localTracingEnabled: Schema.Boolean, otlpTracesUrl: Schema.optional(TrimmedNonEmptyString), otlpTracesEnabled: Schema.Boolean, otlpMetricsUrl: Schema.optional(TrimmedNonEmptyString), otlpMetricsEnabled: Schema.Boolean, }); export type ServerObservability = typeof ServerObservability.Type; export const ServerTraceDiagnosticsErrorKind = Schema.Literals([ "trace-file-not-found", "trace-file-read-failed", ]); export type ServerTraceDiagnosticsErrorKind = typeof ServerTraceDiagnosticsErrorKind.Type; export const ServerTraceDiagnosticsSpanSummary = Schema.Struct({ name: TrimmedNonEmptyString, count: NonNegativeInt, failureCount: NonNegativeInt, totalDurationMs: Schema.Number, averageDurationMs: Schema.Number, maxDurationMs: Schema.Number, }); export type ServerTraceDiagnosticsSpanSummary = typeof ServerTraceDiagnosticsSpanSummary.Type; export const ServerTraceDiagnosticsFailureSummary = Schema.Struct({ name: TrimmedNonEmptyString, cause: TrimmedNonEmptyString, count: NonNegativeInt, lastSeenAt: Schema.DateTimeUtc, traceId: TrimmedNonEmptyString, spanId: TrimmedNonEmptyString, }); export type ServerTraceDiagnosticsFailureSummary = typeof ServerTraceDiagnosticsFailureSummary.Type; export const ServerTraceDiagnosticsRecentFailure = Schema.Struct({ name: TrimmedNonEmptyString, cause: TrimmedNonEmptyString, durationMs: Schema.Number, endedAt: Schema.DateTimeUtc, traceId: TrimmedNonEmptyString, spanId: TrimmedNonEmptyString, }); export type ServerTraceDiagnosticsRecentFailure = typeof ServerTraceDiagnosticsRecentFailure.Type; export const ServerTraceDiagnosticsSpanOccurrence = Schema.Struct({ name: TrimmedNonEmptyString, durationMs: Schema.Number, endedAt: Schema.DateTimeUtc, traceId: TrimmedNonEmptyString, spanId: TrimmedNonEmptyString, }); export type ServerTraceDiagnosticsSpanOccurrence = typeof ServerTraceDiagnosticsSpanOccurrence.Type; export const ServerTraceDiagnosticsLogEvent = Schema.Struct({ spanName: TrimmedNonEmptyString, level: TrimmedNonEmptyString, message: TrimmedNonEmptyString, seenAt: Schema.DateTimeUtc, traceId: TrimmedNonEmptyString, spanId: TrimmedNonEmptyString, }); export type ServerTraceDiagnosticsLogEvent = typeof ServerTraceDiagnosticsLogEvent.Type; export const ServerTraceDiagnosticsResult = Schema.Struct({ traceFilePath: TrimmedNonEmptyString, scannedFilePaths: Schema.Array(TrimmedNonEmptyString), readAt: Schema.DateTimeUtc, recordCount: NonNegativeInt, parseErrorCount: NonNegativeInt, firstSpanAt: Schema.Option(Schema.DateTimeUtc), lastSpanAt: Schema.Option(Schema.DateTimeUtc), failureCount: NonNegativeInt, interruptionCount: NonNegativeInt, slowSpanThresholdMs: NonNegativeInt, slowSpanCount: NonNegativeInt, logLevelCounts: Schema.Record(TrimmedNonEmptyString, NonNegativeInt), topSpansByCount: Schema.Array(ServerTraceDiagnosticsSpanSummary), slowestSpans: Schema.Array(ServerTraceDiagnosticsSpanOccurrence), commonFailures: Schema.Array(ServerTraceDiagnosticsFailureSummary), latestFailures: Schema.Array(ServerTraceDiagnosticsRecentFailure), latestWarningAndErrorLogs: Schema.Array(ServerTraceDiagnosticsLogEvent), partialFailure: Schema.Option(Schema.Boolean), error: Schema.Option( Schema.Struct({ kind: ServerTraceDiagnosticsErrorKind, message: TrimmedNonEmptyString, }), ), }); export type ServerTraceDiagnosticsResult = typeof ServerTraceDiagnosticsResult.Type; export const ServerProcessSignal = Schema.Literals(["SIGINT", "SIGKILL"]); export type ServerProcessSignal = typeof ServerProcessSignal.Type; export const ServerProcessDiagnosticsEntry = Schema.Struct({ pid: PositiveInt, startTimeMs: NonNegativeInt, ppid: NonNegativeInt, pgid: Schema.Option(Schema.Int), status: TrimmedNonEmptyString, cpuPercent: Schema.Number, rssBytes: NonNegativeInt, elapsed: TrimmedNonEmptyString, command: TrimmedNonEmptyString, depth: NonNegativeInt, childPids: Schema.Array(PositiveInt), }); export type ServerProcessDiagnosticsEntry = typeof ServerProcessDiagnosticsEntry.Type; export const ServerProcessDiagnosticsResult = Schema.Struct({ serverPid: PositiveInt, readAt: Schema.DateTimeUtc, processCount: NonNegativeInt, totalRssBytes: NonNegativeInt, totalCpuPercent: Schema.Number, processes: Schema.Array(ServerProcessDiagnosticsEntry), error: Schema.Option( Schema.Struct({ message: TrimmedNonEmptyString, }), ), }); export type ServerProcessDiagnosticsResult = typeof ServerProcessDiagnosticsResult.Type; export const ServerProcessResourceHistoryInput = Schema.Struct({ windowMs: NonNegativeInt, bucketMs: NonNegativeInt, }); export type ServerProcessResourceHistoryInput = typeof ServerProcessResourceHistoryInput.Type; export const ServerProcessResourceHistoryBucket = Schema.Struct({ startedAt: Schema.DateTimeUtc, endedAt: Schema.DateTimeUtc, avgCpuPercent: Schema.Number, maxCpuPercent: Schema.Number, maxRssBytes: NonNegativeInt, maxProcessCount: NonNegativeInt, }); export type ServerProcessResourceHistoryBucket = typeof ServerProcessResourceHistoryBucket.Type; export const ServerProcessResourceHistorySummary = Schema.Struct({ processKey: TrimmedNonEmptyString, pid: PositiveInt, ppid: NonNegativeInt, command: TrimmedNonEmptyString, depth: NonNegativeInt, isServerRoot: Schema.Boolean, firstSeenAt: Schema.DateTimeUtc, lastSeenAt: Schema.DateTimeUtc, currentCpuPercent: Schema.Number, avgCpuPercent: Schema.Number, maxCpuPercent: Schema.Number, cpuSecondsApprox: Schema.Number, currentRssBytes: NonNegativeInt, maxRssBytes: NonNegativeInt, sampleCount: NonNegativeInt, }); export type ServerProcessResourceHistorySummary = typeof ServerProcessResourceHistorySummary.Type; export const ServerProcessResourceHistoryFailureTag = Schema.Literals([ "ProcessDiagnosticsQueryTimeoutError", "ProcessDiagnosticsQueryFailedError", "ProcessDiagnosticsServerProcessSignalError", "ProcessDiagnosticsNotDescendantError", "ProcessDiagnosticsSignalFailedError", ]); export type ServerProcessResourceHistoryFailureTag = typeof ServerProcessResourceHistoryFailureTag.Type; export const ServerProcessResourceHistoryResult = Schema.Struct({ readAt: Schema.DateTimeUtc, windowMs: NonNegativeInt, bucketMs: NonNegativeInt, sampleIntervalMs: NonNegativeInt, retainedSampleCount: NonNegativeInt, totalCpuSecondsApprox: Schema.Number, buckets: Schema.Array(ServerProcessResourceHistoryBucket), topProcesses: Schema.Array(ServerProcessResourceHistorySummary), error: Schema.Option( Schema.Struct({ failureTag: ServerProcessResourceHistoryFailureTag, message: TrimmedNonEmptyString, }), ), }); export type ServerProcessResourceHistoryResult = typeof ServerProcessResourceHistoryResult.Type; export const ServerSignalProcessInput = Schema.Struct({ pid: PositiveInt, startTimeMs: NonNegativeInt, signal: ServerProcessSignal, }); export type ServerSignalProcessInput = typeof ServerSignalProcessInput.Type; export const ServerSignalProcessResult = Schema.Struct({ pid: PositiveInt, signal: ServerProcessSignal, signaled: Schema.Boolean, message: Schema.Option(TrimmedNonEmptyString), }); export type ServerSignalProcessResult = typeof ServerSignalProcessResult.Type; /** * A palette the environment's machine publishes for T3 Code to follow, read * from a theme file next to the rest of the environment's state. Two seed * colors rather than a full palette: clients derive the remaining roles with * the same generator the guided theme editor uses, so a desktop theme carries * over as a coherent T3 Code palette instead of a foreign one. */ export const EnvironmentThemeColor = Schema.String.check( Schema.isPattern(/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/), ); export type EnvironmentThemeColor = typeof EnvironmentThemeColor.Type; /** * Matches the client-side theme id rule, so a published id is selectable. * The appearance keywords are excluded outright: a published `dark.json` * would otherwise capture every client whose stored preference is the stock * `"dark"`, retinting people who never chose it. */ export const EnvironmentThemeId = Schema.String.check( Schema.isPattern(/^(?!(?:system|light|dark)$)[a-z0-9](?:[a-z0-9-]{0,47})$/), ); export type EnvironmentThemeId = typeof EnvironmentThemeId.Type; /** * Role colors as published. Values are any CSS color the client's theme * parser accepts (exported theme files use oklch), canonicalized client-side; * roles a build does not know are dropped there, so a machine may publish * roles a newer client added without breaking an older one. Keys must still * be role-shaped and values color-sized, so the record stays open to future * vocabulary without being an arbitrary-payload channel. */ const EnvironmentThemeColors = Schema.Record( Schema.String.check(Schema.isPattern(/^[a-zA-Z][a-zA-Z0-9]{0,63}$/)), TrimmedNonEmptyString.check(Schema.isMaxLength(64)), ); const environmentThemeFields = { /** * Standard exported theme files (the Download button's output) carry * `version: 1`; the seeded short form a desktop generates has no version. */ version: Schema.optional(Schema.Literal(1)), /** Shown on the theme card, e.g. the desktop theme's own name. */ name: TrimmedNonEmptyString.check(Schema.isMaxLength(48)), appearance: Schema.Literals(["light", "dark"]), /** * Seed colors. When present, clients derive the full palette from them with * the guided theme editor's generator and layer `colors` on top; when * absent, `colors` is the palette, as in an exported theme file. */ canvas: Schema.optional(EnvironmentThemeColor), accent: Schema.optional(EnvironmentThemeColor), colors: Schema.optional(EnvironmentThemeColors), /** The other appearance's palette, as exported theme files carry it. */ variants: Schema.optional( Schema.Struct({ light: Schema.optional(EnvironmentThemeColors), dark: Schema.optional(EnvironmentThemeColors), }), ), }; /** One published theme file. The id is the filename, not part of the content, * so a file cannot claim another file's identity; an embedded `id` is ignored. */ export const EnvironmentThemeFile = Schema.Struct(environmentThemeFields); export type EnvironmentThemeFile = typeof EnvironmentThemeFile.Type; export const EnvironmentTheme = Schema.Struct({ /** The publishing filename without its extension, stable across recolors. */ id: EnvironmentThemeId, ...environmentThemeFields, }); export type EnvironmentTheme = typeof EnvironmentTheme.Type; /** * Whether a theme file carries anything to render. A file with neither seeds * nor colors would show as the stock palette wearing a name, which reads as a * bug rather than a theme — the CLI and the server watcher both reject it, * through this one predicate so they cannot drift. */ export function environmentThemeFileHasColors(file: EnvironmentThemeFile): boolean { return ( (file.canvas !== undefined && file.accent !== undefined) || (file.colors !== undefined && Object.keys(file.colors).length > 0) ); } export const ServerConfig = Schema.Struct({ environment: ExecutionEnvironmentDescriptor, auth: ServerAuthDescriptor, cwd: TrimmedNonEmptyString, keybindingsConfigPath: TrimmedNonEmptyString, keybindings: ResolvedKeybindingsConfig, issues: ServerConfigIssues, providers: ServerProviders, // Editor ids grow over time; drop ones this build does not know rather than // failing the whole config decode. availableEditors: ForwardCompatibleArray(EditorId), /** * SSH hosts this environment advertises for remote open-in-editor links. * Absent on servers that predate the feature; empty when the machine has no * sshd or no advertisable name. */ remoteOpenTargets: Schema.optionalKey(ForwardCompatibleArray(RemoteOpenTarget)), observability: ServerObservability, settings: ServerSettings, /** Whether shell subscriptions can emit an opt-in catch-up completion marker. */ shellResumeCompletionMarker: Schema.optionalKey(Schema.Boolean), /** Whether shell.openInEditor honors `LaunchEditorInput.reveal` for the file-manager editor. */ shellRevealInFileManager: Schema.optionalKey(Schema.Boolean), /** File-manager wording clients should use for reveal actions. */ shellRevealInFileManagerKind: Schema.optionalKey(FileManagerRevealKind), /** Whether thread subscriptions can emit an opt-in catch-up completion marker. */ threadResumeCompletionMarker: Schema.optionalKey(Schema.Boolean), /** * Whether thread detail reads accept a turn window (`turnLimit`/ * `beforeCursor`) and return `page` metadata. Clients must not send window * fields to servers that don't advertise this. */ threadSnapshotPagination: Schema.optionalKey(Schema.Boolean), /** * Palettes published by this environment's machine. Never sent in a config * snapshot: the theme stream emits the current set before any change, so a * snapshot carrying it too would hand every subscriber the same array twice * per connect. Clients populate this by projecting `environmentThemesUpdated`, * and it stays absent for subscribers that did not opt in. */ environmentThemes: Schema.optional(Schema.Array(EnvironmentTheme)), /** * Quota reported by configured `usageLimitSources`. Like themes, never in * a snapshot: the source stream emits the current set on subscribe, and it * stays absent for subscribers that did not opt in. */ usageLimitSources: Schema.optional(UsageLimitSourceSnapshots), }); export type ServerConfig = typeof ServerConfig.Type; /** * The machine an environment should be drawn as: the user's pick, else what * the server detected, else a generic server. A null config (not connected * yet, or an older server) resolves to the same generic so rows never * flicker between glyphs. */ export function resolveEnvironmentMachineKind( config: Pick | null, ): EnvironmentMachineKind { return config?.settings.environmentIcon ?? config?.environment.platform.machine ?? "server"; } const ServerUpsertKeybindingReplaceTarget = Schema.Struct({ key: KeybindingValue, command: KeybindingCommand, when: Schema.optional(KeybindingWhen), }); export const ServerUpsertKeybindingInput = Schema.Struct({ key: KeybindingValue, command: KeybindingCommand, when: Schema.optional(KeybindingWhen), replace: Schema.optional(ServerUpsertKeybindingReplaceTarget), }); export type ServerUpsertKeybindingInput = typeof ServerUpsertKeybindingInput.Type; export const ServerRemoveKeybindingInput = ServerUpsertKeybindingReplaceTarget; export type ServerRemoveKeybindingInput = typeof ServerRemoveKeybindingInput.Type; export const ServerUpsertKeybindingResult = Schema.Struct({ keybindings: ResolvedKeybindingsConfig, issues: ServerConfigIssues, }); export type ServerUpsertKeybindingResult = typeof ServerUpsertKeybindingResult.Type; export const ServerRemoveKeybindingResult = ServerUpsertKeybindingResult; export type ServerRemoveKeybindingResult = typeof ServerRemoveKeybindingResult.Type; export const ServerConfigUpdatedPayload = Schema.Struct({ issues: ServerConfigIssues, providers: ServerProviders, settings: Schema.optional(ServerSettings), }); export type ServerConfigUpdatedPayload = typeof ServerConfigUpdatedPayload.Type; export const ServerConfigKeybindingsUpdatedPayload = Schema.Struct({ keybindings: ResolvedKeybindingsConfig, issues: ServerConfigIssues, }); export type ServerConfigKeybindingsUpdatedPayload = typeof ServerConfigKeybindingsUpdatedPayload.Type; export const ServerConfigProviderStatusesPayload = Schema.Struct({ providers: ServerProviders, }); export type ServerConfigProviderStatusesPayload = typeof ServerConfigProviderStatusesPayload.Type; export const ServerConfigSettingsUpdatedPayload = Schema.Struct({ settings: ServerSettings, }); export type ServerConfigSettingsUpdatedPayload = typeof ServerConfigSettingsUpdatedPayload.Type; export const ServerConfigStreamSnapshotEvent = Schema.Struct({ version: Schema.Literal(1), type: Schema.Literal("snapshot"), config: ServerConfig, }); export type ServerConfigStreamSnapshotEvent = typeof ServerConfigStreamSnapshotEvent.Type; export const ServerConfigStreamKeybindingsUpdatedEvent = Schema.Struct({ version: Schema.Literal(1), type: Schema.Literal("keybindingsUpdated"), payload: ServerConfigKeybindingsUpdatedPayload, }); export type ServerConfigStreamKeybindingsUpdatedEvent = typeof ServerConfigStreamKeybindingsUpdatedEvent.Type; export const ServerConfigStreamProviderStatusesEvent = Schema.Struct({ version: Schema.Literal(1), type: Schema.Literal("providerStatuses"), payload: ServerConfigProviderStatusesPayload, }); export type ServerConfigStreamProviderStatusesEvent = typeof ServerConfigStreamProviderStatusesEvent.Type; export const ServerConfigStreamSettingsUpdatedEvent = Schema.Struct({ version: Schema.Literal(1), type: Schema.Literal("settingsUpdated"), payload: ServerConfigSettingsUpdatedPayload, }); export type ServerConfigStreamSettingsUpdatedEvent = typeof ServerConfigStreamSettingsUpdatedEvent.Type; export const ServerConfigEnvironmentThemesUpdatedPayload = Schema.Struct({ /** The full published set; empty once the machine publishes none. */ themes: Schema.Array(EnvironmentTheme), }); export type ServerConfigEnvironmentThemesUpdatedPayload = typeof ServerConfigEnvironmentThemesUpdatedPayload.Type; export const ServerConfigStreamEnvironmentThemesUpdatedEvent = Schema.Struct({ version: Schema.Literal(1), type: Schema.Literal("environmentThemesUpdated"), payload: ServerConfigEnvironmentThemesUpdatedPayload, }); export type ServerConfigStreamEnvironmentThemesUpdatedEvent = typeof ServerConfigStreamEnvironmentThemesUpdatedEvent.Type; export const ServerConfigUsageLimitSourcesUpdatedPayload = Schema.Struct({ /** The full set; empty once no source is configured. */ sources: UsageLimitSourceSnapshots, }); export type ServerConfigUsageLimitSourcesUpdatedPayload = typeof ServerConfigUsageLimitSourcesUpdatedPayload.Type; export const ServerConfigStreamUsageLimitSourcesUpdatedEvent = Schema.Struct({ version: Schema.Literal(1), type: Schema.Literal("usageLimitSourcesUpdated"), payload: ServerConfigUsageLimitSourcesUpdatedPayload, }); export type ServerConfigStreamUsageLimitSourcesUpdatedEvent = typeof ServerConfigStreamUsageLimitSourcesUpdatedEvent.Type; export const ServerConfigStreamEvent = Schema.Union([ ServerConfigStreamSnapshotEvent, ServerConfigStreamKeybindingsUpdatedEvent, ServerConfigStreamProviderStatusesEvent, ServerConfigStreamSettingsUpdatedEvent, ServerConfigStreamEnvironmentThemesUpdatedEvent, ServerConfigStreamUsageLimitSourcesUpdatedEvent, ]); export type ServerConfigStreamEvent = typeof ServerConfigStreamEvent.Type; /** Terminal selection recorded by the service launcher for one update. */ export const ServerSelfUpdateOutcome = Schema.Struct({ id: TrimmedNonEmptyString, fromVersion: TrimmedNonEmptyString, targetVersion: TrimmedNonEmptyString, status: Schema.Literals(["committed", "rolled-back", "failed"]), reason: Schema.optionalKey(TrimmedNonEmptyString), }); export type ServerSelfUpdateOutcome = typeof ServerSelfUpdateOutcome.Type; export const ServerLifecycleReadyPayload = Schema.Struct({ at: IsoDateTime, environment: ExecutionEnvironmentDescriptor, /** Present when this process resumed a launcher-managed update. */ updateOutcome: Schema.optionalKey(ServerSelfUpdateOutcome), }); export type ServerLifecycleReadyPayload = typeof ServerLifecycleReadyPayload.Type; export const ServerLifecycleWelcomePayload = Schema.Struct({ environment: ExecutionEnvironmentDescriptor, cwd: TrimmedNonEmptyString, projectName: TrimmedNonEmptyString, bootstrapStatus: Schema.optional(Schema.Literals(["pending", "complete"])), bootstrapProjectId: Schema.optional(ProjectId), bootstrapThreadId: Schema.optional(ThreadId), bootstrapProjectCreated: Schema.optional(Schema.Boolean), bootstrapThreadCreated: Schema.optional(Schema.Boolean), }); export type ServerLifecycleWelcomePayload = typeof ServerLifecycleWelcomePayload.Type; export const ServerLifecycleStreamWelcomeEvent = Schema.Struct({ version: Schema.Literal(1), sequence: NonNegativeInt, type: Schema.Literal("welcome"), payload: ServerLifecycleWelcomePayload, }); export type ServerLifecycleStreamWelcomeEvent = typeof ServerLifecycleStreamWelcomeEvent.Type; export const ServerLifecycleStreamReadyEvent = Schema.Struct({ version: Schema.Literal(1), sequence: NonNegativeInt, type: Schema.Literal("ready"), payload: ServerLifecycleReadyPayload, }); export type ServerLifecycleStreamReadyEvent = typeof ServerLifecycleStreamReadyEvent.Type; export const ServerLifecycleStreamEvent = Schema.Union([ ServerLifecycleStreamWelcomeEvent, ServerLifecycleStreamReadyEvent, ]); export type ServerLifecycleStreamEvent = typeof ServerLifecycleStreamEvent.Type; export const ServerProviderUpdatedPayload = Schema.Struct({ providers: ServerProviders, }); export type ServerProviderUpdatedPayload = typeof ServerProviderUpdatedPayload.Type; export const ServerProviderUpdateInput = Schema.Struct({ provider: ProviderDriverKind, instanceId: Schema.optionalKey(ProviderInstanceId), }); export type ServerProviderUpdateInput = typeof ServerProviderUpdateInput.Type; export class ServerProviderUpdateError extends Schema.TaggedError()( "ServerProviderUpdateError", { provider: ProviderDriverKind, reason: TrimmedNonEmptyString, cause: Schema.optional(Schema.Defect()), }, ) { override get message(): string { return `Provider update failed for ${this.provider}: ${this.reason}`; } } export const ServerSelfUpdateInput = Schema.Struct({ /** Exact npm version of the `t3` package to install (never a dist-tag, so the server and the acknowledging client agree on what was requested). */ targetVersion: TrimmedNonEmptyString, /** Opt-in recovery for provider turns that are running when the server hands off to its replacement. Missing and false keep restart behavior conservative under version skew. */ continueRunningThreads: Schema.optionalKey(Schema.Boolean), }); export type ServerSelfUpdateInput = typeof ServerSelfUpdateInput.Type; /** Acknowledgement that the update artifact is installed and the server is about to restart into it — the connection will drop moments later. */ export const ServerSelfUpdateResult = Schema.Struct({ targetVersion: TrimmedNonEmptyString, method: ServerSelfUpdateMethod, /** Launcher-generated correlation ID. Absent when talking to older servers. */ updateId: Schema.optionalKey(TrimmedNonEmptyString), /** Desktop preparation token. Present only for the desktop-app method. */ desktopUpdateToken: Schema.optionalKey(TrimmedNonEmptyString), }); export type ServerSelfUpdateResult = typeof ServerSelfUpdateResult.Type; export const DesktopUpdateCommitInput = Schema.Struct({ requestId: TrimmedNonEmptyString, }); export type DesktopUpdateCommitInput = typeof DesktopUpdateCommitInput.Type; export const ServerSelfUpdateProgressStage = Schema.Literals(["downloading", "installing"]); export type ServerSelfUpdateProgressStage = typeof ServerSelfUpdateProgressStage.Type; export const ServerSelfUpdateProgressEvent = Schema.Union([ Schema.Struct({ type: Schema.Literal("progress"), stage: ServerSelfUpdateProgressStage, }), Schema.Struct({ type: Schema.Literal("complete"), result: ServerSelfUpdateResult, }), ]); export type ServerSelfUpdateProgressEvent = typeof ServerSelfUpdateProgressEvent.Type; export class ServerSelfUpdateError extends Schema.TaggedError()( "ServerSelfUpdateError", { reason: TrimmedNonEmptyString, cause: Schema.optional(Schema.Defect()), }, ) { override get message(): string { return `Server update failed: ${this.reason}`; } }