import { Schema, Struct } from "effect";import { NonNegativeInt, ProjectId, ThreadId, TrimmedNonEmptyString } from "./baseSchemas"; import { ClientOrchestrationCommand, OrchestrationEvent, ORCHESTRATION_WS_CHANNELS, OrchestrationGetFullThreadDiffInput, ORCHESTRATION_WS_METHODS, OrchestrationGetSnapshotInput, OrchestrationGetTurnDiffInput, OrchestrationReplayEventsInput,} from "./orchestration";import { GitCheckoutInput, GitCreateBranchInput, GitCreateWorktreeInput, GitInitInput, GitListBranchesInput, GitPullInput, GitRemoveWorktreeInput, GitRunStackedActionInput, GitStatusInput,} from "./git";import { TerminalClearInput, TerminalCloseInput, TerminalEvent, TerminalOpenInput, TerminalResizeInput, TerminalRestartInput, TerminalWriteInput,} from "./terminal";import { KeybindingRule } from "./keybindings";import { ProjectSearchEntriesInput, ProjectWriteFileInput } from "./project";import { OpenInEditorInput } from "./editor";import { ServerConfigUpdatedPayload } from "./server"; // ── WebSocket RPC Method Names ─────────────────────────────────────── export const WS_METHODS = { // Project registry methods projectsList: "projects.list", projectsAdd: "projects.add", projectsRemove: "projects.remove", projectsSearchEntries: "projects.searchEntries", projectsWriteFile: "projects.writeFile", // Shell methods shellOpenInEditor: "shell.openInEditor", // Git methods gitPull: "git.pull", gitStatus: "git.status", gitRunStackedAction: "git.runStackedAction", gitListBranches: "git.listBranches", gitCreateWorktree: "git.createWorktree", gitRemoveWorktree: "git.removeWorktree", gitCreateBranch: "git.createBranch", gitCheckout: "git.checkout", gitInit: "git.init", // Terminal methods terminalOpen: "terminal.open", terminalWrite: "terminal.write", terminalResize: "terminal.resize", terminalClear: "terminal.clear", terminalRestart: "terminal.restart", terminalClose: "terminal.close", // Server meta serverGetConfig: "server.getConfig", serverUpsertKeybinding: "server.upsertKeybinding",} as const; // ── Push Event Channels ────────────────────────────────────────────── export const WS_CHANNELS = { terminalEvent: "terminal.event", serverWelcome: "server.welcome", serverConfigUpdated: "server.configUpdated",} as const; // -- Tagged Union of all request body schemas ───────────────────────── const tagRequestBody = <const Tag extends string, const Fields extends Schema.Struct.Fields>( tag: Tag, schema: Schema.Struct<Fields>,) => schema.mapFields( Struct.assign({ _tag: Schema.tag(tag) }), // PreserveChecks is safe here. No existing schema should have checks depending on the tag { unsafePreserveChecks: true }, ); const WebSocketRequestBody = Schema.Union([ // Orchestration methods tagRequestBody( ORCHESTRATION_WS_METHODS.dispatchCommand, Schema.Struct({ command: ClientOrchestrationCommand }), ), tagRequestBody(ORCHESTRATION_WS_METHODS.getSnapshot, OrchestrationGetSnapshotInput), tagRequestBody(ORCHESTRATION_WS_METHODS.getTurnDiff, OrchestrationGetTurnDiffInput), tagRequestBody(ORCHESTRATION_WS_METHODS.getFullThreadDiff, OrchestrationGetFullThreadDiffInput), tagRequestBody(ORCHESTRATION_WS_METHODS.replayEvents, OrchestrationReplayEventsInput), // Project Search tagRequestBody(WS_METHODS.projectsSearchEntries, ProjectSearchEntriesInput), tagRequestBody(WS_METHODS.projectsWriteFile, ProjectWriteFileInput), // Shell methods tagRequestBody(WS_METHODS.shellOpenInEditor, OpenInEditorInput), // Git methods tagRequestBody(WS_METHODS.gitPull, GitPullInput), tagRequestBody(WS_METHODS.gitStatus, GitStatusInput), tagRequestBody(WS_METHODS.gitRunStackedAction, GitRunStackedActionInput), tagRequestBody(WS_METHODS.gitListBranches, GitListBranchesInput), tagRequestBody(WS_METHODS.gitCreateWorktree, GitCreateWorktreeInput), tagRequestBody(WS_METHODS.gitRemoveWorktree, GitRemoveWorktreeInput), tagRequestBody(WS_METHODS.gitCreateBranch, GitCreateBranchInput), tagRequestBody(WS_METHODS.gitCheckout, GitCheckoutInput), tagRequestBody(WS_METHODS.gitInit, GitInitInput), // Terminal methods tagRequestBody(WS_METHODS.terminalOpen, TerminalOpenInput), tagRequestBody(WS_METHODS.terminalWrite, TerminalWriteInput), tagRequestBody(WS_METHODS.terminalResize, TerminalResizeInput), tagRequestBody(WS_METHODS.terminalClear, TerminalClearInput), tagRequestBody(WS_METHODS.terminalRestart, TerminalRestartInput), tagRequestBody(WS_METHODS.terminalClose, TerminalCloseInput), // Server meta tagRequestBody(WS_METHODS.serverGetConfig, Schema.Struct({})), tagRequestBody(WS_METHODS.serverUpsertKeybinding, KeybindingRule),]); export const WebSocketRequest = Schema.Struct({ id: TrimmedNonEmptyString, body: WebSocketRequestBody,});export type WebSocketRequest = typeof WebSocketRequest.Type; export const WebSocketResponse = Schema.Struct({ id: TrimmedNonEmptyString, result: Schema.optional(Schema.Unknown), error: Schema.optional( Schema.Struct({ message: Schema.String, }), ),});export type WebSocketResponse = typeof WebSocketResponse.Type; export const WsPushSequence = NonNegativeInt;export type WsPushSequence = typeof WsPushSequence.Type; export const WsWelcomePayload = Schema.Struct({ cwd: TrimmedNonEmptyString, projectName: TrimmedNonEmptyString, bootstrapProjectId: Schema.optional(ProjectId), bootstrapThreadId: Schema.optional(ThreadId),});export type WsWelcomePayload = typeof WsWelcomePayload.Type; export const WsDecodeDiagnosticCode = Schema.Literals([ "invalid-json", "invalid-envelope", "invalid-request", "invalid-response",]);export type WsDecodeDiagnosticCode = typeof WsDecodeDiagnosticCode.Type; export const WsDecodeDiagnostic = Schema.Struct({ code: WsDecodeDiagnosticCode, reason: TrimmedNonEmptyString, rawKind: Schema.optional(TrimmedNonEmptyString), expected: Schema.optional(TrimmedNonEmptyString), actual: Schema.optional(TrimmedNonEmptyString), path: Schema.optional(Schema.Array(TrimmedNonEmptyString)), jsonOffset: Schema.optional(NonNegativeInt),});export type WsDecodeDiagnostic = typeof WsDecodeDiagnostic.Type; export interface WsPushPayloadByChannel { readonly [WS_CHANNELS.serverWelcome]: WsWelcomePayload; readonly [WS_CHANNELS.serverConfigUpdated]: typeof ServerConfigUpdatedPayload.Type; readonly [WS_CHANNELS.terminalEvent]: typeof TerminalEvent.Type; readonly [ORCHESTRATION_WS_CHANNELS.domainEvent]: OrchestrationEvent;} export type WsPushChannel = keyof WsPushPayloadByChannel;export type WsPushData<C extends WsPushChannel> = WsPushPayloadByChannel[C]; const makeWsPushSchema = <const Channel extends string, Payload extends Schema.Schema<any>>( channel: Channel, payload: Payload,) => Schema.Struct({ type: Schema.Literal("push"), sequence: WsPushSequence, channel: Schema.Literal(channel), data: payload, }); export const WsPushServerWelcome = makeWsPushSchema(WS_CHANNELS.serverWelcome, WsWelcomePayload);export const WsPushServerConfigUpdated = makeWsPushSchema( WS_CHANNELS.serverConfigUpdated, ServerConfigUpdatedPayload,);export const WsPushTerminalEvent = makeWsPushSchema(WS_CHANNELS.terminalEvent, TerminalEvent);export const WsPushOrchestrationDomainEvent = makeWsPushSchema( ORCHESTRATION_WS_CHANNELS.domainEvent, OrchestrationEvent,); export const WsPushChannelSchema = Schema.Literals([ WS_CHANNELS.serverWelcome, WS_CHANNELS.serverConfigUpdated, WS_CHANNELS.terminalEvent, ORCHESTRATION_WS_CHANNELS.domainEvent,]);export type WsPushChannelSchema = typeof WsPushChannelSchema.Type; export const WsPush = Schema.Union([ WsPushServerWelcome, WsPushServerConfigUpdated, WsPushTerminalEvent, WsPushOrchestrationDomainEvent,]);export type WsPush = typeof WsPush.Type; export type WsPushMessage<C extends WsPushChannel> = Extract<WsPush, { channel: C }>; export const WsPushEnvelopeBase = Schema.Struct({ type: Schema.Literal("push"), sequence: WsPushSequence, channel: WsPushChannelSchema, data: Schema.Unknown,});export type WsPushEnvelopeBase = typeof WsPushEnvelopeBase.Type; // ── Union of all server → client messages ───────────────────────────── export const WsResponse = Schema.Union([WebSocketResponse, WsPush]);export type WsResponse = typeof WsResponse.Type;
import { Schema, Struct } from "effect";
import { NonNegativeInt, ProjectId, ThreadId, TrimmedNonEmptyString } from "./baseSchemas";
import {
ClientOrchestrationCommand,
OrchestrationEvent,
ORCHESTRATION_WS_CHANNELS,
OrchestrationGetFullThreadDiffInput,
ORCHESTRATION_WS_METHODS,
OrchestrationGetSnapshotInput,
OrchestrationGetTurnDiffInput,
OrchestrationReplayEventsInput,
} from "./orchestration";
import {
GitCheckoutInput,
GitCreateBranchInput,
GitCreateWorktreeInput,
GitInitInput,
GitListBranchesInput,
GitPullInput,
GitRemoveWorktreeInput,
GitRunStackedActionInput,
GitStatusInput,
} from "./git";
import {
TerminalClearInput,
TerminalCloseInput,
TerminalEvent,
TerminalOpenInput,
TerminalResizeInput,
TerminalRestartInput,
TerminalWriteInput,
} from "./terminal";
import { KeybindingRule } from "./keybindings";
import { ProjectSearchEntriesInput, ProjectWriteFileInput } from "./project";
import { OpenInEditorInput } from "./editor";
import { ServerConfigUpdatedPayload } from "./server";
// ── WebSocket RPC Method Names ───────────────────────────────────────
export const WS_METHODS = {
// Project registry methods
projectsList: "projects.list",
projectsAdd: "projects.add",
projectsRemove: "projects.remove",
projectsSearchEntries: "projects.searchEntries",
projectsWriteFile: "projects.writeFile",
// Shell methods
shellOpenInEditor: "shell.openInEditor",
// Git methods
gitPull: "git.pull",
gitStatus: "git.status",
gitRunStackedAction: "git.runStackedAction",
gitListBranches: "git.listBranches",
gitCreateWorktree: "git.createWorktree",
gitRemoveWorktree: "git.removeWorktree",
gitCreateBranch: "git.createBranch",
gitCheckout: "git.checkout",
gitInit: "git.init",
// Terminal methods
terminalOpen: "terminal.open",
terminalWrite: "terminal.write",
terminalResize: "terminal.resize",
terminalClear: "terminal.clear",
terminalRestart: "terminal.restart",
terminalClose: "terminal.close",
// Server meta
serverGetConfig: "server.getConfig",
serverUpsertKeybinding: "server.upsertKeybinding",
} as const;
// ── Push Event Channels ──────────────────────────────────────────────
export const WS_CHANNELS = {
terminalEvent: "terminal.event",
serverWelcome: "server.welcome",
serverConfigUpdated: "server.configUpdated",
} as const;
// -- Tagged Union of all request body schemas ─────────────────────────
const tagRequestBody = <const Tag extends string, const Fields extends Schema.Struct.Fields>(
tag: Tag,
schema: Schema.Struct<Fields>,
) =>
schema.mapFields(
Struct.assign({ _tag: Schema.tag(tag) }),
// PreserveChecks is safe here. No existing schema should have checks depending on the tag
{ unsafePreserveChecks: true },
);
const WebSocketRequestBody = Schema.Union([
// Orchestration methods
tagRequestBody(
ORCHESTRATION_WS_METHODS.dispatchCommand,
Schema.Struct({ command: ClientOrchestrationCommand }),
),
tagRequestBody(ORCHESTRATION_WS_METHODS.getSnapshot, OrchestrationGetSnapshotInput),
tagRequestBody(ORCHESTRATION_WS_METHODS.getTurnDiff, OrchestrationGetTurnDiffInput),
tagRequestBody(ORCHESTRATION_WS_METHODS.getFullThreadDiff, OrchestrationGetFullThreadDiffInput),
tagRequestBody(ORCHESTRATION_WS_METHODS.replayEvents, OrchestrationReplayEventsInput),
// Project Search
tagRequestBody(WS_METHODS.projectsSearchEntries, ProjectSearchEntriesInput),
tagRequestBody(WS_METHODS.projectsWriteFile, ProjectWriteFileInput),
// Shell methods
tagRequestBody(WS_METHODS.shellOpenInEditor, OpenInEditorInput),
// Git methods
tagRequestBody(WS_METHODS.gitPull, GitPullInput),
tagRequestBody(WS_METHODS.gitStatus, GitStatusInput),
tagRequestBody(WS_METHODS.gitRunStackedAction, GitRunStackedActionInput),
tagRequestBody(WS_METHODS.gitListBranches, GitListBranchesInput),
tagRequestBody(WS_METHODS.gitCreateWorktree, GitCreateWorktreeInput),
tagRequestBody(WS_METHODS.gitRemoveWorktree, GitRemoveWorktreeInput),
tagRequestBody(WS_METHODS.gitCreateBranch, GitCreateBranchInput),
tagRequestBody(WS_METHODS.gitCheckout, GitCheckoutInput),
tagRequestBody(WS_METHODS.gitInit, GitInitInput),
// Terminal methods
tagRequestBody(WS_METHODS.terminalOpen, TerminalOpenInput),
tagRequestBody(WS_METHODS.terminalWrite, TerminalWriteInput),
tagRequestBody(WS_METHODS.terminalResize, TerminalResizeInput),
tagRequestBody(WS_METHODS.terminalClear, TerminalClearInput),
tagRequestBody(WS_METHODS.terminalRestart, TerminalRestartInput),
tagRequestBody(WS_METHODS.terminalClose, TerminalCloseInput),
// Server meta
tagRequestBody(WS_METHODS.serverGetConfig, Schema.Struct({})),
tagRequestBody(WS_METHODS.serverUpsertKeybinding, KeybindingRule),
]);
export const WebSocketRequest = Schema.Struct({
id: TrimmedNonEmptyString,
body: WebSocketRequestBody,
});
export type WebSocketRequest = typeof WebSocketRequest.Type;
export const WebSocketResponse = Schema.Struct({
id: TrimmedNonEmptyString,
result: Schema.optional(Schema.Unknown),
error: Schema.optional(
Schema.Struct({
message: Schema.String,
}),
),
});
export type WebSocketResponse = typeof WebSocketResponse.Type;
export const WsPushSequence = NonNegativeInt;
export type WsPushSequence = typeof WsPushSequence.Type;
export const WsWelcomePayload = Schema.Struct({
cwd: TrimmedNonEmptyString,
projectName: TrimmedNonEmptyString,
bootstrapProjectId: Schema.optional(ProjectId),
bootstrapThreadId: Schema.optional(ThreadId),
});
export type WsWelcomePayload = typeof WsWelcomePayload.Type;
export const WsDecodeDiagnosticCode = Schema.Literals([
"invalid-json",
"invalid-envelope",
"invalid-request",
"invalid-response",
]);
export type WsDecodeDiagnosticCode = typeof WsDecodeDiagnosticCode.Type;
export const WsDecodeDiagnostic = Schema.Struct({
code: WsDecodeDiagnosticCode,
reason: TrimmedNonEmptyString,
rawKind: Schema.optional(TrimmedNonEmptyString),
expected: Schema.optional(TrimmedNonEmptyString),
actual: Schema.optional(TrimmedNonEmptyString),
path: Schema.optional(Schema.Array(TrimmedNonEmptyString)),
jsonOffset: Schema.optional(NonNegativeInt),
});
export type WsDecodeDiagnostic = typeof WsDecodeDiagnostic.Type;
export interface WsPushPayloadByChannel {
readonly [WS_CHANNELS.serverWelcome]: WsWelcomePayload;
readonly [WS_CHANNELS.serverConfigUpdated]: typeof ServerConfigUpdatedPayload.Type;
readonly [WS_CHANNELS.terminalEvent]: typeof TerminalEvent.Type;
readonly [ORCHESTRATION_WS_CHANNELS.domainEvent]: OrchestrationEvent;
}
export type WsPushChannel = keyof WsPushPayloadByChannel;
export type WsPushData<C extends WsPushChannel> = WsPushPayloadByChannel[C];
const makeWsPushSchema = <const Channel extends string, Payload extends Schema.Schema<any>>(
channel: Channel,
payload: Payload,
) =>
Schema.Struct({
type: Schema.Literal("push"),
sequence: WsPushSequence,
channel: Schema.Literal(channel),
data: payload,
});
export const WsPushServerWelcome = makeWsPushSchema(WS_CHANNELS.serverWelcome, WsWelcomePayload);
export const WsPushServerConfigUpdated = makeWsPushSchema(
WS_CHANNELS.serverConfigUpdated,
ServerConfigUpdatedPayload,
);
export const WsPushTerminalEvent = makeWsPushSchema(WS_CHANNELS.terminalEvent, TerminalEvent);
export const WsPushOrchestrationDomainEvent = makeWsPushSchema(
ORCHESTRATION_WS_CHANNELS.domainEvent,
OrchestrationEvent,
);
export const WsPushChannelSchema = Schema.Literals([
WS_CHANNELS.serverWelcome,
WS_CHANNELS.serverConfigUpdated,
WS_CHANNELS.terminalEvent,
ORCHESTRATION_WS_CHANNELS.domainEvent,
]);
export type WsPushChannelSchema = typeof WsPushChannelSchema.Type;
export const WsPush = Schema.Union([
WsPushServerWelcome,
WsPushServerConfigUpdated,
WsPushTerminalEvent,
WsPushOrchestrationDomainEvent,
]);
export type WsPush = typeof WsPush.Type;
export type WsPushMessage<C extends WsPushChannel> = Extract<WsPush, { channel: C }>;
export const WsPushEnvelopeBase = Schema.Struct({
type: Schema.Literal("push"),
sequence: WsPushSequence,
channel: WsPushChannelSchema,
data: Schema.Unknown,
});
export type WsPushEnvelopeBase = typeof WsPushEnvelopeBase.Type;
// ── Union of all server → client messages ─────────────────────────────
export const WsResponse = Schema.Union([WebSocketResponse, WsPush]);
export type WsResponse = typeof WsResponse.Type;