1739 lines · 60.0 KB · TypeScript
RawDownload
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.