import { z } from 'zod' import { AGENT_LIFECYCLE_STATUSES } from './agent-lifecycle.js' import { MAX_EXPLICIT_AGENT_TITLE_CHARS } from '../server/agent/agent-title-limits.js' import { AgentProviderSchema } from '../server/agent/provider-manifest.js' import { TOOL_CALL_ICON_NAMES } from '../server/agent/agent-sdk-types.js' import type { AgentCapabilityFlags, AgentModelDefinition, AgentMode, AgentPermissionRequest, AgentPermissionResponse, AgentPersistenceHandle, AgentRuntimeInfo, AgentTimelineItem, ToolCallDetail, ToolCallTimelineItem, AgentUsage, } from '../server/agent/agent-sdk-types.js' export const AgentStatusSchema = z.enum(AGENT_LIFECYCLE_STATUSES) const AgentModeSchema: z.ZodType = z.object({ id: z.string(), label: z.string(), description: z.string().optional(), }) const AgentSelectOptionSchema = z.object({ id: z.string(), label: z.string(), description: z.string().optional(), isDefault: z.boolean().optional(), metadata: z.record(z.unknown()).optional(), }) const AgentModelDefinitionSchema: z.ZodType = z.object({ provider: AgentProviderSchema, id: z.string(), label: z.string(), description: z.string().optional(), isDefault: z.boolean().optional(), metadata: z.record(z.unknown()).optional(), thinkingOptions: z.array(AgentSelectOptionSchema).optional(), defaultThinkingOptionId: z.string().optional(), }) const AgentCapabilityFlagsSchema: z.ZodType = z.object({ supportsStreaming: z.boolean(), supportsSessionPersistence: z.boolean(), supportsDynamicModes: z.boolean(), supportsMcpServers: z.boolean(), supportsReasoningStream: z.boolean(), supportsToolInvocations: z.boolean(), }) const AgentUsageSchema: z.ZodType = z.object({ inputTokens: z.number().optional(), cachedInputTokens: z.number().optional(), outputTokens: z.number().optional(), totalCostUsd: z.number().optional(), }) const McpStdioServerConfigSchema = z.object({ type: z.literal('stdio'), command: z.string(), args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), }) const McpHttpServerConfigSchema = z.object({ type: z.literal('http'), url: z.string(), headers: z.record(z.string()).optional(), }) const McpSseServerConfigSchema = z.object({ type: z.literal('sse'), url: z.string(), headers: z.record(z.string()).optional(), }) const McpServerConfigSchema = z.discriminatedUnion('type', [ McpStdioServerConfigSchema, McpHttpServerConfigSchema, McpSseServerConfigSchema, ]) const AgentSessionConfigSchema = z.object({ provider: AgentProviderSchema, cwd: z.string(), modeId: z.string().optional(), model: z.string().optional(), thinkingOptionId: z.string().optional(), title: z.string().trim().min(1).max(MAX_EXPLICIT_AGENT_TITLE_CHARS).optional().nullable(), approvalPolicy: z.string().optional(), sandboxMode: z.string().optional(), networkAccess: z.boolean().optional(), webSearch: z.boolean().optional(), extra: z .object({ codex: z.record(z.unknown()).optional(), claude: z.record(z.unknown()).optional(), }) .partial() .optional(), systemPrompt: z.string().optional(), mcpServers: z.record(McpServerConfigSchema).optional(), }) const AgentPermissionUpdateSchema = z.record(z.unknown()) export const AgentPermissionResponseSchema: z.ZodType = z.union([ z.object({ behavior: z.literal('allow'), updatedInput: z.record(z.unknown()).optional(), updatedPermissions: z.array(AgentPermissionUpdateSchema).optional(), }), z.object({ behavior: z.literal('deny'), message: z.string().optional(), interrupt: z.boolean().optional(), }), ]) export const AgentPermissionRequestPayloadSchema: z.ZodType = z.object({ id: z.string(), provider: AgentProviderSchema, name: z.string(), kind: z.enum(['tool', 'plan', 'question', 'mode', 'other']), title: z.string().optional(), description: z.string().optional(), input: z.record(z.unknown()).optional(), suggestions: z.array(AgentPermissionUpdateSchema).optional(), metadata: z.record(z.unknown()).optional(), }) const UnknownValueSchema = z.union([ z.null(), z.boolean(), z.number(), z.string(), z.array(z.unknown()), z.object({}).passthrough(), ]) const NonNullUnknownSchema = z.union([ z.boolean(), z.number(), z.string(), z.array(z.unknown()), z.object({}).passthrough(), ]) const ToolCallDetailPayloadSchema: z.ZodType = z.discriminatedUnion('type', [ z.object({ type: z.literal('shell'), command: z.string(), cwd: z.string().optional(), output: z.string().optional(), exitCode: z.number().nullable().optional(), }), z.object({ type: z.literal('read'), filePath: z.string(), content: z.string().optional(), offset: z.number().optional(), limit: z.number().optional(), }), z.object({ type: z.literal('edit'), filePath: z.string(), oldString: z.string().optional(), newString: z.string().optional(), unifiedDiff: z.string().optional(), }), z.object({ type: z.literal('write'), filePath: z.string(), content: z.string().optional(), }), z.object({ type: z.literal('search'), query: z.string(), }), z.object({ type: z.literal('worktree_setup'), worktreePath: z.string(), branchName: z.string(), log: z.string(), commands: z.array( z.object({ index: z.number().int().positive(), command: z.string(), cwd: z.string(), status: z.enum(['running', 'completed', 'failed']), exitCode: z.number().nullable(), durationMs: z.number().nonnegative().optional(), }) ), truncated: z.boolean().optional(), }), z.object({ type: z.literal('sub_agent'), subAgentType: z.string().optional(), description: z.string().optional(), log: z.string(), actions: z.array( z.object({ index: z.number().int().positive(), toolName: z.string(), summary: z.string().optional(), }) ), }), z.object({ type: z.literal('plain_text'), label: z.string().optional(), text: z.string().optional(), icon: z.enum(TOOL_CALL_ICON_NAMES).optional(), }), z.object({ type: z.literal('unknown'), input: UnknownValueSchema, output: UnknownValueSchema, }), ]) const ToolCallBasePayloadSchema = z .object({ type: z.literal('tool_call'), callId: z.string(), name: z.string(), detail: ToolCallDetailPayloadSchema, metadata: z.record(z.unknown()).optional(), }) .strict() const ToolCallRunningPayloadSchema = ToolCallBasePayloadSchema.extend({ status: z.literal('running'), error: z.null(), }) const ToolCallCompletedPayloadSchema = ToolCallBasePayloadSchema.extend({ status: z.literal('completed'), error: z.null(), }) const ToolCallFailedPayloadSchema = ToolCallBasePayloadSchema.extend({ status: z.literal('failed'), error: NonNullUnknownSchema, }) const ToolCallCanceledPayloadSchema = ToolCallBasePayloadSchema.extend({ status: z.literal('canceled'), error: z.null(), }) const ToolCallTimelineItemPayloadSchema: z.ZodType = z.union([ ToolCallRunningPayloadSchema, ToolCallCompletedPayloadSchema, ToolCallFailedPayloadSchema, ToolCallCanceledPayloadSchema, ]) export const AgentTimelineItemPayloadSchema: z.ZodType = z.union([ z.object({ type: z.literal('user_message'), text: z.string(), messageId: z.string().optional(), }), z.object({ type: z.literal('assistant_message'), text: z.string(), }), z.object({ type: z.literal('reasoning'), text: z.string(), }), ToolCallTimelineItemPayloadSchema, z.object({ type: z.literal('todo'), items: z.array( z.object({ text: z.string(), completed: z.boolean(), }) ), }), z.object({ type: z.literal('error'), message: z.string(), }), z.object({ type: z.literal('compaction'), status: z.enum(['loading', 'completed']), trigger: z.enum(['auto', 'manual']).optional(), preTokens: z.number().optional(), }), ]) export const AgentStreamEventPayloadSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('thread_started'), sessionId: z.string(), provider: AgentProviderSchema, }), z.object({ type: z.literal('turn_started'), provider: AgentProviderSchema, }), z.object({ type: z.literal('turn_completed'), provider: AgentProviderSchema, usage: AgentUsageSchema.optional(), }), z.object({ type: z.literal('turn_failed'), provider: AgentProviderSchema, error: z.string(), code: z.string().optional(), diagnostic: z.string().optional(), }), z.object({ type: z.literal('turn_canceled'), provider: AgentProviderSchema, reason: z.string(), }), z.object({ type: z.literal('timeline'), provider: AgentProviderSchema, item: AgentTimelineItemPayloadSchema, }), z.object({ type: z.literal('permission_requested'), provider: AgentProviderSchema, request: AgentPermissionRequestPayloadSchema, }), z.object({ type: z.literal('permission_resolved'), provider: AgentProviderSchema, requestId: z.string(), resolution: AgentPermissionResponseSchema, }), z.object({ type: z.literal('attention_required'), provider: AgentProviderSchema, reason: z.enum(['finished', 'error', 'permission']), timestamp: z.string(), shouldNotify: z.boolean(), notification: z .object({ title: z.string(), body: z.string(), data: z.object({ serverId: z.string(), agentId: z.string(), reason: z.enum(['finished', 'error', 'permission']), }), }) .optional(), }), ]) const AgentPersistenceHandleSchema: z.ZodType = z .object({ provider: AgentProviderSchema, sessionId: z.string(), nativeHandle: z.string().optional(), metadata: z.record(z.unknown()).optional(), }) .nullable() const AgentRuntimeInfoSchema: z.ZodType = z.object({ provider: AgentProviderSchema, sessionId: z.string().nullable(), model: z.string().nullable().optional(), thinkingOptionId: z.string().nullable().optional(), modeId: z.string().nullable().optional(), extra: z.record(z.unknown()).optional(), }) export const AgentSnapshotPayloadSchema = z.object({ id: z.string(), provider: AgentProviderSchema, cwd: z.string(), model: z.string().nullable(), thinkingOptionId: z.string().nullable().optional(), effectiveThinkingOptionId: z.string().nullable().optional(), createdAt: z.string(), updatedAt: z.string(), lastUserMessageAt: z.string().nullable(), status: AgentStatusSchema, capabilities: AgentCapabilityFlagsSchema, currentModeId: z.string().nullable(), availableModes: z.array(AgentModeSchema), pendingPermissions: z.array(AgentPermissionRequestPayloadSchema), persistence: AgentPersistenceHandleSchema.nullable(), runtimeInfo: AgentRuntimeInfoSchema.optional(), lastUsage: AgentUsageSchema.optional(), lastError: z.string().optional(), title: z.string().nullable(), labels: z.record(z.string()).default({}), requiresAttention: z.boolean().optional(), attentionReason: z.enum(['finished', 'error', 'permission']).nullable().optional(), attentionTimestamp: z.string().nullable().optional(), archivedAt: z.string().nullable().optional(), }) export type AgentSnapshotPayload = z.infer export type AgentStreamEventPayload = z.infer // ============================================================================ // Session Inbound Messages (Session receives these) // ============================================================================ export const VoiceAudioChunkMessageSchema = z.object({ type: z.literal('voice_audio_chunk'), audio: z.string(), // base64 encoded format: z.string(), isLast: z.boolean(), }) export const AbortRequestMessageSchema = z.object({ type: z.literal('abort_request'), }) export const AudioPlayedMessageSchema = z.object({ type: z.literal('audio_played'), id: z.string(), }) const AgentDirectoryFilterSchema = z.object({ labels: z.record(z.string()).optional(), projectKeys: z.array(z.string()).optional(), statuses: z.array(AgentStatusSchema).optional(), includeArchived: z.boolean().optional(), requiresAttention: z.boolean().optional(), thinkingOptionId: z.string().nullable().optional(), }) export const DeleteAgentRequestMessageSchema = z.object({ type: z.literal('delete_agent_request'), agentId: z.string(), requestId: z.string(), }) export const ArchiveAgentRequestMessageSchema = z.object({ type: z.literal('archive_agent_request'), agentId: z.string(), requestId: z.string(), }) export const UpdateAgentRequestMessageSchema = z.object({ type: z.literal('update_agent_request'), agentId: z.string(), name: z.string().optional(), labels: z.record(z.string()).optional(), requestId: z.string(), }) export const SetVoiceModeMessageSchema = z.object({ type: z.literal('set_voice_mode'), enabled: z.boolean(), agentId: z.string().optional(), requestId: z.string().optional(), }) export const SendAgentMessageSchema = z.object({ type: z.literal('send_agent_message'), agentId: z.string(), text: z.string(), messageId: z.string().optional(), // Client-provided ID for deduplication images: z .array( z.object({ data: z.string(), // base64 encoded image mimeType: z.string(), // e.g., "image/jpeg", "image/png" }) ) .optional(), }) // ============================================================================ // Agent RPCs (requestId-correlated) // ============================================================================ export const FetchAgentsRequestMessageSchema = z.object({ type: z.literal('fetch_agents_request'), requestId: z.string(), filter: AgentDirectoryFilterSchema.optional(), sort: z .array( z.object({ key: z.enum(['status_priority', 'created_at', 'updated_at', 'title']), direction: z.enum(['asc', 'desc']), }) ) .optional(), page: z .object({ limit: z.number().int().positive().max(200), cursor: z.string().min(1).optional(), }) .optional(), subscribe: z .object({ subscriptionId: z.string().optional(), }) .optional(), }) const WorkspaceStateBucketSchema = z.enum([ 'needs_input', 'failed', 'running', 'attention', 'done', ]) export const FetchWorkspacesRequestMessageSchema = z.object({ type: z.literal('fetch_workspaces_request'), requestId: z.string(), filter: z .object({ query: z.string().optional(), projectId: z.string().optional(), idPrefix: z.string().optional(), }) .optional(), sort: z .array( z.object({ key: z.enum(['status_priority', 'activity_at', 'name', 'project_id']), direction: z.enum(['asc', 'desc']), }) ) .optional(), page: z .object({ limit: z.number().int().positive().max(200), cursor: z.string().min(1).optional(), }) .optional(), subscribe: z .object({ subscriptionId: z.string().optional(), }) .optional(), }) export const FetchAgentRequestMessageSchema = z.object({ type: z.literal('fetch_agent_request'), requestId: z.string(), /** Accepts full ID, unique prefix, or exact full title (server resolves). */ agentId: z.string(), }) export const SendAgentMessageRequestSchema = z.object({ type: z.literal('send_agent_message_request'), requestId: z.string(), /** Accepts full ID, unique prefix, or exact full title (server resolves). */ agentId: z.string(), text: z.string(), messageId: z.string().optional(), // Client-provided ID for deduplication images: z .array( z.object({ data: z.string(), // base64 encoded image mimeType: z.string(), // e.g., "image/jpeg", "image/png" }) ) .optional(), }) export const WaitForFinishRequestSchema = z.object({ type: z.literal('wait_for_finish_request'), requestId: z.string(), /** Accepts full ID, unique prefix, or exact full title (server resolves). */ agentId: z.string(), timeoutMs: z.number().int().positive().optional(), }) // ============================================================================ // Dictation Streaming (lossless, resumable) // ============================================================================ export const DictationStreamStartMessageSchema = z.object({ type: z.literal('dictation_stream_start'), dictationId: z.string(), format: z.string(), // e.g. "audio/pcm;rate=16000;bits=16" }) export const DictationStreamChunkMessageSchema = z.object({ type: z.literal('dictation_stream_chunk'), dictationId: z.string(), seq: z.number().int().nonnegative(), audio: z.string(), // base64 encoded chunk format: z.string(), // e.g. "audio/pcm;rate=16000;bits=16" }) export const DictationStreamFinishMessageSchema = z.object({ type: z.literal('dictation_stream_finish'), dictationId: z.string(), finalSeq: z.number().int().nonnegative(), }) export const DictationStreamCancelMessageSchema = z.object({ type: z.literal('dictation_stream_cancel'), dictationId: z.string(), }) const GitSetupOptionsSchema = z.object({ baseBranch: z.string().optional(), createNewBranch: z.boolean().optional(), newBranchName: z.string().optional(), createWorktree: z.boolean().optional(), worktreeSlug: z.string().optional(), }) export type GitSetupOptions = z.infer export const CreateAgentRequestMessageSchema = z.object({ type: z.literal('create_agent_request'), config: AgentSessionConfigSchema, worktreeName: z.string().optional(), initialPrompt: z.string().optional(), clientMessageId: z.string().optional(), outputSchema: z.record(z.unknown()).optional(), images: z .array( z.object({ data: z.string(), // base64 encoded image mimeType: z.string(), // e.g., "image/jpeg", "image/png" }) ) .optional(), git: GitSetupOptionsSchema.optional(), labels: z.record(z.string()).default({}), requestId: z.string(), }) export const ListProviderModelsRequestMessageSchema = z.object({ type: z.literal('list_provider_models_request'), provider: AgentProviderSchema, cwd: z.string().optional(), requestId: z.string(), }) export const ListAvailableProvidersRequestMessageSchema = z.object({ type: z.literal('list_available_providers_request'), requestId: z.string(), }) export const SpeechModelsListRequestSchema = z.object({ type: z.literal('speech_models_list_request'), requestId: z.string(), }) export const SpeechModelsDownloadRequestSchema = z.object({ type: z.literal('speech_models_download_request'), modelIds: z.array(z.string()).optional(), requestId: z.string(), }) export const ResumeAgentRequestMessageSchema = z.object({ type: z.literal('resume_agent_request'), handle: AgentPersistenceHandleSchema, overrides: AgentSessionConfigSchema.partial().optional(), requestId: z.string(), }) export const RefreshAgentRequestMessageSchema = z.object({ type: z.literal('refresh_agent_request'), agentId: z.string(), requestId: z.string(), }) export const CancelAgentRequestMessageSchema = z.object({ type: z.literal('cancel_agent_request'), agentId: z.string(), }) export const RestartServerRequestMessageSchema = z.object({ type: z.literal('restart_server_request'), reason: z.string().optional(), requestId: z.string(), }) export const ShutdownServerRequestMessageSchema = z.object({ type: z.literal('shutdown_server_request'), requestId: z.string(), }) export const AgentTimelineCursorSchema = z.object({ epoch: z.string(), seq: z.number().int().nonnegative(), }) export const FetchAgentTimelineRequestMessageSchema = z.object({ type: z.literal('fetch_agent_timeline_request'), agentId: z.string(), requestId: z.string(), direction: z.enum(['tail', 'before', 'after']).optional(), cursor: AgentTimelineCursorSchema.optional(), // 0 means "all matching rows for this query window". limit: z.number().int().nonnegative().optional(), // Default should be projected for app timeline loading. projection: z.enum(['projected', 'canonical']).optional(), }) export const SetAgentModeRequestMessageSchema = z.object({ type: z.literal('set_agent_mode_request'), agentId: z.string(), modeId: z.string(), requestId: z.string(), }) export const SetAgentModeResponseMessageSchema = z.object({ type: z.literal('set_agent_mode_response'), payload: z.object({ requestId: z.string(), agentId: z.string(), accepted: z.boolean(), error: z.string().nullable(), }), }) export const SetAgentModelRequestMessageSchema = z.object({ type: z.literal('set_agent_model_request'), agentId: z.string(), modelId: z.string().nullable(), requestId: z.string(), }) export const SetAgentModelResponseMessageSchema = z.object({ type: z.literal('set_agent_model_response'), payload: z.object({ requestId: z.string(), agentId: z.string(), accepted: z.boolean(), error: z.string().nullable(), }), }) export const SetAgentThinkingRequestMessageSchema = z.object({ type: z.literal('set_agent_thinking_request'), agentId: z.string(), thinkingOptionId: z.string().nullable(), requestId: z.string(), }) export const SetAgentThinkingResponseMessageSchema = z.object({ type: z.literal('set_agent_thinking_response'), payload: z.object({ requestId: z.string(), agentId: z.string(), accepted: z.boolean(), error: z.string().nullable(), }), }) export const UpdateAgentResponseMessageSchema = z.object({ type: z.literal('update_agent_response'), payload: z.object({ requestId: z.string(), agentId: z.string(), accepted: z.boolean(), error: z.string().nullable(), }), }) export const SetVoiceModeResponseMessageSchema = z.object({ type: z.literal('set_voice_mode_response'), payload: z.object({ requestId: z.string(), enabled: z.boolean(), agentId: z.string().nullable(), accepted: z.boolean(), error: z.string().nullable(), reasonCode: z.string().optional(), retryable: z.boolean().optional(), missingModelIds: z.array(z.string()).optional(), }), }) export const AgentPermissionResponseMessageSchema = z.object({ type: z.literal('agent_permission_response'), agentId: z.string(), requestId: z.string(), response: AgentPermissionResponseSchema, }) const CheckoutErrorCodeSchema = z.enum(['NOT_GIT_REPO', 'NOT_ALLOWED', 'MERGE_CONFLICT', 'UNKNOWN']) const CheckoutErrorSchema = z.object({ code: CheckoutErrorCodeSchema, message: z.string(), }) const CheckoutDiffCompareSchema = z.object({ mode: z.enum(['uncommitted', 'base']), baseRef: z.string().optional(), }) export const CheckoutStatusRequestSchema = z.object({ type: z.literal('checkout_status_request'), cwd: z.string(), requestId: z.string(), }) export const SubscribeCheckoutDiffRequestSchema = z.object({ type: z.literal('subscribe_checkout_diff_request'), subscriptionId: z.string(), cwd: z.string(), compare: CheckoutDiffCompareSchema, requestId: z.string(), }) export const UnsubscribeCheckoutDiffRequestSchema = z.object({ type: z.literal('unsubscribe_checkout_diff_request'), subscriptionId: z.string(), }) export const CheckoutCommitRequestSchema = z.object({ type: z.literal('checkout_commit_request'), cwd: z.string(), message: z.string().optional(), addAll: z.boolean().optional(), requestId: z.string(), }) export const CheckoutMergeRequestSchema = z.object({ type: z.literal('checkout_merge_request'), cwd: z.string(), baseRef: z.string().optional(), strategy: z.enum(['merge', 'squash']).optional(), requireCleanTarget: z.boolean().optional(), requestId: z.string(), }) export const CheckoutMergeFromBaseRequestSchema = z.object({ type: z.literal('checkout_merge_from_base_request'), cwd: z.string(), baseRef: z.string().optional(), requireCleanTarget: z.boolean().optional(), requestId: z.string(), }) export const CheckoutPushRequestSchema = z.object({ type: z.literal('checkout_push_request'), cwd: z.string(), requestId: z.string(), }) export const CheckoutPrCreateRequestSchema = z.object({ type: z.literal('checkout_pr_create_request'), cwd: z.string(), title: z.string().optional(), body: z.string().optional(), baseRef: z.string().optional(), requestId: z.string(), }) export const CheckoutPrStatusRequestSchema = z.object({ type: z.literal('checkout_pr_status_request'), cwd: z.string(), requestId: z.string(), }) export const ValidateBranchRequestSchema = z.object({ type: z.literal('validate_branch_request'), cwd: z.string(), branchName: z.string(), requestId: z.string(), }) export const BranchSuggestionsRequestSchema = z.object({ type: z.literal('branch_suggestions_request'), cwd: z.string(), query: z.string().optional(), limit: z.number().int().min(1).max(200).optional(), requestId: z.string(), }) export const DirectorySuggestionsRequestSchema = z.object({ type: z.literal('directory_suggestions_request'), query: z.string(), cwd: z.string().optional(), includeFiles: z.boolean().optional(), includeDirectories: z.boolean().optional(), limit: z.number().int().min(1).max(100).optional(), requestId: z.string(), }) export const PaseoWorktreeListRequestSchema = z.object({ type: z.literal('paseo_worktree_list_request'), cwd: z.string().optional(), repoRoot: z.string().optional(), requestId: z.string(), }) export const PaseoWorktreeArchiveRequestSchema = z.object({ type: z.literal('paseo_worktree_archive_request'), worktreePath: z.string().optional(), repoRoot: z.string().optional(), branchName: z.string().optional(), requestId: z.string(), }) export const OpenProjectRequestSchema = z.object({ type: z.literal('open_project_request'), cwd: z.string(), requestId: z.string(), }) export const ArchiveWorkspaceRequestSchema = z.object({ type: z.literal('archive_workspace_request'), workspaceId: z.string(), requestId: z.string(), }) // Highlighted diff token schema // Note: style can be a compound class name (e.g., "heading meta") from the syntax highlighter const HighlightTokenSchema = z.object({ text: z.string(), style: z.string().nullable(), }) const DiffLineSchema = z.object({ type: z.enum(['add', 'remove', 'context', 'header']), content: z.string(), tokens: z.array(HighlightTokenSchema).optional(), }) const DiffHunkSchema = z.object({ oldStart: z.number(), oldCount: z.number(), newStart: z.number(), newCount: z.number(), lines: z.array(DiffLineSchema), }) const ParsedDiffFileSchema = z.object({ path: z.string(), isNew: z.boolean(), isDeleted: z.boolean(), additions: z.number(), deletions: z.number(), hunks: z.array(DiffHunkSchema), status: z.enum(['ok', 'too_large', 'binary']).optional(), }) const FileExplorerEntrySchema = z.object({ name: z.string(), path: z.string(), kind: z.enum(['file', 'directory']), size: z.number(), modifiedAt: z.string(), }) const FileExplorerFileSchema = z.object({ path: z.string(), kind: z.enum(['text', 'image', 'binary']), encoding: z.enum(['utf-8', 'base64', 'none']), content: z.string().optional(), mimeType: z.string().optional(), size: z.number(), modifiedAt: z.string(), }) const FileExplorerDirectorySchema = z.object({ path: z.string(), entries: z.array(FileExplorerEntrySchema), }) export const FileExplorerRequestSchema = z.object({ type: z.literal('file_explorer_request'), cwd: z.string(), path: z.string().optional(), mode: z.enum(['list', 'file']), requestId: z.string(), }) export const ProjectIconRequestSchema = z.object({ type: z.literal('project_icon_request'), cwd: z.string(), requestId: z.string(), }) export const FileDownloadTokenRequestSchema = z.object({ type: z.literal('file_download_token_request'), cwd: z.string(), path: z.string(), requestId: z.string(), }) export const ClearAgentAttentionMessageSchema = z.object({ type: z.literal('clear_agent_attention'), agentId: z.union([z.string(), z.array(z.string())]), }) export const ClientHeartbeatMessageSchema = z.object({ type: z.literal('client_heartbeat'), deviceType: z.enum(['web', 'mobile']), focusedAgentId: z.string().nullable(), lastActivityAt: z.string(), appVisible: z.boolean(), appVisibilityChangedAt: z.string().optional(), }) export const PingMessageSchema = z.object({ type: z.literal('ping'), requestId: z.string(), clientSentAt: z.number().int().optional(), }) const ListCommandsDraftConfigSchema = z.object({ provider: AgentProviderSchema, cwd: z.string(), modeId: z.string().optional(), model: z.string().optional(), thinkingOptionId: z.string().optional(), }) export const ListCommandsRequestSchema = z.object({ type: z.literal('list_commands_request'), agentId: z.string(), draftConfig: ListCommandsDraftConfigSchema.optional(), requestId: z.string(), }) export const RegisterPushTokenMessageSchema = z.object({ type: z.literal('register_push_token'), token: z.string(), }) // ============================================================================ // Terminal Messages // ============================================================================ export const ListTerminalsRequestSchema = z.object({ type: z.literal('list_terminals_request'), cwd: z.string(), requestId: z.string(), }) export const SubscribeTerminalsRequestSchema = z.object({ type: z.literal('subscribe_terminals_request'), cwd: z.string(), }) export const UnsubscribeTerminalsRequestSchema = z.object({ type: z.literal('unsubscribe_terminals_request'), cwd: z.string(), }) export const CreateTerminalRequestSchema = z.object({ type: z.literal('create_terminal_request'), cwd: z.string(), name: z.string().optional(), requestId: z.string(), }) export const SubscribeTerminalRequestSchema = z.object({ type: z.literal('subscribe_terminal_request'), terminalId: z.string(), requestId: z.string(), }) export const UnsubscribeTerminalRequestSchema = z.object({ type: z.literal('unsubscribe_terminal_request'), terminalId: z.string(), }) const TerminalClientMessageSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('input'), data: z.string() }), z.object({ type: z.literal('resize'), rows: z.number(), cols: z.number() }), z.object({ type: z.literal('mouse'), row: z.number(), col: z.number(), button: z.number(), action: z.enum(['down', 'up', 'move']), }), ]) export const TerminalInputSchema = z.object({ type: z.literal('terminal_input'), terminalId: z.string(), message: TerminalClientMessageSchema, }) export const KillTerminalRequestSchema = z.object({ type: z.literal('kill_terminal_request'), terminalId: z.string(), requestId: z.string(), }) export const AttachTerminalStreamRequestSchema = z.object({ type: z.literal('attach_terminal_stream_request'), terminalId: z.string(), resumeOffset: z.number().int().nonnegative().optional(), rows: z.number().int().positive().optional(), cols: z.number().int().positive().optional(), requestId: z.string(), }) export const DetachTerminalStreamRequestSchema = z.object({ type: z.literal('detach_terminal_stream_request'), streamId: z.number().int().nonnegative(), requestId: z.string(), }) export const SessionInboundMessageSchema = z.discriminatedUnion('type', [ VoiceAudioChunkMessageSchema, AbortRequestMessageSchema, AudioPlayedMessageSchema, FetchAgentsRequestMessageSchema, FetchWorkspacesRequestMessageSchema, FetchAgentRequestMessageSchema, DeleteAgentRequestMessageSchema, ArchiveAgentRequestMessageSchema, UpdateAgentRequestMessageSchema, SetVoiceModeMessageSchema, SendAgentMessageRequestSchema, WaitForFinishRequestSchema, DictationStreamStartMessageSchema, DictationStreamChunkMessageSchema, DictationStreamFinishMessageSchema, DictationStreamCancelMessageSchema, CreateAgentRequestMessageSchema, ListProviderModelsRequestMessageSchema, ListAvailableProvidersRequestMessageSchema, SpeechModelsListRequestSchema, SpeechModelsDownloadRequestSchema, ResumeAgentRequestMessageSchema, RefreshAgentRequestMessageSchema, CancelAgentRequestMessageSchema, ShutdownServerRequestMessageSchema, RestartServerRequestMessageSchema, FetchAgentTimelineRequestMessageSchema, SetAgentModeRequestMessageSchema, SetAgentModelRequestMessageSchema, SetAgentThinkingRequestMessageSchema, AgentPermissionResponseMessageSchema, CheckoutStatusRequestSchema, SubscribeCheckoutDiffRequestSchema, UnsubscribeCheckoutDiffRequestSchema, CheckoutCommitRequestSchema, CheckoutMergeRequestSchema, CheckoutMergeFromBaseRequestSchema, CheckoutPushRequestSchema, CheckoutPrCreateRequestSchema, CheckoutPrStatusRequestSchema, ValidateBranchRequestSchema, BranchSuggestionsRequestSchema, DirectorySuggestionsRequestSchema, PaseoWorktreeListRequestSchema, PaseoWorktreeArchiveRequestSchema, OpenProjectRequestSchema, ArchiveWorkspaceRequestSchema, FileExplorerRequestSchema, ProjectIconRequestSchema, FileDownloadTokenRequestSchema, ClearAgentAttentionMessageSchema, ClientHeartbeatMessageSchema, PingMessageSchema, ListCommandsRequestSchema, RegisterPushTokenMessageSchema, ListTerminalsRequestSchema, SubscribeTerminalsRequestSchema, UnsubscribeTerminalsRequestSchema, CreateTerminalRequestSchema, SubscribeTerminalRequestSchema, UnsubscribeTerminalRequestSchema, TerminalInputSchema, KillTerminalRequestSchema, AttachTerminalStreamRequestSchema, DetachTerminalStreamRequestSchema, ]) export type SessionInboundMessage = z.infer // ============================================================================ // Session Outbound Messages (Session emits these) // ============================================================================ export const ActivityLogPayloadSchema = z.object({ id: z.string(), timestamp: z.coerce.date(), type: z.enum(['transcript', 'assistant', 'tool_call', 'tool_result', 'error', 'system']), content: z.string(), metadata: z.record(z.unknown()).optional(), }) export const ActivityLogMessageSchema = z.object({ type: z.literal('activity_log'), payload: ActivityLogPayloadSchema, }) export const AssistantChunkMessageSchema = z.object({ type: z.literal('assistant_chunk'), payload: z.object({ chunk: z.string(), }), }) export const AudioOutputMessageSchema = z.object({ type: z.literal('audio_output'), payload: z.object({ audio: z.string(), // base64 encoded format: z.string(), id: z.string(), isVoiceMode: z.boolean(), // Mode when audio was generated (for drift protection) groupId: z.string().optional(), // Logical utterance id chunkIndex: z.number().int().nonnegative().optional(), isLastChunk: z.boolean().optional(), }), }) export const TranscriptionResultMessageSchema = z.object({ type: z.literal('transcription_result'), payload: z.object({ text: z.string(), language: z.string().optional(), duration: z.number().optional(), requestId: z.string(), // Echoed back from request for tracking avgLogprob: z.number().optional(), isLowConfidence: z.boolean().optional(), byteLength: z.number().optional(), format: z.string().optional(), debugRecordingPath: z.string().optional(), }), }) export const DictationStreamAckMessageSchema = z.object({ type: z.literal('dictation_stream_ack'), payload: z.object({ dictationId: z.string(), ackSeq: z.number().int(), }), }) export const DictationStreamFinishAcceptedMessageSchema = z.object({ type: z.literal('dictation_stream_finish_accepted'), payload: z.object({ dictationId: z.string(), timeoutMs: z.number().int().positive(), }), }) export const DictationStreamPartialMessageSchema = z.object({ type: z.literal('dictation_stream_partial'), payload: z.object({ dictationId: z.string(), text: z.string(), }), }) export const DictationStreamFinalMessageSchema = z.object({ type: z.literal('dictation_stream_final'), payload: z.object({ dictationId: z.string(), text: z.string(), debugRecordingPath: z.string().optional(), }), }) export const DictationStreamErrorMessageSchema = z.object({ type: z.literal('dictation_stream_error'), payload: z.object({ dictationId: z.string(), error: z.string(), retryable: z.boolean(), reasonCode: z.string().optional(), missingModelIds: z.array(z.string()).optional(), debugRecordingPath: z.string().optional(), }), }) export const ServerCapabilityStateSchema = z.object({ enabled: z.boolean(), reason: z.string(), }) export const ServerVoiceCapabilitiesSchema = z.object({ dictation: ServerCapabilityStateSchema, voice: ServerCapabilityStateSchema, }) export const ServerCapabilitiesSchema = z .object({ voice: ServerVoiceCapabilitiesSchema.optional(), }) .passthrough() const ServerInfoHostnameSchema = z.unknown().transform((value): string | null => { if (typeof value !== 'string') { return null } const trimmed = value.trim() return trimmed.length > 0 ? trimmed : null }) const ServerInfoVersionSchema = z.unknown().transform((value): string | null => { if (typeof value !== 'string') { return null } const trimmed = value.trim() return trimmed.length > 0 ? trimmed : null }) const ServerCapabilitiesFromUnknownSchema = z .unknown() .optional() .transform((value): z.infer | undefined => { if (value === undefined) { return undefined } const parsed = ServerCapabilitiesSchema.safeParse(value) if (!parsed.success) { return undefined } return parsed.data }) export const ServerInfoStatusPayloadSchema = z .object({ status: z.literal('server_info'), serverId: z.string().trim().min(1), hostname: ServerInfoHostnameSchema.optional(), version: ServerInfoVersionSchema.optional(), capabilities: ServerCapabilitiesFromUnknownSchema, }) .passthrough() .transform((payload) => ({ ...payload, hostname: payload.hostname ?? null, version: payload.version ?? null, })) export const StatusMessageSchema = z.object({ type: z.literal('status'), payload: z .object({ status: z.string(), }) .passthrough(), // Allow additional fields }) export const PongMessageSchema = z.object({ type: z.literal('pong'), payload: z.object({ requestId: z.string(), clientSentAt: z.number().int().optional(), serverReceivedAt: z.number().int(), serverSentAt: z.number().int(), }), }) export const RpcErrorMessageSchema = z.object({ type: z.literal('rpc_error'), payload: z.object({ requestId: z.string(), requestType: z.string().optional(), error: z.string(), code: z.string().optional(), }), }) const AgentStatusWithRequestSchema = z.object({ agentId: z.string(), requestId: z.string(), }) const AgentStatusWithTimelineSchema = AgentStatusWithRequestSchema.extend({ timelineSize: z.number().optional(), }) export const AgentCreatedStatusPayloadSchema = z .object({ status: z.literal('agent_created'), agent: AgentSnapshotPayloadSchema, }) .extend(AgentStatusWithRequestSchema.shape) export const AgentCreateFailedStatusPayloadSchema = z.object({ status: z.literal('agent_create_failed'), requestId: z.string(), error: z.string(), }) export const AgentResumedStatusPayloadSchema = z .object({ status: z.literal('agent_resumed'), agent: AgentSnapshotPayloadSchema, }) .extend(AgentStatusWithTimelineSchema.shape) export const AgentRefreshedStatusPayloadSchema = z .object({ status: z.literal('agent_refreshed'), }) .extend(AgentStatusWithTimelineSchema.shape) export const RestartRequestedStatusPayloadSchema = z.object({ status: z.literal('restart_requested'), clientId: z.string(), reason: z.string().optional(), requestId: z.string(), }) export const ShutdownRequestedStatusPayloadSchema = z.object({ status: z.literal('shutdown_requested'), clientId: z.string(), requestId: z.string(), }) export const KnownStatusPayloadSchema = z.discriminatedUnion('status', [ AgentCreatedStatusPayloadSchema, AgentCreateFailedStatusPayloadSchema, AgentResumedStatusPayloadSchema, AgentRefreshedStatusPayloadSchema, ShutdownRequestedStatusPayloadSchema, RestartRequestedStatusPayloadSchema, ]) export type KnownStatusPayload = z.infer export const ArtifactMessageSchema = z.object({ type: z.literal('artifact'), payload: z.object({ type: z.enum(['markdown', 'diff', 'image', 'code']), id: z.string(), title: z.string(), content: z.string(), isBase64: z.boolean(), }), }) export const ProjectCheckoutLiteNotGitPayloadSchema = z.object({ cwd: z.string(), isGit: z.literal(false), currentBranch: z.null(), remoteUrl: z.null(), isPaseoOwnedWorktree: z.literal(false), mainRepoRoot: z.null(), }) export const ProjectCheckoutLiteGitNonPaseoPayloadSchema = z.object({ cwd: z.string(), isGit: z.literal(true), currentBranch: z.string().nullable(), remoteUrl: z.string().nullable(), isPaseoOwnedWorktree: z.literal(false), mainRepoRoot: z.null(), }) export const ProjectCheckoutLiteGitPaseoPayloadSchema = z.object({ cwd: z.string(), isGit: z.literal(true), currentBranch: z.string().nullable(), remoteUrl: z.string().nullable(), isPaseoOwnedWorktree: z.literal(true), mainRepoRoot: z.string(), }) export const ProjectCheckoutLitePayloadSchema = z.union([ ProjectCheckoutLiteNotGitPayloadSchema, ProjectCheckoutLiteGitNonPaseoPayloadSchema, ProjectCheckoutLiteGitPaseoPayloadSchema, ]) export const ProjectPlacementPayloadSchema = z.object({ projectKey: z.string(), projectName: z.string(), checkout: ProjectCheckoutLitePayloadSchema, }) export const WorkspaceDescriptorPayloadSchema = z.object({ id: z.string(), projectId: z.string(), projectDisplayName: z.string(), projectRootPath: z.string(), projectKind: z.enum(['git', 'non_git']), workspaceKind: z.enum(['local_checkout', 'worktree', 'directory']), name: z.string(), status: WorkspaceStateBucketSchema, activityAt: z.string().nullable(), }) export const AgentUpdateMessageSchema = z.object({ type: z.literal('agent_update'), payload: z.discriminatedUnion('kind', [ z.object({ kind: z.literal('upsert'), agent: AgentSnapshotPayloadSchema, project: ProjectPlacementPayloadSchema, }), z.object({ kind: z.literal('remove'), agentId: z.string(), }), ]), }) export const AgentStreamMessageSchema = z.object({ type: z.literal('agent_stream'), payload: z.object({ agentId: z.string(), event: AgentStreamEventPayloadSchema, timestamp: z.string(), // Present for timeline events. Maps 1:1 to canonical in-memory timeline rows. seq: z.number().int().nonnegative().optional(), epoch: z.string().optional(), }), }) export const AgentStatusMessageSchema = z.object({ type: z.literal('agent_status'), payload: z.object({ agentId: z.string(), status: z.string(), info: AgentSnapshotPayloadSchema, }), }) export const AgentListMessageSchema = z.object({ type: z.literal('agent_list'), payload: z.object({ agents: z.array(AgentSnapshotPayloadSchema), }), }) export const FetchAgentsResponseMessageSchema = z.object({ type: z.literal('fetch_agents_response'), payload: z.object({ requestId: z.string(), subscriptionId: z.string().nullable().optional(), entries: z.array( z.object({ agent: AgentSnapshotPayloadSchema, project: ProjectPlacementPayloadSchema, }) ), pageInfo: z.object({ nextCursor: z.string().nullable(), prevCursor: z.string().nullable(), hasMore: z.boolean(), }), }), }) export const FetchWorkspacesResponseMessageSchema = z.object({ type: z.literal('fetch_workspaces_response'), payload: z.object({ requestId: z.string(), subscriptionId: z.string().nullable().optional(), entries: z.array(WorkspaceDescriptorPayloadSchema), pageInfo: z.object({ nextCursor: z.string().nullable(), prevCursor: z.string().nullable(), hasMore: z.boolean(), }), }), }) export const WorkspaceUpdateMessageSchema = z.object({ type: z.literal('workspace_update'), payload: z.discriminatedUnion('kind', [ z.object({ kind: z.literal('upsert'), workspace: WorkspaceDescriptorPayloadSchema, }), z.object({ kind: z.literal('remove'), id: z.string(), }), ]), }) export const OpenProjectResponseMessageSchema = z.object({ type: z.literal('open_project_response'), payload: z.object({ requestId: z.string(), workspace: WorkspaceDescriptorPayloadSchema.nullable(), error: z.string().nullable(), }), }) export const ArchiveWorkspaceResponseMessageSchema = z.object({ type: z.literal('archive_workspace_response'), payload: z.object({ requestId: z.string(), workspaceId: z.string(), archivedAt: z.string().nullable(), error: z.string().nullable(), }), }) export const FetchAgentResponseMessageSchema = z.object({ type: z.literal('fetch_agent_response'), payload: z.object({ requestId: z.string(), agent: AgentSnapshotPayloadSchema.nullable(), project: ProjectPlacementPayloadSchema.nullable().optional(), error: z.string().nullable(), }), }) const AgentTimelineSeqRangeSchema = z.object({ startSeq: z.number().int().nonnegative(), endSeq: z.number().int().nonnegative(), }) export const AgentTimelineEntryPayloadSchema = z.object({ provider: AgentProviderSchema, item: AgentTimelineItemPayloadSchema, timestamp: z.string(), seqStart: z.number().int().nonnegative(), seqEnd: z.number().int().nonnegative(), sourceSeqRanges: z.array(AgentTimelineSeqRangeSchema), collapsed: z.array(z.enum(['assistant_merge', 'tool_lifecycle'])), }) export const FetchAgentTimelineResponseMessageSchema = z.object({ type: z.literal('fetch_agent_timeline_response'), payload: z.object({ requestId: z.string(), agentId: z.string(), agent: AgentSnapshotPayloadSchema.nullable(), direction: z.enum(['tail', 'before', 'after']), projection: z.enum(['projected', 'canonical']), epoch: z.string(), reset: z.boolean(), staleCursor: z.boolean(), gap: z.boolean(), window: z.object({ minSeq: z.number().int().nonnegative(), maxSeq: z.number().int().nonnegative(), nextSeq: z.number().int().nonnegative(), }), startCursor: AgentTimelineCursorSchema.nullable(), endCursor: AgentTimelineCursorSchema.nullable(), hasOlder: z.boolean(), hasNewer: z.boolean(), entries: z.array(AgentTimelineEntryPayloadSchema), error: z.string().nullable(), }), }) export const SendAgentMessageResponseMessageSchema = z.object({ type: z.literal('send_agent_message_response'), payload: z.object({ requestId: z.string(), agentId: z.string(), accepted: z.boolean(), error: z.string().nullable(), }), }) export const WaitForFinishResponseMessageSchema = z.object({ type: z.literal('wait_for_finish_response'), payload: z.object({ requestId: z.string(), status: z.enum(['idle', 'error', 'permission', 'timeout']), final: AgentSnapshotPayloadSchema.nullable(), error: z.string().nullable(), lastMessage: z.string().nullable(), }), }) export const AgentPermissionRequestMessageSchema = z.object({ type: z.literal('agent_permission_request'), payload: z.object({ agentId: z.string(), request: AgentPermissionRequestPayloadSchema, }), }) export const AgentPermissionResolvedMessageSchema = z.object({ type: z.literal('agent_permission_resolved'), payload: z.object({ agentId: z.string(), requestId: z.string(), resolution: AgentPermissionResponseSchema, }), }) export const AgentDeletedMessageSchema = z.object({ type: z.literal('agent_deleted'), payload: z.object({ agentId: z.string(), requestId: z.string(), }), }) export const AgentArchivedMessageSchema = z.object({ type: z.literal('agent_archived'), payload: z.object({ agentId: z.string(), archivedAt: z.string(), requestId: z.string(), }), }) const AheadBehindSchema = z.object({ ahead: z.number(), behind: z.number(), }) const CheckoutStatusCommonSchema = z.object({ cwd: z.string(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }) const CheckoutStatusNotGitSchema = CheckoutStatusCommonSchema.extend({ isGit: z.literal(false), isPaseoOwnedWorktree: z.literal(false), repoRoot: z.null(), currentBranch: z.null(), isDirty: z.null(), baseRef: z.null(), aheadBehind: z.null(), aheadOfOrigin: z.null(), behindOfOrigin: z.null(), hasRemote: z.boolean(), remoteUrl: z.null(), }) const CheckoutStatusGitNonPaseoSchema = CheckoutStatusCommonSchema.extend({ isGit: z.literal(true), isPaseoOwnedWorktree: z.literal(false), repoRoot: z.string(), currentBranch: z.string().nullable(), isDirty: z.boolean(), baseRef: z.string().nullable(), aheadBehind: AheadBehindSchema.nullable(), aheadOfOrigin: z.number().nullable(), behindOfOrigin: z.number().nullable(), hasRemote: z.boolean(), remoteUrl: z.string().nullable(), }) const CheckoutStatusGitPaseoSchema = CheckoutStatusCommonSchema.extend({ isGit: z.literal(true), isPaseoOwnedWorktree: z.literal(true), repoRoot: z.string(), mainRepoRoot: z.string(), currentBranch: z.string().nullable(), isDirty: z.boolean(), baseRef: z.string(), aheadBehind: AheadBehindSchema.nullable(), aheadOfOrigin: z.number().nullable(), behindOfOrigin: z.number().nullable(), hasRemote: z.boolean(), remoteUrl: z.string().nullable(), }) export const CheckoutStatusResponseSchema = z.object({ type: z.literal('checkout_status_response'), payload: z.union([ CheckoutStatusNotGitSchema, CheckoutStatusGitNonPaseoSchema, CheckoutStatusGitPaseoSchema, ]), }) const CheckoutDiffSubscriptionPayloadSchema = z.object({ subscriptionId: z.string(), cwd: z.string(), files: z.array(ParsedDiffFileSchema), error: CheckoutErrorSchema.nullable(), }) export const SubscribeCheckoutDiffResponseSchema = z.object({ type: z.literal('subscribe_checkout_diff_response'), payload: CheckoutDiffSubscriptionPayloadSchema.extend({ requestId: z.string(), }), }) export const CheckoutDiffUpdateSchema = z.object({ type: z.literal('checkout_diff_update'), payload: CheckoutDiffSubscriptionPayloadSchema, }) export const CheckoutCommitResponseSchema = z.object({ type: z.literal('checkout_commit_response'), payload: z.object({ cwd: z.string(), success: z.boolean(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) export const CheckoutMergeResponseSchema = z.object({ type: z.literal('checkout_merge_response'), payload: z.object({ cwd: z.string(), success: z.boolean(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) export const CheckoutMergeFromBaseResponseSchema = z.object({ type: z.literal('checkout_merge_from_base_response'), payload: z.object({ cwd: z.string(), success: z.boolean(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) export const CheckoutPushResponseSchema = z.object({ type: z.literal('checkout_push_response'), payload: z.object({ cwd: z.string(), success: z.boolean(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) export const CheckoutPrCreateResponseSchema = z.object({ type: z.literal('checkout_pr_create_response'), payload: z.object({ cwd: z.string(), url: z.string().nullable(), number: z.number().nullable(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) const CheckoutPrStatusSchema = z.object({ url: z.string(), title: z.string(), state: z.string(), baseRefName: z.string(), headRefName: z.string(), isMerged: z.boolean(), }) export const CheckoutPrStatusResponseSchema = z.object({ type: z.literal('checkout_pr_status_response'), payload: z.object({ cwd: z.string(), status: CheckoutPrStatusSchema.nullable(), githubFeaturesEnabled: z.boolean(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) export const ValidateBranchResponseSchema = z.object({ type: z.literal('validate_branch_response'), payload: z.object({ exists: z.boolean(), resolvedRef: z.string().nullable(), isRemote: z.boolean(), error: z.string().nullable(), requestId: z.string(), }), }) export const BranchSuggestionsResponseSchema = z.object({ type: z.literal('branch_suggestions_response'), payload: z.object({ branches: z.array(z.string()), error: z.string().nullable(), requestId: z.string(), }), }) export const DirectorySuggestionsResponseSchema = z.object({ type: z.literal('directory_suggestions_response'), payload: z.object({ directories: z.array(z.string()), entries: z .array( z.object({ path: z.string(), kind: z.enum(['file', 'directory']), }) ) .optional() .default([]), error: z.string().nullable(), requestId: z.string(), }), }) const PaseoWorktreeSchema = z.object({ worktreePath: z.string(), createdAt: z.string(), branchName: z.string().nullable().optional(), head: z.string().nullable().optional(), }) export const PaseoWorktreeListResponseSchema = z.object({ type: z.literal('paseo_worktree_list_response'), payload: z.object({ worktrees: z.array(PaseoWorktreeSchema), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) export const PaseoWorktreeArchiveResponseSchema = z.object({ type: z.literal('paseo_worktree_archive_response'), payload: z.object({ success: z.boolean(), removedAgents: z.array(z.string()).optional(), error: CheckoutErrorSchema.nullable(), requestId: z.string(), }), }) export const FileExplorerResponseSchema = z.object({ type: z.literal('file_explorer_response'), payload: z.object({ cwd: z.string(), path: z.string(), mode: z.enum(['list', 'file']), directory: FileExplorerDirectorySchema.nullable(), file: FileExplorerFileSchema.nullable(), error: z.string().nullable(), requestId: z.string(), }), }) const ProjectIconSchema = z.object({ data: z.string(), mimeType: z.string(), }) export const ProjectIconResponseSchema = z.object({ type: z.literal('project_icon_response'), payload: z.object({ cwd: z.string(), icon: ProjectIconSchema.nullable(), error: z.string().nullable(), requestId: z.string(), }), }) export const FileDownloadTokenResponseSchema = z.object({ type: z.literal('file_download_token_response'), payload: z.object({ cwd: z.string(), path: z.string(), token: z.string().nullable(), fileName: z.string().nullable(), mimeType: z.string().nullable(), size: z.number().nullable(), error: z.string().nullable(), requestId: z.string(), }), }) export const ListProviderModelsResponseMessageSchema = z.object({ type: z.literal('list_provider_models_response'), payload: z.object({ provider: AgentProviderSchema, models: z.array(AgentModelDefinitionSchema).optional(), error: z.string().nullable().optional(), fetchedAt: z.string(), requestId: z.string(), }), }) const ProviderAvailabilitySchema = z.object({ provider: AgentProviderSchema, available: z.boolean(), error: z.string().nullable().optional(), }) export const ListAvailableProvidersResponseSchema = z.object({ type: z.literal('list_available_providers_response'), payload: z.object({ providers: z.array(ProviderAvailabilitySchema), error: z.string().nullable().optional(), fetchedAt: z.string(), requestId: z.string(), }), }) export const SpeechModelsListResponseSchema = z.object({ type: z.literal('speech_models_list_response'), payload: z.object({ modelsDir: z.string(), models: z.array( z.object({ id: z.string(), kind: z.string(), description: z.string(), modelDir: z.string(), isDownloaded: z.boolean(), missingFiles: z.array(z.string()).optional(), }) ), requestId: z.string(), }), }) export const SpeechModelsDownloadResponseSchema = z.object({ type: z.literal('speech_models_download_response'), payload: z.object({ modelsDir: z.string(), downloadedModelIds: z.array(z.string()), error: z.string().nullable(), requestId: z.string(), }), }) const AgentSlashCommandSchema = z.object({ name: z.string(), description: z.string(), argumentHint: z.string(), }) export const ListCommandsResponseSchema = z.object({ type: z.literal('list_commands_response'), payload: z.object({ agentId: z.string(), commands: z.array(AgentSlashCommandSchema), error: z.string().nullable(), requestId: z.string(), }), }) // ============================================================================ // Terminal Outbound Messages // ============================================================================ const TerminalInfoSchema = z.object({ id: z.string(), name: z.string(), cwd: z.string(), }) const TerminalCellSchema = z.object({ char: z.string(), fg: z.number().optional(), bg: z.number().optional(), fgMode: z.number().optional(), bgMode: z.number().optional(), bold: z.boolean().optional(), italic: z.boolean().optional(), underline: z.boolean().optional(), }) const TerminalStateSchema = z.object({ rows: z.number(), cols: z.number(), grid: z.array(z.array(TerminalCellSchema)), scrollback: z.array(z.array(TerminalCellSchema)), cursor: z.object({ row: z.number(), col: z.number() }), }) export const ListTerminalsResponseSchema = z.object({ type: z.literal('list_terminals_response'), payload: z.object({ cwd: z.string(), terminals: z.array(TerminalInfoSchema.omit({ cwd: true })), requestId: z.string(), }), }) export const TerminalsChangedSchema = z.object({ type: z.literal('terminals_changed'), payload: z.object({ cwd: z.string(), terminals: z.array(TerminalInfoSchema.omit({ cwd: true })), }), }) export const CreateTerminalResponseSchema = z.object({ type: z.literal('create_terminal_response'), payload: z.object({ terminal: TerminalInfoSchema.nullable(), error: z.string().nullable(), requestId: z.string(), }), }) export const SubscribeTerminalResponseSchema = z.object({ type: z.literal('subscribe_terminal_response'), payload: z.object({ terminalId: z.string(), state: TerminalStateSchema.nullable(), error: z.string().nullable(), requestId: z.string(), }), }) export const TerminalOutputSchema = z.object({ type: z.literal('terminal_output'), payload: z.object({ terminalId: z.string(), state: TerminalStateSchema, }), }) export const KillTerminalResponseSchema = z.object({ type: z.literal('kill_terminal_response'), payload: z.object({ terminalId: z.string(), success: z.boolean(), requestId: z.string(), }), }) export const AttachTerminalStreamResponseSchema = z.object({ type: z.literal('attach_terminal_stream_response'), payload: z.object({ terminalId: z.string(), streamId: z.number().int().nonnegative().nullable(), replayedFrom: z.number().int().nonnegative(), currentOffset: z.number().int().nonnegative(), earliestAvailableOffset: z.number().int().nonnegative(), reset: z.boolean(), error: z.string().nullable(), requestId: z.string(), }), }) export const DetachTerminalStreamResponseSchema = z.object({ type: z.literal('detach_terminal_stream_response'), payload: z.object({ streamId: z.number().int().nonnegative(), success: z.boolean(), requestId: z.string(), }), }) export const TerminalStreamExitSchema = z.object({ type: z.literal('terminal_stream_exit'), payload: z.object({ streamId: z.number().int().nonnegative(), terminalId: z.string(), }), }) export const SessionOutboundMessageSchema = z.discriminatedUnion('type', [ ActivityLogMessageSchema, AssistantChunkMessageSchema, AudioOutputMessageSchema, TranscriptionResultMessageSchema, DictationStreamAckMessageSchema, DictationStreamFinishAcceptedMessageSchema, DictationStreamPartialMessageSchema, DictationStreamFinalMessageSchema, DictationStreamErrorMessageSchema, StatusMessageSchema, PongMessageSchema, RpcErrorMessageSchema, ArtifactMessageSchema, AgentUpdateMessageSchema, WorkspaceUpdateMessageSchema, AgentStreamMessageSchema, AgentStatusMessageSchema, FetchAgentsResponseMessageSchema, FetchWorkspacesResponseMessageSchema, OpenProjectResponseMessageSchema, ArchiveWorkspaceResponseMessageSchema, FetchAgentResponseMessageSchema, FetchAgentTimelineResponseMessageSchema, SendAgentMessageResponseMessageSchema, SetVoiceModeResponseMessageSchema, SetAgentModeResponseMessageSchema, SetAgentModelResponseMessageSchema, SetAgentThinkingResponseMessageSchema, UpdateAgentResponseMessageSchema, WaitForFinishResponseMessageSchema, AgentPermissionRequestMessageSchema, AgentPermissionResolvedMessageSchema, AgentDeletedMessageSchema, AgentArchivedMessageSchema, CheckoutStatusResponseSchema, SubscribeCheckoutDiffResponseSchema, CheckoutDiffUpdateSchema, CheckoutCommitResponseSchema, CheckoutMergeResponseSchema, CheckoutMergeFromBaseResponseSchema, CheckoutPushResponseSchema, CheckoutPrCreateResponseSchema, CheckoutPrStatusResponseSchema, ValidateBranchResponseSchema, BranchSuggestionsResponseSchema, DirectorySuggestionsResponseSchema, PaseoWorktreeListResponseSchema, PaseoWorktreeArchiveResponseSchema, FileExplorerResponseSchema, ProjectIconResponseSchema, FileDownloadTokenResponseSchema, ListProviderModelsResponseMessageSchema, ListAvailableProvidersResponseSchema, SpeechModelsListResponseSchema, SpeechModelsDownloadResponseSchema, ListCommandsResponseSchema, ListTerminalsResponseSchema, TerminalsChangedSchema, CreateTerminalResponseSchema, SubscribeTerminalResponseSchema, TerminalOutputSchema, KillTerminalResponseSchema, AttachTerminalStreamResponseSchema, DetachTerminalStreamResponseSchema, TerminalStreamExitSchema, ]) export type SessionOutboundMessage = z.infer // Type exports for individual message types export type ActivityLogMessage = z.infer export type AssistantChunkMessage = z.infer export type AudioOutputMessage = z.infer export type TranscriptionResultMessage = z.infer export type StatusMessage = z.infer export type ServerCapabilityState = z.infer export type ServerVoiceCapabilities = z.infer export type ServerCapabilities = z.infer export type ServerInfoStatusPayload = z.infer export type RpcErrorMessage = z.infer export type ArtifactMessage = z.infer export type AgentUpdateMessage = z.infer export type AgentStreamMessage = z.infer export type AgentStatusMessage = z.infer export type ProjectCheckoutLitePayload = z.infer export type ProjectPlacementPayload = z.infer export type WorkspaceStateBucket = z.infer export type WorkspaceDescriptorPayload = z.infer export type FetchAgentsResponseMessage = z.infer export type FetchWorkspacesResponseMessage = z.infer export type OpenProjectResponseMessage = z.infer export type ArchiveWorkspaceResponseMessage = z.infer export type FetchAgentResponseMessage = z.infer export type FetchAgentTimelineResponseMessage = z.infer< typeof FetchAgentTimelineResponseMessageSchema > export type SendAgentMessageResponseMessage = z.infer export type SetVoiceModeResponseMessage = z.infer export type UpdateAgentResponseMessage = z.infer export type WaitForFinishResponseMessage = z.infer export type AgentPermissionRequestMessage = z.infer export type AgentPermissionResolvedMessage = z.infer export type AgentDeletedMessage = z.infer export type ListProviderModelsResponseMessage = z.infer< typeof ListProviderModelsResponseMessageSchema > export type ListAvailableProvidersResponse = z.infer export type SpeechModelsListResponse = z.infer export type SpeechModelsDownloadResponse = z.infer // Type exports for payload types export type ActivityLogPayload = z.infer // Type exports for inbound message types export type VoiceAudioChunkMessage = z.infer export type FetchAgentsRequestMessage = z.infer export type FetchWorkspacesRequestMessage = z.infer export type FetchAgentRequestMessage = z.infer export type SendAgentMessageRequest = z.infer export type WaitForFinishRequest = z.infer export type DictationStreamStartMessage = z.infer export type DictationStreamChunkMessage = z.infer export type DictationStreamFinishMessage = z.infer export type DictationStreamCancelMessage = z.infer export type CreateAgentRequestMessage = z.infer export type ListProviderModelsRequestMessage = z.infer< typeof ListProviderModelsRequestMessageSchema > export type ListAvailableProvidersRequestMessage = z.infer< typeof ListAvailableProvidersRequestMessageSchema > export type SpeechModelsListRequestMessage = z.infer export type SpeechModelsDownloadRequestMessage = z.infer export type ResumeAgentRequestMessage = z.infer export type DeleteAgentRequestMessage = z.infer export type UpdateAgentRequestMessage = z.infer export type SetAgentModeRequestMessage = z.infer export type SetAgentModelRequestMessage = z.infer export type SetAgentThinkingRequestMessage = z.infer export type AgentPermissionResponseMessage = z.infer export type CheckoutStatusRequest = z.infer export type CheckoutStatusResponse = z.infer export type SubscribeCheckoutDiffRequest = z.infer export type UnsubscribeCheckoutDiffRequest = z.infer export type SubscribeCheckoutDiffResponse = z.infer export type CheckoutDiffUpdate = z.infer export type CheckoutCommitRequest = z.infer export type CheckoutCommitResponse = z.infer export type CheckoutMergeRequest = z.infer export type CheckoutMergeResponse = z.infer export type CheckoutMergeFromBaseRequest = z.infer export type CheckoutMergeFromBaseResponse = z.infer export type CheckoutPushRequest = z.infer export type CheckoutPushResponse = z.infer export type CheckoutPrCreateRequest = z.infer export type CheckoutPrCreateResponse = z.infer export type CheckoutPrStatusRequest = z.infer export type CheckoutPrStatusResponse = z.infer export type ValidateBranchRequest = z.infer export type ValidateBranchResponse = z.infer export type BranchSuggestionsRequest = z.infer export type BranchSuggestionsResponse = z.infer export type DirectorySuggestionsRequest = z.infer export type DirectorySuggestionsResponse = z.infer export type PaseoWorktreeListRequest = z.infer export type PaseoWorktreeListResponse = z.infer export type PaseoWorktreeArchiveRequest = z.infer export type PaseoWorktreeArchiveResponse = z.infer export type OpenProjectRequest = z.infer export type ArchiveWorkspaceRequest = z.infer export type FileExplorerRequest = z.infer export type FileExplorerResponse = z.infer export type ProjectIconRequest = z.infer export type ProjectIconResponse = z.infer export type ProjectIcon = z.infer export type FileDownloadTokenRequest = z.infer export type FileDownloadTokenResponse = z.infer export type RestartServerRequestMessage = z.infer export type ShutdownServerRequestMessage = z.infer export type ClearAgentAttentionMessage = z.infer export type ClientHeartbeatMessage = z.infer export type ListCommandsRequest = z.infer export type ListCommandsResponse = z.infer export type RegisterPushTokenMessage = z.infer // Terminal message types export type ListTerminalsRequest = z.infer export type ListTerminalsResponse = z.infer export type SubscribeTerminalsRequest = z.infer export type UnsubscribeTerminalsRequest = z.infer export type TerminalsChanged = z.infer export type CreateTerminalRequest = z.infer export type CreateTerminalResponse = z.infer export type SubscribeTerminalRequest = z.infer export type SubscribeTerminalResponse = z.infer export type UnsubscribeTerminalRequest = z.infer export type TerminalInput = z.infer export type TerminalOutput = z.infer export type KillTerminalRequest = z.infer export type KillTerminalResponse = z.infer export type AttachTerminalStreamRequest = z.infer export type AttachTerminalStreamResponse = z.infer export type DetachTerminalStreamRequest = z.infer export type DetachTerminalStreamResponse = z.infer export type TerminalStreamExit = z.infer // ============================================================================ // WebSocket Level Messages (wraps session messages) // ============================================================================ // WebSocket-only messages (not session messages) export const WSPingMessageSchema = z.object({ type: z.literal('ping'), }) export const WSPongMessageSchema = z.object({ type: z.literal('pong'), }) export const WSHelloMessageSchema = z.object({ type: z.literal('hello'), clientId: z.string().min(1), clientType: z.enum(['mobile', 'browser', 'cli', 'mcp']), protocolVersion: z.number().int(), capabilities: z .object({ voice: z.boolean().optional(), pushNotifications: z.boolean().optional(), }) .passthrough() .optional(), }) export const WSWelcomeMessageSchema = z.object({ type: z.literal('welcome'), serverId: z.string(), hostname: z.string().nullable(), version: z.string().nullable(), resumed: z.boolean(), capabilities: ServerCapabilitiesSchema.optional(), }) export const WSRecordingStateMessageSchema = z.object({ type: z.literal('recording_state'), isRecording: z.boolean(), }) // Wrapped session message export const WSSessionInboundSchema = z.object({ type: z.literal('session'), message: SessionInboundMessageSchema, }) export const WSSessionOutboundSchema = z.object({ type: z.literal('session'), message: SessionOutboundMessageSchema, }) // Complete WebSocket message schemas export const WSInboundMessageSchema = z.discriminatedUnion('type', [ WSPingMessageSchema, WSHelloMessageSchema, WSRecordingStateMessageSchema, WSSessionInboundSchema, ]) export const WSOutboundMessageSchema = z.discriminatedUnion('type', [ WSPongMessageSchema, WSWelcomeMessageSchema, WSSessionOutboundSchema, ]) export type WSInboundMessage = z.infer export type WSOutboundMessage = z.infer export type WSHelloMessage = z.infer export type WSWelcomeMessage = z.infer // ============================================================================ // Helper functions for message conversion // ============================================================================ /** * Extract session message from WebSocket message * Returns null if message should be handled at WS level only */ export function extractSessionMessage(wsMsg: WSInboundMessage): SessionInboundMessage | null { if (wsMsg.type === 'session') { return wsMsg.message } // Ping and recording_state are WS-level only return null } /** * Wrap session message in WebSocket envelope */ export function wrapSessionMessage(sessionMsg: SessionOutboundMessage): WSOutboundMessage { return { type: 'session', message: sessionMsg, } } export function parseServerInfoStatusPayload(payload: unknown): ServerInfoStatusPayload | null { const parsed = ServerInfoStatusPayloadSchema.safeParse(payload) if (!parsed.success) { return null } return parsed.data }