// contracts §17 — Projection/UI Contracts // File: ui.ts — all *Projection types, ProjectionSnapshot, ProjectionStore, // ProjectionClient, UiCommandChannel; projection.ts symbols merged per DD §3. import type { SessionID, TaskID, AgentID, ToolRunID, CommandRunID, ArtifactID, ISOTimeString, } from './ids.js' import type { RuntimeEvent } from './event.js' import type { TaskStatus } from './task.js' import type { AgentType } from './runtime.js' // Re-export for external consumers of this module export type { SessionID, TaskID, AgentID, ToolRunID, CommandRunID, ArtifactID, ISOTimeString, } export type { TaskStatus } from './task.js' export type { AgentType } from './runtime.js' export type { RuntimeEvent } from './event.js' // ============================================================================= // §17 — Projection Types // ============================================================================= /** * Derived status for CommandRunProjection. * Matches DD §4.4 derivation: * completed_at == null -> "running" * cancellation metadata present -> "cancelled" * exit_code === 0 -> "ok" * exit_code != 0 (non-null) -> "error" * otherwise -> "unknown" */ export type CommandRunStatus = 'running' | 'ok' | 'error' | 'cancelled' | 'unknown' /** * Pure derivation function for command run status. * Implements DD §4.4 logic so that CommandRunProjection.status * never invents a value outside the defined union. */ export function derive_command_status(fields: { completed_at: ISOTimeString | null | undefined exit_code: number | null | undefined cancelled: boolean }): CommandRunStatus { if (fields.completed_at == null) return 'running' if (fields.cancelled) return 'cancelled' if (fields.exit_code === 0) return 'ok' if (fields.exit_code != null) return 'error' return 'unknown' } export interface SessionProjection { session_id: SessionID title?: string status: string } export interface TaskProjection { task_id: TaskID title: string status: TaskStatus progress_text?: string } export interface AgentProjection { agent_id: AgentID agent_type: AgentType status: string task_id?: TaskID progress_text?: string } export interface ToolRunProjection { tool_run_id: ToolRunID tool_name: string status: string task_id?: TaskID } export interface CommandRunProjection { command_run_id: CommandRunID command: string status: CommandRunStatus exit_code?: number } export interface ArtifactProjection { artifact_id: ArtifactID type: string uri: string } export interface PermissionPromptProjection { prompt_id: string subject: string risk_level: string reason: string options: string[] } export interface BlockerProjection { task_id?: TaskID reason: string required_decision: string } // ============================================================================= // §17 — ProjectionSnapshot // ============================================================================= export interface ProjectionSnapshot { session?: SessionProjection tasks: TaskProjection[] agents: AgentProjection[] tool_runs: ToolRunProjection[] command_runs: CommandRunProjection[] artifacts: ArtifactProjection[] permission_prompts: PermissionPromptProjection[] blockers: BlockerProjection[] updated_at: ISOTimeString } // ============================================================================= // §17 — Subscription (re-declared here for projection consumers) // ============================================================================= /** * Subscription handle returned by ProjectionStore.subscribe and * ProjectionClient.subscribe. Mirrors the EventBus Subscription * contract (§7) for standalone projection consumers. */ export interface Subscription { unsubscribe(): void } // ============================================================================= // §17 — ProjectionStore // ============================================================================= /** * ProjectionStore.apply handles all durable events and key ephemeral events * (agent.heartbeat, task.progress, assistant.message.delta, tool.progress, * command.stdout.delta, command.stderr.delta). Unknown event types are ignored. * * command_runs projection status uses the derivation in DD §4.4 * (derive_command_status). ProjectionStore is never a scheduling/recovery * source of truth (overview §9.3). */ export interface ProjectionStore { hydrate(session_id: SessionID): Promise apply(event: RuntimeEvent): void snapshot(): ProjectionSnapshot subscribe(handler: (snapshot: ProjectionSnapshot) => void): Subscription } // ============================================================================= // §17 — ProjectionClient // ============================================================================= /** * Read-only client for the TUI. V1 transport: TUI runs in-process with runtime; * ProjectionClient is a direct interface reference, not IPC. * TUI may consume only ProjectionClient or projection contracts, never runtime * internals, SQLite, or EventBus directly. */ export interface ProjectionClient { snapshot(): ProjectionSnapshot subscribe(handler: (snapshot: ProjectionSnapshot) => void): Subscription } // ============================================================================= // §17 — UiCommandChannel // ============================================================================= /** * Narrow interface through which the TUI emits user decisions back to the * runtime. Covers two V1 command types: * * - Permission prompt responses (user selects an option for a * permission.prompt.requested event; runtime emits * permission.prompt.resolved). * - Blocker decisions (user resolves a BlockerProjection; runtime * processes the decision through the scheduler/task system). * * Boundary rule (code-view §7): packages/tui may only import from * packages/contracts, never from packages/runtime/src/*. */ export interface UiCommandChannel { /** * Resolve a permission prompt by selecting an option. * The runtime will emit a permission.prompt.resolved durable event * and resume the suspended tool call. */ resolve_permission_prompt(prompt_id: string, selected_option: string): Promise /** * Resolve a blocker by providing the user's decision. * The runtime processes the decision through the task system * to unblock the affected task. */ resolve_blocker(blocker: BlockerProjection, decision: string): Promise }