packages/contracts/src/orchestration.ts
import * as Effect from "effect/Effect";
import * as Schema from "effect/Schema";
import * as Struct from "effect/Struct";
import {
ApprovalRequestId,
CheckpointRef,
ClientSurface,
CommandId,
EventId,
IsoDateTime,
MessageId,
NonNegativeInt,
PositiveInt,
ProjectId,
ThreadId,
TrimmedNonEmptyString,
TrimmedString,
TurnId,
} from "./baseSchemas.ts";
import { ChatAttachment, UploadChatAttachment } from "./chatAttachment.ts";
import { ModelSelection } from "./modelSelection.ts";
import {
DEFAULT_PROVIDER_INTERACTION_MODE,
DEFAULT_RUNTIME_MODE,
ProviderApprovalDecision,
ProviderInteractionMode,
ProviderUserInputAnswers,
RuntimeMode,
} from "./providerPolicy.ts";
import { ProviderInstanceId } from "./providerInstance.ts";
import { Project, ProjectScript, ProjectScriptIcon } from "./project.ts";
import { RepositoryIdentity, ThreadEnvMode } from "./environment.ts";
import { OrchestrationProjectShell } from "./orchestrationProject.ts";
import {
ApplicationEventMetadata,
ApplicationProjectCreatedEvent,
ApplicationProjectDeletedEvent,
ApplicationProjectMetaUpdatedEvent,
} from "./applicationEvent.ts";
export { OrchestrationProjectShell } from "./orchestrationProject.ts";
export {
ApplicationProjectCreatedPayload as ProjectCreatedPayload,
ApplicationProjectDeletedPayload as ProjectDeletedPayload,
ApplicationProjectMetaUpdatedPayload as ProjectMetaUpdatedPayload,
OrchestrationClientOrigin,
} from "./applicationEvent.ts";
export {
OrchestrationGetFullThreadDiffError,
OrchestrationGetFullThreadDiffInput,
OrchestrationGetFullThreadDiffResult,
OrchestrationGetTurnDiffError,
OrchestrationGetTurnDiffInput,
OrchestrationGetTurnDiffResult,
ThreadTurnDiff,
TurnCountRange,
} from "./checkpointDiff.ts";
// Correlation id is command id by design in this model.
export const CorrelationId = CommandId;
export type CorrelationId = typeof CorrelationId.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 ThreadLinkedPullRequest = Schema.Struct({
projectId: ProjectId,
repository: TrimmedNonEmptyString,
number: PositiveInt,
url: TrimmedNonEmptyString,
});
export type ThreadLinkedPullRequest = typeof ThreadLinkedPullRequest.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),
linkedPullRequest: Schema.optional(Schema.NullOr(ThreadLinkedPullRequest)),
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))),
// When the thread last re-entered the active list (any thread.unsettled).
// Anchors the active-list sort so an unsettled thread surfaces at the top
// instead of sinking back to its creation-order slot. Cleared on settle.
// Optional so payloads from pre-stamp servers still decode.
unsettledAt: Schema.optional(Schema.NullOr(IsoDateTime)),
// 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)),
// Active pinned threads render in the pinned block. Settled and snoozed
// threads remain in their respective shelves even when pinned.
// 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 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),
linkedPullRequest: Schema.optional(Schema.NullOr(ThreadLinkedPullRequest)),
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))),
// See OrchestrationThread.unsettledAt: last re-entry into the active list.
unsettledAt: Schema.optional(Schema.NullOr(IsoDateTime)),
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({
snapshotSequence: NonNegativeInt,
projects: Schema.Array(OrchestrationProjectShell),
threads: Schema.Array(OrchestrationThreadShell),
updatedAt: IsoDateTime,
});
export type OrchestrationShellSnapshot = typeof OrchestrationShellSnapshot.Type;
export const OrchestrationShellStreamEvent = Schema.Union([
Schema.Struct({
kind: Schema.Literal("project-upserted"),
sequence: NonNegativeInt,
project: OrchestrationProjectShell,
}),
Schema.Struct({
kind: Schema.Literal("project-removed"),
sequence: NonNegativeInt,
projectId: ProjectId,
}),
Schema.Struct({
kind: Schema.Literal("thread-upserted"),
sequence: NonNegativeInt,
thread: OrchestrationThreadShell,
}),
Schema.Struct({
kind: Schema.Literal("thread-removed"),
sequence: NonNegativeInt,
threadId: ThreadId,
}),
]);
export type OrchestrationShellStreamEvent = typeof OrchestrationShellStreamEvent.Type;
export const OrchestrationShellStreamItem = Schema.Union([
Schema.Struct({
kind: Schema.Literal("synchronized"),
}),
Schema.Struct({
kind: Schema.Literal("snapshot"),
snapshot: OrchestrationShellSnapshot,
}),
OrchestrationShellStreamEvent,
]);
export type OrchestrationShellStreamItem = typeof OrchestrationShellStreamItem.Type;
export const OrchestrationSubscribeThreadInput = Schema.Struct({
threadId: ThreadId,
});
export type OrchestrationSubscribeThreadInput = typeof OrchestrationSubscribeThreadInput.Type;
/**
* Bounds a thread detail read to a window of recent turns. `turnLimit` counts
* turns with a user pending message (subagent/fan-out turns between them ride
* along), so the window always contains the last N user prompts. `beforeCursor`
* requests the disjoint page of older turns strictly before a previously
* returned cursor. Requests without a window get the full thread; pagination is
* strictly opt-in so older clients keep today's behavior on both HTTP and the
* WebSocket fallback snapshot.
*/
export const OrchestrationThreadDetailWindow = Schema.Struct({
turnLimit: Schema.optionalKey(PositiveInt),
beforeCursor: Schema.optionalKey(TrimmedNonEmptyString),
});
export type OrchestrationThreadDetailWindow = typeof OrchestrationThreadDetailWindow.Type;
/**
* Page metadata for a windowed thread detail read. `beforeCursor` is opaque and
* exclusive: passing it back returns the adjacent disjoint slice of older
* turns. `null` means the thread is fully loaded below this page. The
* `snapshotSequence` mirrors the top-level snapshot sequence so history pages
* can be sequence-checked against live state before merging.
*/
export const OrchestrationThreadDetailPage = Schema.Struct({
beforeCursor: Schema.NullOr(TrimmedNonEmptyString),
hasMore: Schema.Boolean,
snapshotSequence: NonNegativeInt,
/**
* Highest event sequence applied to THIS thread at page read time. The
* global `snapshotSequence` advances with every thread's events, so a
* client cannot wait for it via its per-thread subscription; this
* thread-scoped watermark is reachable. A client merging an older page
* must first have applied live events up to it — otherwise a streaming
* turn outside the loaded window could have deltas replayed on top of
* page content that already includes them, duplicating text.
*/
threadSequence: Schema.optionalKey(NonNegativeInt),
});
export type OrchestrationThreadDetailPage = typeof OrchestrationThreadDetailPage.Type;
export const OrchestrationThreadDetailSnapshot = Schema.Struct({
snapshotSequence: NonNegativeInt,
thread: OrchestrationThread,
// Present only on windowed responses. Absent on full snapshots (and from
// pre-pagination servers), which clients treat as fully loaded.
page: Schema.optional(OrchestrationThreadDetailPage),
});
export type OrchestrationThreadDetailSnapshot = typeof OrchestrationThreadDetailSnapshot.Type;
export const ProjectCreateCommand = Schema.Struct({
type: Schema.Literal("project.create"),
commandId: CommandId,
projectId: ProjectId,
title: TrimmedNonEmptyString,
workspaceRoot: TrimmedNonEmptyString,
createWorkspaceRootIfMissing: Schema.optional(Schema.Boolean),
defaultModelSelection: Schema.optional(Schema.NullOr(ModelSelection)),
scripts: Schema.optional(Schema.Array(ProjectScript)),
createdAt: IsoDateTime,
});
export const ProjectMetaUpdateCommand = Schema.Struct({
type: Schema.Literal("project.meta.update"),
commandId: CommandId,
projectId: ProjectId,
title: Schema.optional(TrimmedNonEmptyString),
workspaceRoot: Schema.optional(TrimmedNonEmptyString),
defaultModelSelection: Schema.optional(Schema.NullOr(ModelSelection)),
// Absent = leave unchanged; null = clear the override.
defaultThreadEnvMode: Schema.optional(Schema.NullOr(ThreadEnvMode)),
faviconPath: Schema.optional(Schema.NullOr(ProjectFaviconPath)),
scripts: Schema.optional(Schema.Array(ProjectScript)),
});
export const ProjectDeleteCommand = Schema.Struct({
type: Schema.Literal("project.delete"),
commandId: CommandId,
projectId: ProjectId,
force: Schema.optional(Schema.Boolean),
});
export const ProjectOrchestrationCommand = Schema.Union([
ProjectCreateCommand,
ProjectMetaUpdateCommand,
ProjectDeleteCommand,
]);
export type ProjectOrchestrationCommand = typeof ProjectOrchestrationCommand.Type;
const ThreadCreateCommand = Schema.Struct({
type: Schema.Literal("thread.create"),
commandId: CommandId,
threadId: 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),
createdAt: IsoDateTime,
});
const ThreadDeleteCommand = Schema.Struct({
type: Schema.Literal("thread.delete"),
commandId: CommandId,
threadId: ThreadId,
});
const ThreadArchiveCommand = Schema.Struct({
type: Schema.Literal("thread.archive"),
commandId: CommandId,
threadId: ThreadId,
});
const ThreadUnarchiveCommand = Schema.Struct({
type: Schema.Literal("thread.unarchive"),
commandId: CommandId,
threadId: ThreadId,
});
const ThreadSettleCommand = Schema.Struct({
type: Schema.Literal("thread.settle"),
commandId: CommandId,
threadId: ThreadId,
});
const ThreadUnsettleCommand = Schema.Struct({
type: Schema.Literal("thread.unsettle"),
commandId: CommandId,
threadId: ThreadId,
// Commands only carry "user": activity un-settles are decided server-side
// (the decider emits thread.unsettled(reason: "activity") events directly,
// never through this command), so a client cannot forge the neutral reset.
reason: Schema.Literal("user"),
});
const ThreadSnoozeCommand = Schema.Struct({
type: Schema.Literal("thread.snooze"),
commandId: CommandId,
threadId: ThreadId,
// The wake time. Event-based wake conditions (PR merged, review posted)
// will arrive as an optional condition field alongside this; time-based
// snooze is just the first kind of condition.
snoozedUntil: IsoDateTime,
});
const ThreadUnsnoozeCommand = Schema.Struct({
type: Schema.Literal("thread.unsnooze"),
commandId: CommandId,
threadId: ThreadId,
// Commands only carry "user": activity wakes are decided server-side (the
// decider emits thread.unsnoozed(reason: "activity") directly), and timer
// wakes need no event at all — clients derive visibility from snoozedUntil,
// so a passed wake time simply stops classifying as snoozed.
reason: Schema.Literal("user"),
});
const ThreadPinCommand = Schema.Struct({
type: Schema.Literal("thread.pin"),
commandId: CommandId,
threadId: ThreadId,
// Initial slot in the user-arranged pinned order (see ThreadPinReorderCommand).
// Optional: clients on pre-reorder servers omit it, and the pinned block
// falls back to creation order for keyless threads.
orderKey: Schema.optional(TrimmedNonEmptyString),
});
const ThreadUnpinCommand = Schema.Struct({
type: Schema.Literal("thread.unpin"),
commandId: CommandId,
threadId: ThreadId,
});
const ThreadPinReorderCommand = Schema.Struct({
type: Schema.Literal("thread.pin.reorder"),
commandId: CommandId,
threadId: ThreadId,
// Fractional index key: pinned threads sort by plain string comparison of
// these keys, so a drag writes one key to one thread — neighbors (possibly
// on other servers) are never touched. Clients compute a key that sorts
// between the dropped position's neighbors.
orderKey: TrimmedNonEmptyString,
});
const ThreadMetaUpdateCommand = Schema.Struct({
type: Schema.Literal("thread.meta.update"),
commandId: CommandId,
threadId: ThreadId,
title: Schema.optional(TrimmedNonEmptyString),
regenerateTitle: Schema.optional(Schema.Literal(true)),
modelSelection: Schema.optional(ModelSelection),
branch: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
expectedBranch: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
worktreePath: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
linkedPullRequest: Schema.optional(Schema.NullOr(ThreadLinkedPullRequest)),
}).check(
Schema.makeFilter(
(input) =>
!(input.title !== undefined && input.regenerateTitle === true) ||
"title and regenerateTitle cannot be specified together",
),
);
const ThreadRuntimeModeSetCommand = Schema.Struct({
type: Schema.Literal("thread.runtime-mode.set"),
commandId: CommandId,
threadId: ThreadId,
runtimeMode: RuntimeMode,
createdAt: IsoDateTime,
});
const ThreadInteractionModeSetCommand = Schema.Struct({
type: Schema.Literal("thread.interaction-mode.set"),
commandId: CommandId,
threadId: ThreadId,
interactionMode: ProviderInteractionMode,
createdAt: IsoDateTime,
});
const ThreadTurnStartBootstrapCreateThread = Schema.Struct({
projectId: ProjectId,
title: TrimmedNonEmptyString,
modelSelection: ModelSelection,
runtimeMode: RuntimeMode,
interactionMode: ProviderInteractionMode,
branch: Schema.NullOr(TrimmedNonEmptyString),
worktreePath: Schema.NullOr(TrimmedNonEmptyString),
createdAt: IsoDateTime,
});
const ThreadTurnStartBootstrapPrepareWorktree = Schema.Struct({
projectCwd: TrimmedNonEmptyString,
baseBranch: TrimmedNonEmptyString,
branch: Schema.optional(TrimmedNonEmptyString),
startFromOrigin: Schema.optional(Schema.Boolean),
});
const ThreadTurnStartBootstrap = Schema.Struct({
createThread: Schema.optional(ThreadTurnStartBootstrapCreateThread),
prepareWorktree: Schema.optional(ThreadTurnStartBootstrapPrepareWorktree),
runSetupScript: Schema.optional(Schema.Boolean),
});
export type ThreadTurnStartBootstrap = typeof ThreadTurnStartBootstrap.Type;
export const ThreadTurnStartCommand = Schema.Struct({
type: Schema.Literal("thread.turn.start"),
commandId: CommandId,
threadId: ThreadId,
message: Schema.Struct({
messageId: MessageId,
role: Schema.Literal("user"),
text: Schema.String,
attachments: Schema.Array(ChatAttachment),
}),
modelSelection: Schema.optional(ModelSelection),
titleSeed: Schema.optional(TrimmedNonEmptyString),
runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE))),
interactionMode: ProviderInteractionMode.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)),
),
bootstrap: Schema.optional(ThreadTurnStartBootstrap),
sourceProposedPlan: Schema.optional(SourceProposedPlanReference),
createdAt: IsoDateTime,
});
const ClientThreadTurnStartCommand = Schema.Struct({
type: Schema.Literal("thread.turn.start"),
commandId: CommandId,
threadId: ThreadId,
message: Schema.Struct({
messageId: MessageId,
role: Schema.Literal("user"),
text: Schema.String,
attachments: Schema.Array(Schema.Union([UploadChatAttachment, ChatAttachment])),
}),
modelSelection: Schema.optional(ModelSelection),
titleSeed: Schema.optional(TrimmedNonEmptyString),
runtimeMode: RuntimeMode,
interactionMode: ProviderInteractionMode,
bootstrap: Schema.optional(ThreadTurnStartBootstrap),
sourceProposedPlan: Schema.optional(SourceProposedPlanReference),
createdAt: IsoDateTime,
});
const ThreadTurnInterruptCommand = Schema.Struct({
type: Schema.Literal("thread.turn.interrupt"),
commandId: CommandId,
threadId: ThreadId,
turnId: Schema.optional(TurnId),
createdAt: IsoDateTime,
});
const ThreadApprovalRespondCommand = Schema.Struct({
type: Schema.Literal("thread.approval.respond"),
commandId: CommandId,
threadId: ThreadId,
requestId: ApprovalRequestId,
decision: ProviderApprovalDecision,
createdAt: IsoDateTime,
});
const ThreadUserInputRespondCommand = Schema.Struct({
type: Schema.Literal("thread.user-input.respond"),
commandId: CommandId,
threadId: ThreadId,
requestId: ApprovalRequestId,
answers: ProviderUserInputAnswers,
createdAt: IsoDateTime,
});
const ThreadCheckpointRevertCommand = Schema.Struct({
type: Schema.Literal("thread.checkpoint.revert"),
commandId: CommandId,
threadId: ThreadId,
turnCount: NonNegativeInt,
createdAt: IsoDateTime,
});
const ThreadSessionStopCommand = Schema.Struct({
type: Schema.Literal("thread.session.stop"),
commandId: CommandId,
threadId: ThreadId,
createdAt: IsoDateTime,
// Settle-cleanup stops are conditional: the decider drops the stop if the
// thread was re-engaged (unsettled, session starting/running, or a queued
// turn start) between the settle and this command. Guarding in the decider
// closes the race a post-settle snapshot read cannot: commands are decided
// serially against the authoritative read model.
onlyIfSettled: Schema.optional(Schema.Boolean),
});
const DispatchableClientOrchestrationCommand = Schema.Union([
ProjectCreateCommand,
ProjectMetaUpdateCommand,
ProjectDeleteCommand,
ThreadCreateCommand,
ThreadDeleteCommand,
ThreadArchiveCommand,
ThreadUnarchiveCommand,
ThreadSettleCommand,
ThreadUnsettleCommand,
ThreadSnoozeCommand,
ThreadUnsnoozeCommand,
ThreadPinCommand,
ThreadUnpinCommand,
ThreadPinReorderCommand,
ThreadMetaUpdateCommand,
ThreadRuntimeModeSetCommand,
ThreadInteractionModeSetCommand,
ThreadTurnStartCommand,
ThreadTurnInterruptCommand,
ThreadApprovalRespondCommand,
ThreadUserInputRespondCommand,
ThreadCheckpointRevertCommand,
ThreadSessionStopCommand,
]);
export type DispatchableClientOrchestrationCommand =
typeof DispatchableClientOrchestrationCommand.Type;
export const ClientOrchestrationCommand = Schema.Union([
ProjectCreateCommand,
ProjectMetaUpdateCommand,
ProjectDeleteCommand,
ThreadCreateCommand,
ThreadDeleteCommand,
ThreadArchiveCommand,
ThreadUnarchiveCommand,
ThreadSettleCommand,
ThreadUnsettleCommand,
ThreadSnoozeCommand,
ThreadUnsnoozeCommand,
ThreadPinCommand,
ThreadUnpinCommand,
ThreadPinReorderCommand,
ThreadMetaUpdateCommand,
ThreadRuntimeModeSetCommand,
ThreadInteractionModeSetCommand,
ClientThreadTurnStartCommand,
ThreadTurnInterruptCommand,
ThreadApprovalRespondCommand,
ThreadUserInputRespondCommand,
ThreadCheckpointRevertCommand,
ThreadSessionStopCommand,
]);
export type ClientOrchestrationCommand = typeof ClientOrchestrationCommand.Type;
const ThreadSessionSetCommand = Schema.Struct({
type: Schema.Literal("thread.session.set"),
commandId: CommandId,
threadId: ThreadId,
session: OrchestrationSession,
createdAt: IsoDateTime,
});
const ThreadMessageAssistantDeltaCommand = Schema.Struct({
type: Schema.Literal("thread.message.assistant.delta"),
commandId: CommandId,
threadId: ThreadId,
messageId: MessageId,
delta: Schema.String,
turnId: Schema.optional(TurnId),
createdAt: IsoDateTime,
});
const ThreadMessageAssistantCompleteCommand = Schema.Struct({
type: Schema.Literal("thread.message.assistant.complete"),
commandId: CommandId,
threadId: ThreadId,
messageId: MessageId,
turnId: Schema.optional(TurnId),
createdAt: IsoDateTime,
});
const ThreadProposedPlanUpsertCommand = Schema.Struct({
type: Schema.Literal("thread.proposed-plan.upsert"),
commandId: CommandId,
threadId: ThreadId,
proposedPlan: OrchestrationProposedPlan,
createdAt: IsoDateTime,
});
const ThreadTurnDiffCompleteCommand = Schema.Struct({
type: Schema.Literal("thread.turn.diff.complete"),
commandId: CommandId,
threadId: ThreadId,
turnId: TurnId,
completedAt: IsoDateTime,
checkpointRef: CheckpointRef,
status: OrchestrationCheckpointStatus,
files: Schema.Array(OrchestrationCheckpointFile),
assistantMessageId: Schema.optional(MessageId),
checkpointTurnCount: NonNegativeInt,
createdAt: IsoDateTime,
});
const ThreadActivityAppendCommand = Schema.Struct({
type: Schema.Literal("thread.activity.append"),
commandId: CommandId,
threadId: ThreadId,
activity: OrchestrationThreadActivity,
createdAt: IsoDateTime,
});
const ThreadRevertCompleteCommand = Schema.Struct({
type: Schema.Literal("thread.revert.complete"),
commandId: CommandId,
threadId: ThreadId,
turnCount: NonNegativeInt,
createdAt: IsoDateTime,
});
const ThreadTitleRegenerationCompleteCommand = Schema.Struct({
type: Schema.Literal("thread.title.regeneration.complete"),
commandId: CommandId,
threadId: ThreadId,
requestId: CommandId,
title: Schema.optional(TrimmedNonEmptyString),
});
const InternalOrchestrationCommand = Schema.Union([
ThreadSessionSetCommand,
ThreadMessageAssistantDeltaCommand,
ThreadMessageAssistantCompleteCommand,
ThreadProposedPlanUpsertCommand,
ThreadTurnDiffCompleteCommand,
ThreadActivityAppendCommand,
ThreadRevertCompleteCommand,
ThreadTitleRegenerationCompleteCommand,
]);
export type InternalOrchestrationCommand = typeof InternalOrchestrationCommand.Type;
export const OrchestrationCommand = Schema.Union([
DispatchableClientOrchestrationCommand,
InternalOrchestrationCommand,
]);
export type OrchestrationCommand = typeof OrchestrationCommand.Type;
export const OrchestrationEventType = Schema.Literals([
"project.created",
"project.meta-updated",
"project.deleted",
"thread.created",
"thread.deleted",
"thread.archived",
"thread.unarchived",
"thread.settled",
"thread.unsettled",
"thread.snoozed",
"thread.unsnoozed",
"thread.pinned",
"thread.unpinned",
"thread.pin-reordered",
"thread.meta-updated",
"thread.runtime-mode-set",
"thread.interaction-mode-set",
"thread.message-sent",
"thread.turn-start-requested",
"thread.turn-interrupt-requested",
"thread.approval-response-requested",
"thread.user-input-response-requested",
"thread.checkpoint-revert-requested",
"thread.reverted",
"thread.session-stop-requested",
"thread.session-set",
"thread.proposed-plan-upserted",
"thread.turn-diff-completed",
"thread.activity-appended",
]);
export type OrchestrationEventType = typeof OrchestrationEventType.Type;
export const OrchestrationAggregateKind = Schema.Literals(["project", "thread"]);
export type OrchestrationAggregateKind = typeof OrchestrationAggregateKind.Type;
export const OrchestrationActorKind = Schema.Literals(["client", "server", "provider"]);
export const ThreadCreatedPayload = Schema.Struct({
threadId: ThreadId,
projectId: ProjectId,
title: TrimmedNonEmptyString,
modelSelection: ModelSelection,
runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE))),
interactionMode: ProviderInteractionMode.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)),
),
branch: Schema.NullOr(TrimmedNonEmptyString),
worktreePath: Schema.NullOr(TrimmedNonEmptyString),
createdAt: IsoDateTime,
updatedAt: IsoDateTime,
});
export const ThreadDeletedPayload = Schema.Struct({
threadId: ThreadId,
deletedAt: IsoDateTime,
});
export const ThreadArchivedPayload = Schema.Struct({
threadId: ThreadId,
archivedAt: IsoDateTime,
updatedAt: IsoDateTime,
});
export const ThreadUnarchivedPayload = Schema.Struct({
threadId: ThreadId,
updatedAt: IsoDateTime,
});
export const ThreadSettledPayload = Schema.Struct({
threadId: ThreadId,
settledAt: IsoDateTime,
updatedAt: IsoDateTime,
});
export const ThreadUnsettledPayload = Schema.Struct({
threadId: ThreadId,
reason: Schema.Literals(["user", "activity"]),
updatedAt: IsoDateTime,
});
export const ThreadSnoozedPayload = Schema.Struct({
threadId: ThreadId,
snoozedUntil: IsoDateTime,
snoozedAt: IsoDateTime,
updatedAt: IsoDateTime,
});
export const ThreadUnsnoozedPayload = Schema.Struct({
threadId: ThreadId,
// user: explicit "wake now". activity: real work arrived (user message /
// session coming alive) and the decider cleared the snooze — mirrors
// thread.unsettled's activity resets. Timer wakes emit no event: clients
// derive them from snoozedUntil passing.
reason: Schema.Literals(["user", "activity"]),
updatedAt: IsoDateTime,
});
export const ThreadPinnedPayload = Schema.Struct({
threadId: ThreadId,
pinnedAt: IsoDateTime,
// Absent on re-pins of an already-pinned thread (the existing key wins)
// and on pins from clients that predate reordering.
pinOrderKey: Schema.optional(TrimmedNonEmptyString),
updatedAt: IsoDateTime,
});
export const ThreadUnpinnedPayload = Schema.Struct({
threadId: ThreadId,
updatedAt: IsoDateTime,
});
export const ThreadPinReorderedPayload = Schema.Struct({
threadId: ThreadId,
orderKey: TrimmedNonEmptyString,
updatedAt: IsoDateTime,
});
export const ThreadMetaUpdatedPayload = Schema.Struct({
threadId: ThreadId,
title: Schema.optional(TrimmedNonEmptyString),
/** Intent marker consumed by the title-generation reactor. Keeping this on
the existing event lets older clients safely ignore the new field. */
regenerateTitle: Schema.optional(Schema.Literal(true)),
/** Title at request time, used to avoid overwriting a later manual rename. */
previousTitle: Schema.optional(TrimmedNonEmptyString),
/** Pending state shared with clients. Null clears a matching request. */
titleRegeneration: Schema.optional(Schema.NullOr(ThreadTitleRegeneration)),
modelSelection: Schema.optional(ModelSelection),
branch: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
worktreePath: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)),
linkedPullRequest: Schema.optional(Schema.NullOr(ThreadLinkedPullRequest)),
updatedAt: IsoDateTime,
});
export const ThreadRuntimeModeSetPayload = Schema.Struct({
threadId: ThreadId,
runtimeMode: RuntimeMode,
updatedAt: IsoDateTime,
});
export const ThreadInteractionModeSetPayload = Schema.Struct({
threadId: ThreadId,
interactionMode: ProviderInteractionMode.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)),
),
updatedAt: IsoDateTime,
});
export const ThreadMessageSentPayload = Schema.Struct({
threadId: ThreadId,
messageId: MessageId,
role: OrchestrationMessageRole,
text: Schema.String,
attachments: Schema.optional(Schema.Array(ChatAttachment)),
turnId: Schema.NullOr(TurnId),
streaming: Schema.Boolean,
createdAt: IsoDateTime,
updatedAt: IsoDateTime,
});
export const ThreadTurnStartRequestedPayload = Schema.Struct({
threadId: ThreadId,
messageId: MessageId,
modelSelection: Schema.optional(ModelSelection),
titleSeed: Schema.optional(TrimmedNonEmptyString),
runtimeMode: RuntimeMode.pipe(Schema.withDecodingDefault(Effect.succeed(DEFAULT_RUNTIME_MODE))),
interactionMode: ProviderInteractionMode.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_PROVIDER_INTERACTION_MODE)),
),
sourceProposedPlan: Schema.optional(SourceProposedPlanReference),
createdAt: IsoDateTime,
});
export const ThreadTurnInterruptRequestedPayload = Schema.Struct({
threadId: ThreadId,
turnId: Schema.optional(TurnId),
createdAt: IsoDateTime,
});
export const ThreadApprovalResponseRequestedPayload = Schema.Struct({
threadId: ThreadId,
requestId: ApprovalRequestId,
decision: ProviderApprovalDecision,
createdAt: IsoDateTime,
});
const ThreadUserInputResponseRequestedPayload = Schema.Struct({
threadId: ThreadId,
requestId: ApprovalRequestId,
answers: ProviderUserInputAnswers,
createdAt: IsoDateTime,
});
export const ThreadCheckpointRevertRequestedPayload = Schema.Struct({
threadId: ThreadId,
turnCount: NonNegativeInt,
createdAt: IsoDateTime,
});
export const ThreadRevertedPayload = Schema.Struct({
threadId: ThreadId,
turnCount: NonNegativeInt,
});
export const ThreadSessionStopRequestedPayload = Schema.Struct({
threadId: ThreadId,
createdAt: IsoDateTime,
});
export const ThreadSessionSetPayload = Schema.Struct({
threadId: ThreadId,
session: OrchestrationSession,
});
export const ThreadProposedPlanUpsertedPayload = Schema.Struct({
threadId: ThreadId,
proposedPlan: OrchestrationProposedPlan,
});
export const ThreadTurnDiffCompletedPayload = Schema.Struct({
threadId: ThreadId,
turnId: TurnId,
checkpointTurnCount: NonNegativeInt,
checkpointRef: CheckpointRef,
status: OrchestrationCheckpointStatus,
files: Schema.Array(OrchestrationCheckpointFile),
assistantMessageId: Schema.NullOr(MessageId),
completedAt: IsoDateTime,
});
export const ThreadActivityAppendedPayload = Schema.Struct({
threadId: ThreadId,
activity: OrchestrationThreadActivity,
});
export const OrchestrationEventMetadata = ApplicationEventMetadata;
export type OrchestrationEventMetadata = typeof OrchestrationEventMetadata.Type;
const EventBaseFields = {
sequence: NonNegativeInt,
eventId: EventId,
aggregateKind: OrchestrationAggregateKind,
aggregateId: Schema.Union([ProjectId, ThreadId]),
occurredAt: IsoDateTime,
commandId: Schema.NullOr(CommandId),
causationEventId: Schema.NullOr(EventId),
correlationId: Schema.NullOr(CommandId),
metadata: OrchestrationEventMetadata,
} as const;
export const OrchestrationEvent = Schema.Union([
ApplicationProjectCreatedEvent,
ApplicationProjectMetaUpdatedEvent,
ApplicationProjectDeletedEvent,
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.created"),
payload: ThreadCreatedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.deleted"),
payload: ThreadDeletedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.archived"),
payload: ThreadArchivedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.unarchived"),
payload: ThreadUnarchivedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.settled"),
payload: ThreadSettledPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.unsettled"),
payload: ThreadUnsettledPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.snoozed"),
payload: ThreadSnoozedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.unsnoozed"),
payload: ThreadUnsnoozedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.pinned"),
payload: ThreadPinnedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.unpinned"),
payload: ThreadUnpinnedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.pin-reordered"),
payload: ThreadPinReorderedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.meta-updated"),
payload: ThreadMetaUpdatedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.runtime-mode-set"),
payload: ThreadRuntimeModeSetPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.interaction-mode-set"),
payload: ThreadInteractionModeSetPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.message-sent"),
payload: ThreadMessageSentPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.turn-start-requested"),
payload: ThreadTurnStartRequestedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.turn-interrupt-requested"),
payload: ThreadTurnInterruptRequestedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.approval-response-requested"),
payload: ThreadApprovalResponseRequestedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.user-input-response-requested"),
payload: ThreadUserInputResponseRequestedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.checkpoint-revert-requested"),
payload: ThreadCheckpointRevertRequestedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.reverted"),
payload: ThreadRevertedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.session-stop-requested"),
payload: ThreadSessionStopRequestedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.session-set"),
payload: ThreadSessionSetPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.proposed-plan-upserted"),
payload: ThreadProposedPlanUpsertedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.turn-diff-completed"),
payload: ThreadTurnDiffCompletedPayload,
}),
Schema.Struct({
...EventBaseFields,
type: Schema.Literal("thread.activity-appended"),
payload: ThreadActivityAppendedPayload,
}),
]);
export type OrchestrationEvent = typeof OrchestrationEvent.Type;
export const OrchestrationThreadStreamItem = Schema.Union([
Schema.Struct({
kind: Schema.Literal("synchronized"),
}),
Schema.Struct({
kind: Schema.Literal("snapshot"),
snapshot: OrchestrationThreadDetailSnapshot,
}),
Schema.Struct({
kind: Schema.Literal("event"),
event: OrchestrationEvent,
}),
]);
export type OrchestrationThreadStreamItem = typeof OrchestrationThreadStreamItem.Type;
export const OrchestrationCommandReceiptStatus = Schema.Literals(["accepted", "rejected"]);
export type OrchestrationCommandReceiptStatus = typeof OrchestrationCommandReceiptStatus.Type;
export const ProviderSessionRuntimeStatus = Schema.Literals([
"starting",
"running",
"stopped",
"error",
]);
export type ProviderSessionRuntimeStatus = typeof ProviderSessionRuntimeStatus.Type;
const ProjectionThreadTurnStatus = Schema.Literals([
"running",
"completed",
"interrupted",
"error",
]);
export type ProjectionThreadTurnStatus = typeof ProjectionThreadTurnStatus.Type;
const ProjectionCheckpointRow = Schema.Struct({
threadId: ThreadId,
turnId: TurnId,
checkpointTurnCount: NonNegativeInt,
checkpointRef: CheckpointRef,
status: OrchestrationCheckpointStatus,
files: Schema.Array(OrchestrationCheckpointFile),
assistantMessageId: Schema.NullOr(MessageId),
completedAt: IsoDateTime,
});
export type ProjectionCheckpointRow = typeof ProjectionCheckpointRow.Type;
export const ProjectionPendingApprovalStatus = Schema.Literals(["pending", "resolved"]);
export type ProjectionPendingApprovalStatus = typeof ProjectionPendingApprovalStatus.Type;
export const ProjectionPendingApprovalDecision = Schema.NullOr(ProviderApprovalDecision);
export type ProjectionPendingApprovalDecision = typeof ProjectionPendingApprovalDecision.Type;
export const OrchestrationThreadSearchSource = Schema.Literals(["user", "assistant"]);
export type OrchestrationThreadSearchSource = typeof OrchestrationThreadSearchSource.Type;
// The server's SQLite client is synchronous and single-connection. Bound both
// scan input and response size so a search cannot monopolize that connection.
export const OrchestrationSearchThreadsInput = Schema.Struct({
query: TrimmedString.check(Schema.isMinLength(2), Schema.isMaxLength(200)),
limit: Schema.optionalKey(Schema.Int.check(Schema.isBetween({ minimum: 1, maximum: 50 }))),
});
export type OrchestrationSearchThreadsInput = typeof OrchestrationSearchThreadsInput.Type;
export const OrchestrationThreadSearchMatch = Schema.Struct({
threadId: ThreadId,
projectId: ProjectId,
source: OrchestrationThreadSearchSource,
snippet: Schema.String.check(Schema.isMaxLength(240)),
messageCreatedAt: Schema.NullOr(IsoDateTime),
});
export type OrchestrationThreadSearchMatch = typeof OrchestrationThreadSearchMatch.Type;
export const OrchestrationSearchThreadsResult = Schema.Struct({
matches: Schema.Array(OrchestrationThreadSearchMatch),
});
export type OrchestrationSearchThreadsResult = typeof OrchestrationSearchThreadsResult.Type;
export class OrchestrationSearchThreadsError extends Schema.TaggedErrorClass<OrchestrationSearchThreadsError>()(
"OrchestrationSearchThreadsError",
{
message: TrimmedNonEmptyString,
cause: Schema.optional(Schema.Defect()),
},
) {}
export class OrchestrationDispatchCommandError extends Schema.TaggedErrorClass<OrchestrationDispatchCommandError>()(
"OrchestrationDispatchCommandError",
{
message: TrimmedNonEmptyString,
cause: Schema.optional(Schema.Defect()),
bootstrapThreadDisposition: Schema.optional(Schema.Literal("deleted")),
},
) {}