import * as Effect from "effect/Effect";import * as Schema from "effect/Schema";import * as SchemaIssue from "effect/SchemaIssue";import * as SchemaTransformation from "effect/SchemaTransformation";import * as Struct from "effect/Struct";import { ProviderOptionSelections } from "./model.ts";import { RepositoryIdentity, ThreadEnvMode } from "./environment.ts";import { ApprovalRequestId, CheckpointRef, CommandId, EventId, IsoDateTime, MessageId, NonNegativeInt, PositiveInt, ProjectId, ProviderItemId, ThreadId, TrimmedNonEmptyString, TrimmedString, TurnId,} from "./baseSchemas.ts";import { ProviderInstanceId } from "./providerInstance.ts"; export const ORCHESTRATION_WS_METHODS = { dispatchCommand: "orchestration.dispatchCommand", getWorkflowScript: "orchestration.getWorkflowScript", getTurnDiff: "orchestration.getTurnDiff", getFullThreadDiff: "orchestration.getFullThreadDiff", searchThreads: "orchestration.searchThreads", getArchivedShellSnapshot: "orchestration.getArchivedShellSnapshot", subscribeShell: "orchestration.subscribeShell", subscribeThread: "orchestration.subscribeThread",} as const; export const ProviderApprovalPolicy = Schema.Literals([ "untrusted", "on-failure", "on-request", "never",]);export type ProviderApprovalPolicy = typeof ProviderApprovalPolicy.Type;export const ProviderSandboxMode = Schema.Literals([ "read-only", "workspace-write", "danger-full-access",]);export type ProviderSandboxMode = typeof ProviderSandboxMode.Type; /** * `ModelSelection` — selection of a model on a configured provider instance. * * The routing key is `instanceId` (a user-defined slug identifying one * configured provider instance). Drivers, credentials, working-directory * bindings, and any other per-instance state are recovered from the * runtime registry via the instance id. * * Wire legacy: persisted selections produced before the driver/instance * split carried a `provider: <driver-id>` field instead. The schema absorbs * that shape via a pre-decoding transform — `{provider, model}` is promoted * to `{instanceId: defaultInstanceIdForDriver(provider), model}`. No * post-decode compatibility code lives in the runtime; the transform is the * only compat surface. */const ModelSelectionWire = Schema.Struct({ instanceId: ProviderInstanceId, model: TrimmedNonEmptyString, options: Schema.optionalKey(ProviderOptionSelections),}); // Source shape for persisted legacy payloads. Fields are typed as// `Schema.Unknown` so malformed drafts still make it into the transform and// fail validation through the target schema (with proper error messages)// rather than at the source-struct layer where the error is less actionable.const ModelSelectionSource = Schema.Struct({ provider: Schema.optional(Schema.Unknown), instanceId: Schema.optional(Schema.Unknown), model: Schema.Unknown, options: Schema.optional(Schema.Unknown),}); export const ModelSelection = ModelSelectionSource.pipe( Schema.decodeTo( ModelSelectionWire, SchemaTransformation.transformOrFail({ decode: (raw) => { // Resolve the routing key: prefer an explicit `instanceId`; fall // back to promoting the legacy `provider` slug (the canonical // `defaultInstanceIdForDriver` mapping) so persisted rollout-era // payloads decode without data loss. The target schema brands the // string as `ProviderInstanceId`. const instanceIdSource = raw.instanceId !== undefined ? raw.instanceId : typeof raw.provider === "string" ? raw.provider : undefined; const base: Record<string, unknown> = { instanceId: instanceIdSource, model: raw.model, }; if (raw.options !== undefined) base.options = raw.options; return Effect.succeed(base as typeof ModelSelectionWire.Encoded); }, encode: (value) => { const base: Record<string, unknown> = { model: value.model, instanceId: value.instanceId, }; if (value.options !== undefined) base.options = value.options; return Effect.succeed(base as typeof ModelSelectionSource.Encoded); }, }), ),);export type ModelSelection = typeof ModelSelection.Type; export const RuntimeMode = Schema.Literals([ "approval-required", "auto-accept-edits", "auto", "full-access",]);export type RuntimeMode = typeof RuntimeMode.Type;export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access";export const ProviderInteractionMode = Schema.Literals(["default", "plan"]);export type ProviderInteractionMode = typeof ProviderInteractionMode.Type;export const DEFAULT_PROVIDER_INTERACTION_MODE: ProviderInteractionMode = "default";export const ProviderRequestKind = Schema.Literals(["command", "file-read", "file-change"]);export type ProviderRequestKind = typeof ProviderRequestKind.Type;export const AssistantDeliveryMode = Schema.Literals(["buffered", "streaming"]);export type AssistantDeliveryMode = typeof AssistantDeliveryMode.Type;export const ProviderApprovalDecision = Schema.Literals([ "accept", "acceptForSession", "decline", "cancel",]);export type ProviderApprovalDecision = typeof ProviderApprovalDecision.Type;export const ProviderUserInputAnswers = Schema.Record(Schema.String, Schema.Unknown);export type ProviderUserInputAnswers = typeof ProviderUserInputAnswers.Type; export const PROVIDER_SEND_TURN_MAX_INPUT_CHARS = 120_000;export const PROVIDER_SEND_TURN_MAX_ATTACHMENTS = 8;export const PROVIDER_SEND_TURN_MAX_IMAGE_BYTES = 10 * 1024 * 1024;export const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES = [ "image/gif", "image/jpeg", "image/png", "image/webp",] as const;const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET = new Set<string>( PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES,); /** Whether a pasted or picked image mime type can be sent on a provider turn. */export function isProviderSendTurnSupportedImageMimeType(mimeType: string): boolean { return PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET.has(mimeType.toLowerCase());}const PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS = 14_000_000;const CHAT_ATTACHMENT_ID_MAX_CHARS = 128;// Correlation id is command id by design in this model.export const CorrelationId = CommandId;export type CorrelationId = typeof CorrelationId.Type; const ChatAttachmentId = TrimmedNonEmptyString.check( Schema.isMaxLength(CHAT_ATTACHMENT_ID_MAX_CHARS), Schema.isPattern(/^[a-z0-9_-]+$/i),);export type ChatAttachmentId = typeof ChatAttachmentId.Type; export const ChatImageAttachment = Schema.Struct({ type: Schema.Literal("image"), id: ChatAttachmentId, name: TrimmedNonEmptyString.check(Schema.isMaxLength(255)), mimeType: TrimmedNonEmptyString.check(Schema.isMaxLength(100), Schema.isPattern(/^image\//i)), sizeBytes: NonNegativeInt.check(Schema.isLessThanOrEqualTo(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES)),});export type ChatImageAttachment = typeof ChatImageAttachment.Type; const UploadChatImageAttachment = Schema.Struct({ type: Schema.Literal("image"), name: TrimmedNonEmptyString.check(Schema.isMaxLength(255)), mimeType: TrimmedNonEmptyString.check(Schema.isMaxLength(100), Schema.isPattern(/^image\//i)), sizeBytes: NonNegativeInt.check(Schema.isLessThanOrEqualTo(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES)), dataUrl: TrimmedNonEmptyString.check( Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS), ),});export type UploadChatImageAttachment = typeof UploadChatImageAttachment.Type; export const ChatAttachment = Schema.Union([ChatImageAttachment]);export type ChatAttachment = typeof ChatAttachment.Type;const UploadChatAttachment = Schema.Union([UploadChatImageAttachment]);export type UploadChatAttachment = typeof UploadChatAttachment.Type; export const ProjectScriptIcon = Schema.Literals([ "play", "test", "lint", "configure", "build", "debug",]);export type ProjectScriptIcon = typeof ProjectScriptIcon.Type; export const ProjectScript = Schema.Struct({ id: TrimmedNonEmptyString, name: TrimmedNonEmptyString, command: TrimmedNonEmptyString, icon: ProjectScriptIcon, runOnWorktreeCreate: Schema.Boolean, /** * URL to open in the in-app browser preview when this script runs (or * when the user explicitly requests a preview). Optional; only honored on * the desktop build. */ previewUrl: Schema.optional(TrimmedNonEmptyString), /** * When true, automatically open the preview panel pointed at `previewUrl` * the moment this script starts. Ignored without `previewUrl` or on web. */ autoOpenPreview: Schema.optional(Schema.Boolean),});export type ProjectScript = typeof ProjectScript.Type; export const ProjectFaviconPath = TrimmedNonEmptyString.check( Schema.isMaxLength(1024), Schema.isPattern(/\.(?:avif|gif|ico|jpe?g|png|svg|webp)$/i),);export type ProjectFaviconPath = typeof ProjectFaviconPath.Type; export const OrchestrationProject = Schema.Struct({ id: ProjectId, title: TrimmedNonEmptyString, workspaceRoot: TrimmedNonEmptyString, repositoryIdentity: Schema.optional(Schema.NullOr(RepositoryIdentity)), defaultModelSelection: Schema.NullOr(ModelSelection), // Per-project override for where new threads start. Null/absent means // "no override": clients fall back to t3.json, then the global setting. defaultThreadEnvMode: Schema.optional(Schema.NullOr(ThreadEnvMode)), // Optional on the wire so cached snapshots from older servers still decode. faviconPath: Schema.optional(Schema.NullOr(ProjectFaviconPath)), scripts: Schema.Array(ProjectScript), createdAt: IsoDateTime, updatedAt: IsoDateTime, deletedAt: Schema.NullOr(IsoDateTime),});export type OrchestrationProject = typeof OrchestrationProject.Type; export const OrchestrationMessageRole = Schema.Literals(["user", "assistant", "system"]);export type OrchestrationMessageRole = typeof OrchestrationMessageRole.Type; export const OrchestrationMessage = Schema.Struct({ id: MessageId, role: OrchestrationMessageRole, text: Schema.String, attachments: Schema.optional(Schema.Array(ChatAttachment)), turnId: Schema.NullOr(TurnId), streaming: Schema.Boolean, createdAt: IsoDateTime, updatedAt: IsoDateTime,});export type OrchestrationMessage = typeof OrchestrationMessage.Type; export const OrchestrationProposedPlanId = TrimmedNonEmptyString;export type OrchestrationProposedPlanId = typeof OrchestrationProposedPlanId.Type; export const OrchestrationProposedPlan = Schema.Struct({ id: OrchestrationProposedPlanId, turnId: Schema.NullOr(TurnId), planMarkdown: TrimmedNonEmptyString, implementedAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), implementationThreadId: Schema.NullOr(ThreadId).pipe( Schema.withDecodingDefault(Effect.succeed(null)), ), createdAt: IsoDateTime, updatedAt: IsoDateTime,});export type OrchestrationProposedPlan = typeof OrchestrationProposedPlan.Type; const SourceProposedPlanReference = Schema.Struct({ threadId: ThreadId, planId: OrchestrationProposedPlanId,}); export const OrchestrationSessionStatus = Schema.Literals([ "idle", "starting", "running", "ready", "interrupted", "stopped", "error",]);export type OrchestrationSessionStatus = typeof OrchestrationSessionStatus.Type; export const OrchestrationSession = Schema.Struct({ threadId: ThreadId, status: OrchestrationSessionStatus, providerName: Schema.NullOr(TrimmedNonEmptyString), providerInstanceId: Schema.optional(ProviderInstanceId), runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE))), activeTurnId: Schema.NullOr(TurnId), lastError: Schema.NullOr(TrimmedNonEmptyString), updatedAt: IsoDateTime,});export type OrchestrationSession = typeof OrchestrationSession.Type; export const OrchestrationCheckpointFile = Schema.Struct({ path: TrimmedNonEmptyString, kind: TrimmedNonEmptyString, additions: NonNegativeInt, deletions: NonNegativeInt,});export type OrchestrationCheckpointFile = typeof OrchestrationCheckpointFile.Type; export const OrchestrationCheckpointStatus = Schema.Literals(["ready", "missing", "error"]);export type OrchestrationCheckpointStatus = typeof OrchestrationCheckpointStatus.Type; export const OrchestrationCheckpointSummary = Schema.Struct({ turnId: TurnId, checkpointTurnCount: NonNegativeInt, checkpointRef: CheckpointRef, status: OrchestrationCheckpointStatus, files: Schema.Array(OrchestrationCheckpointFile), assistantMessageId: Schema.NullOr(MessageId), completedAt: IsoDateTime,});export type OrchestrationCheckpointSummary = typeof OrchestrationCheckpointSummary.Type; export const OrchestrationThreadActivityTone = Schema.Literals([ "info", "tool", "approval", "error",]);export type OrchestrationThreadActivityTone = typeof OrchestrationThreadActivityTone.Type; export const OrchestrationThreadActivity = Schema.Struct({ id: EventId, tone: OrchestrationThreadActivityTone, kind: TrimmedNonEmptyString, summary: TrimmedNonEmptyString, payload: Schema.Unknown, turnId: Schema.NullOr(TurnId), sequence: Schema.optional(NonNegativeInt), createdAt: IsoDateTime,});export type OrchestrationThreadActivity = typeof OrchestrationThreadActivity.Type; const OrchestrationLatestTurnState = Schema.Literals([ "running", "interrupted", "completed", "error",]);export type OrchestrationLatestTurnState = typeof OrchestrationLatestTurnState.Type; export const OrchestrationLatestTurn = Schema.Struct({ turnId: TurnId, state: OrchestrationLatestTurnState, requestedAt: IsoDateTime, startedAt: Schema.NullOr(IsoDateTime), completedAt: Schema.NullOr(IsoDateTime), assistantMessageId: Schema.NullOr(MessageId), sourceProposedPlan: Schema.optional(SourceProposedPlanReference),});export type OrchestrationLatestTurn = typeof OrchestrationLatestTurn.Type; export const ThreadTitleRegeneration = Schema.Struct({ requestId: CommandId, startedAt: IsoDateTime,});export type ThreadTitleRegeneration = typeof ThreadTitleRegeneration.Type; export const OrchestrationThread = Schema.Struct({ id: ThreadId, projectId: ProjectId, title: TrimmedNonEmptyString, modelSelection: ModelSelection, runtimeMode: RuntimeMode, interactionMode: ProviderInteractionMode.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)), ), branch: Schema.NullOr(TrimmedNonEmptyString), worktreePath: Schema.NullOr(TrimmedNonEmptyString), latestTurn: Schema.NullOr(OrchestrationLatestTurn), createdAt: IsoDateTime, updatedAt: IsoDateTime, archivedAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), settledOverride: Schema.NullOr(Schema.Literals(["settled", "active"])).pipe( Schema.withDecodingDefault(Effect.succeed(null)), ), settledAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), // Snooze is an overlay on the active lifecycle, not a fourth destination: // a snoozed thread stays "active" in the model and is only suppressed from // the inbox until snoozedUntil passes (or the thread raises its hand). // Optional so payloads from pre-snooze servers still decode. snoozedUntil: Schema.optional(Schema.NullOr(IsoDateTime)), snoozedAt: Schema.optional(Schema.NullOr(IsoDateTime)), // A pin overrides the settled/snoozed lifecycle: while pinnedAt is set the // thread renders in the pinned block and never classifies into a shelf. // Optional so payloads from pre-pinning servers still decode. pinnedAt: Schema.optional(Schema.NullOr(IsoDateTime)), // Fractional index for user-arranged pinned order. Keyed threads sort by // string comparison ahead of keyless ones (which keep creation order), so // servers never need each other's threads to agree on the merged list. // Optional so payloads from pre-reorder servers still decode. pinOrderKey: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), // Pending-only state. Optional so older servers remain compatible. titleRegeneration: Schema.optional(Schema.NullOr(ThreadTitleRegeneration)), deletedAt: Schema.NullOr(IsoDateTime), messages: Schema.Array(OrchestrationMessage), proposedPlans: Schema.Array(OrchestrationProposedPlan).pipe( Schema.withDecodingDefault(Effect.succeed([])), ), activities: Schema.Array(OrchestrationThreadActivity), checkpoints: Schema.Array(OrchestrationCheckpointSummary), session: Schema.NullOr(OrchestrationSession),});export type OrchestrationThread = typeof OrchestrationThread.Type; export const OrchestrationReadModel = Schema.Struct({ snapshotSequence: NonNegativeInt, projects: Schema.Array(OrchestrationProject), threads: Schema.Array(OrchestrationThread), updatedAt: IsoDateTime,});export type OrchestrationReadModel = typeof OrchestrationReadModel.Type; export const OrchestrationProjectShell = Schema.Struct({ id: ProjectId, title: TrimmedNonEmptyString, workspaceRoot: TrimmedNonEmptyString, repositoryIdentity: Schema.optional(Schema.NullOr(RepositoryIdentity)), defaultModelSelection: Schema.NullOr(ModelSelection), defaultThreadEnvMode: Schema.optional(Schema.NullOr(ThreadEnvMode)), // Optional on the wire so cached snapshots from older servers still decode. faviconPath: Schema.optional(Schema.NullOr(ProjectFaviconPath)), scripts: Schema.Array(ProjectScript), createdAt: IsoDateTime, updatedAt: IsoDateTime,});export type OrchestrationProjectShell = typeof OrchestrationProjectShell.Type; export const OrchestrationThreadShell = Schema.Struct({ id: ThreadId, projectId: ProjectId, title: TrimmedNonEmptyString, modelSelection: ModelSelection, runtimeMode: RuntimeMode, interactionMode: ProviderInteractionMode.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)), ), branch: Schema.NullOr(TrimmedNonEmptyString), worktreePath: Schema.NullOr(TrimmedNonEmptyString), latestTurn: Schema.NullOr(OrchestrationLatestTurn), createdAt: IsoDateTime, updatedAt: IsoDateTime, archivedAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), settledOverride: Schema.NullOr(Schema.Literals(["settled", "active"])).pipe( Schema.withDecodingDefault(Effect.succeed(null)), ), settledAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), snoozedUntil: Schema.optional(Schema.NullOr(IsoDateTime)), snoozedAt: Schema.optional(Schema.NullOr(IsoDateTime)), pinnedAt: Schema.optional(Schema.NullOr(IsoDateTime)), pinOrderKey: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), titleRegeneration: Schema.optional(Schema.NullOr(ThreadTitleRegeneration)), session: Schema.NullOr(OrchestrationSession), latestUserMessageAt: Schema.NullOr(IsoDateTime), hasPendingApprovals: Schema.Boolean, hasPendingUserInput: Schema.Boolean, hasActionableProposedPlan: Schema.Boolean, /** * Native background work alive after the turn settles: "working" while * subagents/workflows run, "monitoring" when watch loops are the only * live work. Optional so old servers/clients interop; absent = none. */ backgroundLiveness: Schema.optional(Schema.NullOr(Schema.Literals(["working", "monitoring"]))), /** * Current plan step while a turn runs, for the Working indicators * (sidebar row, in-chat working line). Cleared when the turn settles — * never persists as stale UI. Optional so old servers/clients interop. */ planProgress: Schema.optional( Schema.NullOr( Schema.Struct({ step: TrimmedNonEmptyString, completedSteps: NonNegativeInt, totalSteps: NonNegativeInt, }), ), ),});export type OrchestrationThreadShell = typeof OrchestrationThreadShell.Type; export const OrchestrationShellSnapshot = Schema.Struct({Lines 1–500 of 1739. Load more
import * as Effect from "effect/Effect"; import * as Schema from "effect/Schema"; import * as SchemaIssue from "effect/SchemaIssue"; import * as SchemaTransformation from "effect/SchemaTransformation"; import * as Struct from "effect/Struct"; import { ProviderOptionSelections } from "./model.ts"; import { RepositoryIdentity, ThreadEnvMode } from "./environment.ts"; import { ApprovalRequestId, CheckpointRef, CommandId, EventId, IsoDateTime, MessageId, NonNegativeInt, PositiveInt, ProjectId, ProviderItemId, ThreadId, TrimmedNonEmptyString, TrimmedString, TurnId, } from "./baseSchemas.ts"; import { ProviderInstanceId } from "./providerInstance.ts"; export const ORCHESTRATION_WS_METHODS = { dispatchCommand: "orchestration.dispatchCommand", getWorkflowScript: "orchestration.getWorkflowScript", getTurnDiff: "orchestration.getTurnDiff", getFullThreadDiff: "orchestration.getFullThreadDiff", searchThreads: "orchestration.searchThreads", getArchivedShellSnapshot: "orchestration.getArchivedShellSnapshot", subscribeShell: "orchestration.subscribeShell", subscribeThread: "orchestration.subscribeThread", } as const; export const ProviderApprovalPolicy = Schema.Literals([ "untrusted", "on-failure", "on-request", "never", ]); export type ProviderApprovalPolicy = typeof ProviderApprovalPolicy.Type; export const ProviderSandboxMode = Schema.Literals([ "read-only", "workspace-write", "danger-full-access", ]); export type ProviderSandboxMode = typeof ProviderSandboxMode.Type; /** * `ModelSelection` — selection of a model on a configured provider instance. * * The routing key is `instanceId` (a user-defined slug identifying one * configured provider instance). Drivers, credentials, working-directory * bindings, and any other per-instance state are recovered from the * runtime registry via the instance id. * * Wire legacy: persisted selections produced before the driver/instance * split carried a `provider: <driver-id>` field instead. The schema absorbs * that shape via a pre-decoding transform — `{provider, model}` is promoted * to `{instanceId: defaultInstanceIdForDriver(provider), model}`. No * post-decode compatibility code lives in the runtime; the transform is the * only compat surface. */ const ModelSelectionWire = Schema.Struct({ instanceId: ProviderInstanceId, model: TrimmedNonEmptyString, options: Schema.optionalKey(ProviderOptionSelections), }); // Source shape for persisted legacy payloads. Fields are typed as // `Schema.Unknown` so malformed drafts still make it into the transform and // fail validation through the target schema (with proper error messages) // rather than at the source-struct layer where the error is less actionable. const ModelSelectionSource = Schema.Struct({ provider: Schema.optional(Schema.Unknown), instanceId: Schema.optional(Schema.Unknown), model: Schema.Unknown, options: Schema.optional(Schema.Unknown), }); export const ModelSelection = ModelSelectionSource.pipe( Schema.decodeTo( ModelSelectionWire, SchemaTransformation.transformOrFail({ decode: (raw) => { // Resolve the routing key: prefer an explicit `instanceId`; fall // back to promoting the legacy `provider` slug (the canonical // `defaultInstanceIdForDriver` mapping) so persisted rollout-era // payloads decode without data loss. The target schema brands the // string as `ProviderInstanceId`. const instanceIdSource = raw.instanceId !== undefined ? raw.instanceId : typeof raw.provider === "string" ? raw.provider : undefined; const base: Record<string, unknown> = { instanceId: instanceIdSource, model: raw.model, }; if (raw.options !== undefined) base.options = raw.options; return Effect.succeed(base as typeof ModelSelectionWire.Encoded); }, encode: (value) => { const base: Record<string, unknown> = { model: value.model, instanceId: value.instanceId, }; if (value.options !== undefined) base.options = value.options; return Effect.succeed(base as typeof ModelSelectionSource.Encoded); }, }), ), ); export type ModelSelection = typeof ModelSelection.Type; export const RuntimeMode = Schema.Literals([ "approval-required", "auto-accept-edits", "auto", "full-access", ]); export type RuntimeMode = typeof RuntimeMode.Type; export const DEFAULT_RUNTIME_MODE: RuntimeMode = "full-access"; export const ProviderInteractionMode = Schema.Literals(["default", "plan"]); export type ProviderInteractionMode = typeof ProviderInteractionMode.Type; export const DEFAULT_PROVIDER_INTERACTION_MODE: ProviderInteractionMode = "default"; export const ProviderRequestKind = Schema.Literals(["command", "file-read", "file-change"]); export type ProviderRequestKind = typeof ProviderRequestKind.Type; export const AssistantDeliveryMode = Schema.Literals(["buffered", "streaming"]); export type AssistantDeliveryMode = typeof AssistantDeliveryMode.Type; export const ProviderApprovalDecision = Schema.Literals([ "accept", "acceptForSession", "decline", "cancel", ]); export type ProviderApprovalDecision = typeof ProviderApprovalDecision.Type; export const ProviderUserInputAnswers = Schema.Record(Schema.String, Schema.Unknown); export type ProviderUserInputAnswers = typeof ProviderUserInputAnswers.Type; export const PROVIDER_SEND_TURN_MAX_INPUT_CHARS = 120_000; export const PROVIDER_SEND_TURN_MAX_ATTACHMENTS = 8; export const PROVIDER_SEND_TURN_MAX_IMAGE_BYTES = 10 * 1024 * 1024; export const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES = [ "image/gif", "image/jpeg", "image/png", "image/webp", ] as const; const PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET = new Set<string>( PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPES, ); /** Whether a pasted or picked image mime type can be sent on a provider turn. */ export function isProviderSendTurnSupportedImageMimeType(mimeType: string): boolean { return PROVIDER_SEND_TURN_SUPPORTED_IMAGE_MIME_TYPE_SET.has(mimeType.toLowerCase()); } const PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS = 14_000_000; const CHAT_ATTACHMENT_ID_MAX_CHARS = 128; // Correlation id is command id by design in this model. export const CorrelationId = CommandId; export type CorrelationId = typeof CorrelationId.Type; const ChatAttachmentId = TrimmedNonEmptyString.check( Schema.isMaxLength(CHAT_ATTACHMENT_ID_MAX_CHARS), Schema.isPattern(/^[a-z0-9_-]+$/i), ); export type ChatAttachmentId = typeof ChatAttachmentId.Type; export const ChatImageAttachment = Schema.Struct({ type: Schema.Literal("image"), id: ChatAttachmentId, name: TrimmedNonEmptyString.check(Schema.isMaxLength(255)), mimeType: TrimmedNonEmptyString.check(Schema.isMaxLength(100), Schema.isPattern(/^image\//i)), sizeBytes: NonNegativeInt.check(Schema.isLessThanOrEqualTo(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES)), }); export type ChatImageAttachment = typeof ChatImageAttachment.Type; const UploadChatImageAttachment = Schema.Struct({ type: Schema.Literal("image"), name: TrimmedNonEmptyString.check(Schema.isMaxLength(255)), mimeType: TrimmedNonEmptyString.check(Schema.isMaxLength(100), Schema.isPattern(/^image\//i)), sizeBytes: NonNegativeInt.check(Schema.isLessThanOrEqualTo(PROVIDER_SEND_TURN_MAX_IMAGE_BYTES)), dataUrl: TrimmedNonEmptyString.check( Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_IMAGE_DATA_URL_CHARS), ), }); export type UploadChatImageAttachment = typeof UploadChatImageAttachment.Type; export const ChatAttachment = Schema.Union([ChatImageAttachment]); export type ChatAttachment = typeof ChatAttachment.Type; const UploadChatAttachment = Schema.Union([UploadChatImageAttachment]); export type UploadChatAttachment = typeof UploadChatAttachment.Type; export const ProjectScriptIcon = Schema.Literals([ "play", "test", "lint", "configure", "build", "debug", ]); export type ProjectScriptIcon = typeof ProjectScriptIcon.Type; export const ProjectScript = Schema.Struct({ id: TrimmedNonEmptyString, name: TrimmedNonEmptyString, command: TrimmedNonEmptyString, icon: ProjectScriptIcon, runOnWorktreeCreate: Schema.Boolean, /** * URL to open in the in-app browser preview when this script runs (or * when the user explicitly requests a preview). Optional; only honored on * the desktop build. */ previewUrl: Schema.optional(TrimmedNonEmptyString), /** * When true, automatically open the preview panel pointed at `previewUrl` * the moment this script starts. Ignored without `previewUrl` or on web. */ autoOpenPreview: Schema.optional(Schema.Boolean), }); export type ProjectScript = typeof ProjectScript.Type; export const ProjectFaviconPath = TrimmedNonEmptyString.check( Schema.isMaxLength(1024), Schema.isPattern(/\.(?:avif|gif|ico|jpe?g|png|svg|webp)$/i), ); export type ProjectFaviconPath = typeof ProjectFaviconPath.Type; export const OrchestrationProject = Schema.Struct({ id: ProjectId, title: TrimmedNonEmptyString, workspaceRoot: TrimmedNonEmptyString, repositoryIdentity: Schema.optional(Schema.NullOr(RepositoryIdentity)), defaultModelSelection: Schema.NullOr(ModelSelection), // Per-project override for where new threads start. Null/absent means // "no override": clients fall back to t3.json, then the global setting. defaultThreadEnvMode: Schema.optional(Schema.NullOr(ThreadEnvMode)), // Optional on the wire so cached snapshots from older servers still decode. faviconPath: Schema.optional(Schema.NullOr(ProjectFaviconPath)), scripts: Schema.Array(ProjectScript), createdAt: IsoDateTime, updatedAt: IsoDateTime, deletedAt: Schema.NullOr(IsoDateTime), }); export type OrchestrationProject = typeof OrchestrationProject.Type; export const OrchestrationMessageRole = Schema.Literals(["user", "assistant", "system"]); export type OrchestrationMessageRole = typeof OrchestrationMessageRole.Type; export const OrchestrationMessage = Schema.Struct({ id: MessageId, role: OrchestrationMessageRole, text: Schema.String, attachments: Schema.optional(Schema.Array(ChatAttachment)), turnId: Schema.NullOr(TurnId), streaming: Schema.Boolean, createdAt: IsoDateTime, updatedAt: IsoDateTime, }); export type OrchestrationMessage = typeof OrchestrationMessage.Type; export const OrchestrationProposedPlanId = TrimmedNonEmptyString; export type OrchestrationProposedPlanId = typeof OrchestrationProposedPlanId.Type; export const OrchestrationProposedPlan = Schema.Struct({ id: OrchestrationProposedPlanId, turnId: Schema.NullOr(TurnId), planMarkdown: TrimmedNonEmptyString, implementedAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), implementationThreadId: Schema.NullOr(ThreadId).pipe( Schema.withDecodingDefault(Effect.succeed(null)), ), createdAt: IsoDateTime, updatedAt: IsoDateTime, }); export type OrchestrationProposedPlan = typeof OrchestrationProposedPlan.Type; const SourceProposedPlanReference = Schema.Struct({ threadId: ThreadId, planId: OrchestrationProposedPlanId, }); export const OrchestrationSessionStatus = Schema.Literals([ "idle", "starting", "running", "ready", "interrupted", "stopped", "error", ]); export type OrchestrationSessionStatus = typeof OrchestrationSessionStatus.Type; export const OrchestrationSession = Schema.Struct({ threadId: ThreadId, status: OrchestrationSessionStatus, providerName: Schema.NullOr(TrimmedNonEmptyString), providerInstanceId: Schema.optional(ProviderInstanceId), runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE))), activeTurnId: Schema.NullOr(TurnId), lastError: Schema.NullOr(TrimmedNonEmptyString), updatedAt: IsoDateTime, }); export type OrchestrationSession = typeof OrchestrationSession.Type; export const OrchestrationCheckpointFile = Schema.Struct({ path: TrimmedNonEmptyString, kind: TrimmedNonEmptyString, additions: NonNegativeInt, deletions: NonNegativeInt, }); export type OrchestrationCheckpointFile = typeof OrchestrationCheckpointFile.Type; export const OrchestrationCheckpointStatus = Schema.Literals(["ready", "missing", "error"]); export type OrchestrationCheckpointStatus = typeof OrchestrationCheckpointStatus.Type; export const OrchestrationCheckpointSummary = Schema.Struct({ turnId: TurnId, checkpointTurnCount: NonNegativeInt, checkpointRef: CheckpointRef, status: OrchestrationCheckpointStatus, files: Schema.Array(OrchestrationCheckpointFile), assistantMessageId: Schema.NullOr(MessageId), completedAt: IsoDateTime, }); export type OrchestrationCheckpointSummary = typeof OrchestrationCheckpointSummary.Type; export const OrchestrationThreadActivityTone = Schema.Literals([ "info", "tool", "approval", "error", ]); export type OrchestrationThreadActivityTone = typeof OrchestrationThreadActivityTone.Type; export const OrchestrationThreadActivity = Schema.Struct({ id: EventId, tone: OrchestrationThreadActivityTone, kind: TrimmedNonEmptyString, summary: TrimmedNonEmptyString, payload: Schema.Unknown, turnId: Schema.NullOr(TurnId), sequence: Schema.optional(NonNegativeInt), createdAt: IsoDateTime, }); export type OrchestrationThreadActivity = typeof OrchestrationThreadActivity.Type; const OrchestrationLatestTurnState = Schema.Literals([ "running", "interrupted", "completed", "error", ]); export type OrchestrationLatestTurnState = typeof OrchestrationLatestTurnState.Type; export const OrchestrationLatestTurn = Schema.Struct({ turnId: TurnId, state: OrchestrationLatestTurnState, requestedAt: IsoDateTime, startedAt: Schema.NullOr(IsoDateTime), completedAt: Schema.NullOr(IsoDateTime), assistantMessageId: Schema.NullOr(MessageId), sourceProposedPlan: Schema.optional(SourceProposedPlanReference), }); export type OrchestrationLatestTurn = typeof OrchestrationLatestTurn.Type; export const ThreadTitleRegeneration = Schema.Struct({ requestId: CommandId, startedAt: IsoDateTime, }); export type ThreadTitleRegeneration = typeof ThreadTitleRegeneration.Type; export const OrchestrationThread = Schema.Struct({ id: ThreadId, projectId: ProjectId, title: TrimmedNonEmptyString, modelSelection: ModelSelection, runtimeMode: RuntimeMode, interactionMode: ProviderInteractionMode.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)), ), branch: Schema.NullOr(TrimmedNonEmptyString), worktreePath: Schema.NullOr(TrimmedNonEmptyString), latestTurn: Schema.NullOr(OrchestrationLatestTurn), createdAt: IsoDateTime, updatedAt: IsoDateTime, archivedAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), settledOverride: Schema.NullOr(Schema.Literals(["settled", "active"])).pipe( Schema.withDecodingDefault(Effect.succeed(null)), ), settledAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), // Snooze is an overlay on the active lifecycle, not a fourth destination: // a snoozed thread stays "active" in the model and is only suppressed from // the inbox until snoozedUntil passes (or the thread raises its hand). // Optional so payloads from pre-snooze servers still decode. snoozedUntil: Schema.optional(Schema.NullOr(IsoDateTime)), snoozedAt: Schema.optional(Schema.NullOr(IsoDateTime)), // A pin overrides the settled/snoozed lifecycle: while pinnedAt is set the // thread renders in the pinned block and never classifies into a shelf. // Optional so payloads from pre-pinning servers still decode. pinnedAt: Schema.optional(Schema.NullOr(IsoDateTime)), // Fractional index for user-arranged pinned order. Keyed threads sort by // string comparison ahead of keyless ones (which keep creation order), so // servers never need each other's threads to agree on the merged list. // Optional so payloads from pre-reorder servers still decode. pinOrderKey: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), // Pending-only state. Optional so older servers remain compatible. titleRegeneration: Schema.optional(Schema.NullOr(ThreadTitleRegeneration)), deletedAt: Schema.NullOr(IsoDateTime), messages: Schema.Array(OrchestrationMessage), proposedPlans: Schema.Array(OrchestrationProposedPlan).pipe( Schema.withDecodingDefault(Effect.succeed([])), ), activities: Schema.Array(OrchestrationThreadActivity), checkpoints: Schema.Array(OrchestrationCheckpointSummary), session: Schema.NullOr(OrchestrationSession), }); export type OrchestrationThread = typeof OrchestrationThread.Type; export const OrchestrationReadModel = Schema.Struct({ snapshotSequence: NonNegativeInt, projects: Schema.Array(OrchestrationProject), threads: Schema.Array(OrchestrationThread), updatedAt: IsoDateTime, }); export type OrchestrationReadModel = typeof OrchestrationReadModel.Type; export const OrchestrationProjectShell = Schema.Struct({ id: ProjectId, title: TrimmedNonEmptyString, workspaceRoot: TrimmedNonEmptyString, repositoryIdentity: Schema.optional(Schema.NullOr(RepositoryIdentity)), defaultModelSelection: Schema.NullOr(ModelSelection), defaultThreadEnvMode: Schema.optional(Schema.NullOr(ThreadEnvMode)), // Optional on the wire so cached snapshots from older servers still decode. faviconPath: Schema.optional(Schema.NullOr(ProjectFaviconPath)), scripts: Schema.Array(ProjectScript), createdAt: IsoDateTime, updatedAt: IsoDateTime, }); export type OrchestrationProjectShell = typeof OrchestrationProjectShell.Type; export const OrchestrationThreadShell = Schema.Struct({ id: ThreadId, projectId: ProjectId, title: TrimmedNonEmptyString, modelSelection: ModelSelection, runtimeMode: RuntimeMode, interactionMode: ProviderInteractionMode.pipe( Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)), ), branch: Schema.NullOr(TrimmedNonEmptyString), worktreePath: Schema.NullOr(TrimmedNonEmptyString), latestTurn: Schema.NullOr(OrchestrationLatestTurn), createdAt: IsoDateTime, updatedAt: IsoDateTime, archivedAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), settledOverride: Schema.NullOr(Schema.Literals(["settled", "active"])).pipe( Schema.withDecodingDefault(Effect.succeed(null)), ), settledAt: Schema.NullOr(IsoDateTime).pipe(Schema.withDecodingDefault(Effect.succeed(null))), snoozedUntil: Schema.optional(Schema.NullOr(IsoDateTime)), snoozedAt: Schema.optional(Schema.NullOr(IsoDateTime)), pinnedAt: Schema.optional(Schema.NullOr(IsoDateTime)), pinOrderKey: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), titleRegeneration: Schema.optional(Schema.NullOr(ThreadTitleRegeneration)), session: Schema.NullOr(OrchestrationSession), latestUserMessageAt: Schema.NullOr(IsoDateTime), hasPendingApprovals: Schema.Boolean, hasPendingUserInput: Schema.Boolean, hasActionableProposedPlan: Schema.Boolean, /** * Native background work alive after the turn settles: "working" while * subagents/workflows run, "monitoring" when watch loops are the only * live work. Optional so old servers/clients interop; absent = none. */ backgroundLiveness: Schema.optional(Schema.NullOr(Schema.Literals(["working", "monitoring"]))), /** * Current plan step while a turn runs, for the Working indicators * (sidebar row, in-chat working line). Cleared when the turn settles — * never persists as stale UI. Optional so old servers/clients interop. */ planProgress: Schema.optional( Schema.NullOr( Schema.Struct({ step: TrimmedNonEmptyString, completedSteps: NonNegativeInt, totalSteps: NonNegativeInt, }), ), ), }); export type OrchestrationThreadShell = typeof OrchestrationThreadShell.Type; export const OrchestrationShellSnapshot = Schema.Struct({