/** * WebSocket wire protocol types shared between the spectral local server and * the (future) browser client in `landing/`. * * Keep this file dependency-free. It will be copied or imported by the * frontend in Day 3, so we don't want it to drag in `better-sqlite3`, * `ws`, or any spectral SDK types. * * Naming: client → server messages are `ClientMessage`, server → client * frames are `ServerEvent`. JSON-encoded one-per-frame on the wire. */ /** * Image attached to a user message. * * Two carriers, in order of preference: * * 1. `url` — the image was uploaded to backend storage (S3) and only the * pointer travels over the relay. **Preferred**: a screenshot is worth * megabytes of base64, a url is ~40 characters. * 2. `data` — inline base64 (no data URI prefix). Kept for backward * compatibility with small/legacy attachments; rejected above * `MAX_INLINE_IMAGE_BASE64_CHARS` (measured on the base64 string, i.e. * on the wire payload) so the relay never carries megabytes. * * At least one of `data` / `url` must be present. */ export interface ImageAttachment { /** * Raw base64 image data (no data URI prefix). Legacy/small images only — * hard-capped at `MAX_INLINE_IMAGE_BASE64_CHARS` (768 KiB of base64) at * the wire boundary. */ data?: string; /** * Hosted image url: backend-relative (`/generated-images/`) or * absolute. Preferred over `data` — when both are set the url wins. */ url?: string; /** Backend image id, when the attachment came from `uploadImage()`. */ imageId?: string; /** MIME type, e.g. "image/jpeg" or "image/png". */ mimeType: string; /** Compressed width in pixels (optional). */ width?: number; /** Compressed height in pixels (optional). */ height?: number; } /** Persisted message row as returned to the browser. */ export interface WireMessage { id: string; role: "user" | "assistant" | "system"; content: string; /** Newline-delimited JSON of agent events captured for this message (assistant only, may be ""). */ events: string; createdAt: number; /** Stable per-session insertion sequence used for server-authoritative ordering. */ sequence?: number; /** Image attachments on user messages (hosted `url` or inline base64; empty for assistant/system). */ images?: ImageAttachment[]; /** Credits consumed by this message, persisted independently of the events blob (assistant only). */ creditsUsed?: number | null; /** Output token count, persisted independently of the events blob (assistant only). */ outputTokens?: number | null; /** Generation window ms (message_end − first streamed delta, excludes TTFT), persisted independently (assistant only). */ durationMs?: number | null; /** Output tokens per second over the generation window, rounded to 1 decimal (assistant only). */ tokensPerSecond?: number | null; /** Time-to-first-delta ms (first streamed delta − message_start), persisted independently (assistant only). */ ttftMs?: number | null; } /** * Authoritative per-session agent run phase. The CLI resolves this single * value server-side (see `SessionStream.resolveRunState`) using the * precedence: retrying > compacting > looping > streaming > queued > idle. * The frontend badge renders it but never derives it locally. */ export type RunPhase = "idle" | "streaming" | "retrying" | "compacting" | "looping" | "queued"; /** * Single source of truth for "is the agent working right now". `busy` is the * only field the frontend composer should use to decide send vs queue; `phase` * and `flags` are granular, additive signals for the live status badge. */ export interface WireRunState { /** UUID of the current logical run (user prompt + retries/continuations). */ runId: string; /** Monotonic per-session generation; increments at each new logical run. */ generation: number; /** True while the agent is doing work. `queued` does NOT set this. */ busy: boolean; /** Primary phase, resolved server-side. */ phase: RunPhase; flags: { streaming: boolean; retrying: boolean; compacting: boolean; looping: boolean; queued: boolean; }; } /** * Project — a user-declared root directory that owns 0..N sessions. * * spectral runs with `cwd = project.path`, so the project is the unit of "where on * disk this conversation operates". Sessions are scoped to a project; the * sidebar is a 2-tier projects → sessions view. */ export interface WireProject { id: string; name: string; /** Absolute filesystem path. Server-side validated on create. */ path: string; createdAt: number; updatedAt: number; /** Number of sessions currently associated with this project. */ sessionCount: number; /** Studio project ID when the project is bound to Aexol Studio (from `.aexol/aexol.jsonc`). */ studioProjectId?: string; /** Studio project display name when bound. Falls back to projectId if name is not set. */ studioProjectName?: string; /** Studio team ID when the project is bound to Aexol Studio. */ studioTeamId?: string; /** True when at least one of this project's sessions has an in-flight turn. */ hasActiveSession?: boolean; /** True when at least one of this project's sessions has active agent work. */ busy?: boolean; } /** Session row (list view — no messages). */ export interface WireSessionSummary { id: string; /** Owning project. Always set — sessions cannot exist without a project. */ projectId: string; title: string; createdAt: number; updatedAt: number; messageCount: number; /** True when this session currently has an in-flight assistant turn. */ hasActiveTurn?: boolean; /** True when this session currently has active agent work. */ busy?: boolean; } /** * Lifecycle status of a machine-level dev process. Mirrors the status values * produced by `dev-process-registry.ts` (`running`/`exited`/`killed`) plus * the durable-recovery state (`recovered`) used after a CLI restart. */ export type DevProcessStatus = "running" | "exited" | "killed" | "recovered"; /** * Wire-safe snapshot of a machine-level background dev process. This type is * intentionally dependency-free and structurally identical to the registry's * `DevProcessSummary` so events can be emitted without importing * `better-sqlite3`, `ws`, or any spectral SDK types into `wire.ts`. */ export interface DevProcessSummary { id: string; /** Stable id of the dev-process definition, once definitions exist. */ definitionId?: string; /** Human-readable label shown in the settings panel. */ label: string; command: string; cwd: string; status: DevProcessStatus; pid?: number; /** TCP ports currently being listened on by this process. */ ports?: number[]; startedAt: number; endedAt?: number; exitCode: number | null; /** Bounded tail of the latest output. */ lastOutputTail: string; fullOutputPath: string; } /** * Wire-safe snapshot of a listening TCP server discovered by a port scan. * Unlike `DevProcessSummary`, this may include servers that the agent did not * start. `projectId`/`projectName` are populated only when the process cwd * resolves to a known Spectral project path. */ export interface DetectedDevServer { id: string; pid: number | null; port: number; address: string; process: string | null; cwd: string | null; projectId: string | null; projectName: string | null; } /** Session row with messages (detail view). */ export interface WireSessionDetail { id: string; /** Owning project. Mirrors WireSessionSummary; included so the client can * recover the project context from a deep link without an extra fetch. */ projectId: string; title: string; createdAt: number; updatedAt: number; messages: WireMessage[]; /** Total number of persisted messages in the session. */ totalMessageCount?: number; /** Number of messages included in this payload. */ loadedMessageCount?: number; /** True when older messages exist but were omitted from this payload. */ hasEarlierMessages?: boolean; /** True when this session was created via "Fork & Compact" and has not yet * had its first compaction. The UI uses this to show a special send button * for the first message. */ forkCompactPending?: boolean; /** True when this session currently has active agent work. */ busy?: boolean; /** Authoritative run-state snapshot for reconnect reconciliation. */ runState?: WireRunState; } export interface WireDcpLiteTelemetry { /** Additive telemetry schema version. */ version: 1; event: "decision" | "activation" | "application" | "scheduled" | "deferred" | "skipped" | "completed" | "failed"; source: "tool" | "auto"; mode?: "request_time" | "after_turn"; scope?: "closed_phase" | "older_history"; /** Stable classification; never includes user content or error bodies. */ reason?: string; satisfiedSignals?: string[]; timestamp: number; rawTokens?: number; contextTokens?: number | null; contextPercent?: number | null; tokensBefore?: number; tokensAfter?: number; tokensRemoved?: number; } export interface WireDcpLiteStatus { contextWindow?: { usedTokens: number | null; maxTokens: number | null; percent: number | null; }; thresholds: { postRunCompactionThresholdTokens: number; autoContextLimitRatio: number; olderHistoryKeepRecentTokens: number; olderHistoryMinCompactableTokens: number; cooldownTurns: number; }; lastEvent?: { kind: "decision" | "activation" | "application" | "scheduled" | "deferred" | "skipped" | "completed" | "failed"; message: string; timestamp: number; source?: WireDcpLiteTelemetry["source"]; mode?: WireDcpLiteTelemetry["mode"]; scope?: WireDcpLiteTelemetry["scope"]; reason?: string; satisfiedSignals?: string[]; rawTokens?: number; contextTokens?: number | null; contextPercent?: number | null; tokensBefore?: number; tokensAfter?: number; tokensRemoved?: number; }; } export interface WireSessionMemoryStatus { mode: "active" | "passive"; phase: "idle" | "observing" | "compacting" | "reflecting" | "pruning"; inFlight: { observer: boolean; compaction: boolean; reflection: boolean; pruner: boolean; }; reflections: { count: number; tokens: number; }; observations: { committedCount: number; committedTokens: number; pendingCount: number; pendingTokens: number; }; thresholds: { observation: number; compaction: number; reflection: number; }; progress: { sinceObservationBoundTokens: number; sinceLastCompactionTokens: number; observationPoolTokens: number; }; dcpLite?: WireDcpLiteStatus; } export interface WireObservationItem { id: string; content: string; timestamp: string; relevance: "low" | "medium" | "high" | "critical"; /** Whether this observation is pending (not yet covered by a compaction snapshot). */ pending: boolean; } export interface WireReflectionItem { id: string; content: string; /** IDs of observations that support this reflection. May be empty for legacy reflections. */ supportingObservationIds: string[]; } export interface WireSessionMemoryDetails { summary: string | null; observations: WireObservationItem[]; reflections: WireReflectionItem[]; } export interface WireCompactionTokenSourceBucket { type: "role" | "tool" | "source"; name: string; tokens: number; } export type ClientMessage = { type: "user_message"; content: string; /** Enable autonomous iterative loop for this message (Ralph Wiggum pattern). */ loop?: boolean; /** Optional image attachments (hosted `url` preferred, inline base64 legacy). */ images?: ImageAttachment[]; /** Optional loop max iterations (server caps at MAX_LOOP_ITERATIONS). */ loopMaxIterations?: number; /** Optional loop goal / acceptance criteria. */ loopGoal?: string; /** * Optional interval scheduler (minutes). When set, arms a recurring * scheduler that re-launches the prompt (or a fresh loop, when `loop` * is also set) every N minutes, guarded by an idle check. The first * launch happens immediately via the normal prompt path; the scheduler * ticks from +N minutes. */ intervalMinutes?: number; /** * Interval scheduler control. `"run_now"` fires the next occurrence * immediately (and resets the cadence); `"stop"` disarms the scheduler. * Sent as a user_message with no content. */ loopControl?: "run_now" | "stop"; }; /** * Subset of `ServerEvent` that's safe to buffer per turn and replay on * reconnect. Excludes framing/lifecycle events (`session_ready`, `agent_end`) * that are emitted at the session level rather than within a turn. * * NOTE: `message_start` IS included because the client uses it to flip into * "streaming" status during replay, matching live behavior. */ export type ReplayableTurnEvent = Extract; /** * Snapshot of an in-flight assistant turn — the buffered event log plus * metadata. Sent inside `session_ready.currentTurn` so a reconnecting client * can rebuild the timeline up to the present moment. */ export interface InProgressTurnSnapshot { turnId: string; startedAt: number; events: ReplayableTurnEvent[]; } export type ServerEvent = { type: "session_ready"; sessionId: string; history: WireMessage[]; /** Total number of persisted messages in the session. */ totalMessageCount?: number; /** Number of messages included in this payload. */ loadedMessageCount?: number; /** True when older messages exist but were omitted from this payload. */ hasEarlierMessages?: boolean; /** * If a turn is in flight when this WS attached, the buffered events * for that turn so the client can rebuild it. `null`/absent when no * turn is active. Additive: older clients ignoring this field still * see consistent history. */ currentTurn?: InProgressTurnSnapshot | null; /** True when this session was created via "Fork & Compact" and is * awaiting its first compaction. The UI uses this to show a special * send button for the first message. */ forkCompactPending?: boolean; /** True when context compaction is currently running. Allows a * reconnecting client to restore the compaction overlay immediately * rather than waiting for the next compaction_start event. */ compacting?: boolean; /** Cumulative context tokens used across the session (from spectral's getContextUsage()). Null when unknown. */ contextWindowUsed: number | null; /** Model's total context window in tokens. Null when model metadata is not yet available. */ contextWindowMax: number | null; /** Interval scheduler snapshot for reconnect/UI resync. */ intervalActive?: boolean; intervalMinutes?: number | null; intervalNextTickAt?: number | null; intervalLaunchCount?: number; intervalHasLoop?: boolean; intervalGoal?: string | null; /** Authoritative run-state snapshot for reconnect/hydration. */ runState?: WireRunState; } | { type: "session_ready_start"; sessionId: string; /** If a turn is in flight when this WS attached, the buffered events for that turn so the client can rebuild it. `null`/absent when no turn is active. */ currentTurn?: InProgressTurnSnapshot | null; /** Total number of `history_chunk` events that will follow before `session_ready_end`. */ totalChunks: number; /** Total persisted messages in the session (integrity check vs. sum of chunk lengths at `session_ready_end`). */ totalMessages: number; /** Total number of persisted messages in the session. */ totalMessageCount?: number; /** Number of messages included across all chunks in this run. */ loadedMessageCount?: number; /** True when older messages exist but were omitted from this payload. */ hasEarlierMessages?: boolean; /** True when this session was created via "Fork & Compact" and is awaiting its first compaction. */ forkCompactPending?: boolean; /** True when context compaction is currently running. */ compacting?: boolean; /** Cumulative context tokens used across the session. Null when unknown. */ contextWindowUsed: number | null; /** Model's total context window in tokens. Null when model metadata is not yet available. */ contextWindowMax: number | null; /** Interval scheduler snapshot for reconnect/UI resync. */ intervalActive?: boolean; intervalMinutes?: number | null; intervalNextTickAt?: number | null; intervalLaunchCount?: number; intervalHasLoop?: boolean; intervalGoal?: string | null; /** Authoritative run-state snapshot for reconnect/hydration. */ runState?: WireRunState; } | { type: "history_chunk"; sessionId: string; index: number; total: number; messages?: WireMessage[]; encoding?: "gzip-base64"; payload?: string; } | { type: "session_ready_end"; sessionId: string; totalMessages: number; } | { /** * Broadcast right after a user message is persisted to SQLite. ALL * subscribers receive this — including the tab that originated the * `user_message`. The client de-duplicates by `message.id` (which is * generated server-side at persistence time and is the source of * truth). Allows multi-tab live sync to show the user's prompt in * every tab without local optimistic appends. */ type: "user_message_appended"; message: WireMessage; isFirstUserMessage: boolean; } | { type: "message_start"; messageId: string; role: "assistant"; } | { type: "text_delta"; messageId: string; delta: string; } | { type: "thinking_start"; messageId: string; metadata?: Record; } | { type: "thinking_delta"; messageId: string; delta: string; metadata?: Record; } | { type: "thinking_end"; messageId: string; metadata?: Record; } | { type: "tool_call"; messageId: string; id: string; name: string; args: unknown; /** Server timestamp (ms epoch) when tool execution started. Optional for older landing builds. */ startedAt?: number; } | { type: "tool_result"; messageId: string; id: string; result: unknown; isError?: boolean; /** Server timestamp (ms epoch) when tool execution started (orphan fallback aid). */ startedAt?: number; /** Server timestamp (ms epoch) when tool execution finished. Optional for older landing builds. */ finishedAt?: number; } | { type: "message_end"; messageId: string; /** Provider stop reason for the finalized assistant message. Optional for backward compatibility. */ stopReason?: "stop" | "length" | "toolUse" | "error" | "aborted"; /** True when the provider stopped because the response hit its output-length limit. */ truncated?: true; /** Provider/transport error detail when stopReason is `error`. */ errorMessage?: string; } | { type: "agent_end"; /** * True when the CLI will re-enter the agent loop for this same logical * run (transient retry, length continuation, compact-and-retry). The * turn is NOT over — keep the composer busy until `run_state(false)`. */ willRetry?: boolean; } | { /** * Authoritative run-state snapshot. Emitted whenever `busy`/`phase`/`flags` * change, and on session hydrate. The frontend uses `generation` to * discard out-of-order frames. */ type: "run_state"; sessionId: string; runState: WireRunState; } | { /** * Semantic "the agent will come back" signal for gaps not covered by * `agent_end.willRetry` (length continuation / compact-and-retry / * auto-retry). The stream turns this into `pendingContinue`. */ type: "turn_continue"; reason: "retry" | "length_continuation" | "compaction"; } | { /** * Emitted when spectral gives up auto-retrying a failed provider call * (`auto_retry_end` with `success:false`) — the retry budget is * exhausted and the turn is over. Additive signal for UI/logging: the * terminal failed assistant message itself is persisted separately * (message_end with stopReason "error" is never dropped). */ type: "retry_failed"; /** 1-based attempt number of the final failed attempt. */ attempt?: number; /** Humanized final error that exhausted all retry attempts. */ finalError?: string; } | { /** * Broadcast to ALL subscribers of a session when the server changes the * session's title — currently only fired by the server-side auto-titler * after the first assistant turn completes. Manual user renames take a * different code path (PATCH /api/sessions/:id) and the sidebar * already updates locally for those. * * Clients should treat this as authoritative and reflect the new title * everywhere it appears (sidebar list, browser tab title, etc.). */ type: "session_renamed"; sessionId: string; title: string; } | { type: "error"; message: string; } | { /** Emitted at the start of each loop iteration. */ type: "loop_iteration"; iteration: number; maxIterations: number; prompt: string; } | { /** Emitted when the agent signals completion via marker. */ type: "loop_complete"; iterations: number; } | { /** Emitted when the loop hits the safety iteration limit. */ type: "loop_max_iterations"; iterations: number; } | { /** Emitted when the interval scheduler is armed. */ type: "interval_armed"; intervalMinutes: number; hasLoop: boolean; goal: string | null; } | { /** Emitted each time the interval scheduler launches a new occurrence. */ type: "interval_launched"; occurrence: number; } | { /** Emitted when a scheduled tick is skipped because the session is busy. */ type: "interval_skipped"; reason: "busy"; } | { /** Emitted when the interval scheduler is disarmed (stop/dispose). */ type: "interval_stopped"; } | { /** * Emitted once per assistant message, right after `message_end`. * Carries token counts and estimated usage for the just-completed * assistant turn. */ type: "token_usage"; messageId: string; usage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; totalTokens: number; /** @deprecated Legacy USD estimate kept for wire compatibility. */ cost: number | null; /** Estimated Aexol credits used for this message. */ creditsUsed: number; }; /** Cumulative context tokens used across the session (from spectral's getContextUsage()). Null if unknown. */ contextWindowUsed: number | null; /** Model's total context window in tokens. Null if model metadata is not available. */ contextWindowMax: number | null; /** * Wall-clock generation window in ms: message_end minus the first * streamed text/thinking delta (excludes TTFT). Null when timing is * unavailable (e.g. synthetic context-window refresh events, providers * that skip per-token deltas). */ durationMs?: number | null; /** * Time-to-first-delta in ms: first streamed delta minus message_start. * Null when no delta was streamed or timing is unavailable. */ ttftMs?: number | null; /** * Output tokens per second across the generation window, rounded to * 1 decimal. Null when outputTokens <= 0, durationMs <= 0, or no delta * was ever streamed. */ tokensPerSecond?: number | null; } | { /** * Emitted when spectral starts context compaction (manual or auto). * Non-replayable session lifecycle event; the client can use this * to show a "Compacting context…" indicator. */ type: "compaction_start"; /** Why compaction was triggered. */ reason: "manual" | "threshold" | "overflow"; } | { /** * Emitted during context compaction as the LLM generates the * summary. Streams the summary text incrementally so the UI can * show live progress instead of waiting for compaction_end. */ type: "compaction_delta"; /** Text delta from the compaction LLM call. */ delta: string; /** Cumulative compaction content so far. */ content: string; } | { /** * Emitted when spectral completes context compaction. The session has * been reloaded with the compacted context; subsequent prompts * will see the reduced context window footprint. */ type: "compaction_end"; /** Summary text generated by the compaction LLM call. */ summary: string; /** Token count before compaction. */ tokensBefore: number; /** Estimated tokens in the raw span replaced by the compaction summary. */ tokensCompacted?: number; /** Estimated net tokens removed by replacing raw span tokens with summary tokens. */ tokensRemoved?: number; /** Estimated tokens added back as the compaction summary. */ summaryTokens?: number; /** Estimated percentage of compacted raw tokens removed by the summary. */ reductionPercent?: number; /** 1-based compaction ordinal within the session branch. */ compactionNumber?: number; /** Largest estimated token buckets in the compacted span. */ largestTokenSources?: WireCompactionTokenSourceBucket[]; /** UUID of the first branch entry kept after compaction (spectral's cut point). */ firstKeptEntryId?: string; /** True when compaction was aborted (e.g. by a new user message or manual abort). */ aborted?: boolean; /** True when compaction intentionally did not append/prune because it would not shrink context. */ skipped?: boolean; /** Machine-readable skip reason. */ skipReason?: "non_shrinking"; /** Human-readable failure or skip message. */ errorMessage?: string; /** True when the run will continue after compaction (compact-and-retry). */ willRetry?: boolean; } | { /** * Emitted by extensions (e.g. observational memory) to surface * informational status updates in the browser UI. Not part of the * turn timeline — purely a transient notification. */ type: "agent_notification"; /** Human-readable message. */ message: string; /** Severity level. */ level: "info" | "warning" | "error"; /** * Subsystem that emitted the notification. Allows the UI to show * targeted indicators (e.g. "Memory · observing"). */ system?: "memory_observer" | "memory_compaction" | "memory_reflection" | "memory_pruner" | "extension" | "vision" | "prompt_mutation"; /** Additive structured metadata; notification text remains display-only. */ dcpLiteTelemetry?: WireDcpLiteTelemetry; } | { /** * Emitted when auto-research starts for a project. The UI can show * a progress indicator with the project name. */ type: "auto_research_start"; projectId: string; } | { /** * Emitted periodically while auto-research is running. The `phase` * field allows the UI to show granular status: * - "context_collecting" — reading project files and git state * - "context_analyzing" — LLM analyzing the collected context * - "extension_generating" — writing .ts extension files * - "extension_validating" — validating generated extensions */ type: "auto_research_progress"; projectId: string; phase: "context_collecting" | "context_analyzing" | "extension_generating" | "extension_validating"; message: string; } | { /** * Emitted when auto-research completes successfully. Contains the * list of generated extensions with their names, paths, and brief * descriptions. The UI can show a summary and offer to enable them. */ type: "auto_research_complete"; projectId: string; extensions: Array<{ name: string; path: string; description: string; /** True if the extension uses LLM inference (Phase 5+). */ usesLLM?: boolean; /** File count for multi-file extensions. */ fileCount?: number; }>; /** True if AGENTS.md was updated with an auto-research section. */ agentsMdUpdated?: boolean; } | { /** Emitted when auto-research encounters an error. */ type: "auto_research_error"; projectId: string; message: string; } | { /** Emitted when auto-optimizer starts (analyze or execute phase). */ type: "auto_optimize_start"; projectId: string; action: "analyze" | "execute"; } | { /** Emitted after the analyze phase completes with recommendations. */ type: "auto_optimize_analysis_complete"; projectId: string; recommendations: Array<{ id: string; filePath: string; issue: string; suggestion: string; severity: "low" | "medium" | "high"; estimatedLineReduction?: number; }>; } | { /** Emitted after the execute phase — per-recommendation results + gate checks. */ type: "auto_optimize_execution_complete"; projectId: string; results: Array<{ id: string; filePath: string; success: boolean; linesBefore?: number; linesAfter?: number; errorMessage?: string; }>; gatesPassed: boolean; gateResults: Array<{ gate: string; passed: boolean; message: string; }>; } | { /** Emitted when auto-optimizer encounters an error. */ type: "auto_optimize_error"; projectId: string; message: string; } | { /** * Emitted when another active session in the same project shares an * observation via the inter-agent broker. The UI can show a transient * indicator or include the observation in the session context. */ type: "inter_agent_observation"; projectId: string; sourceSessionId: string; sourceSessionName?: string; memoryId: string; content: string; relevance: "low" | "medium" | "high" | "critical"; originType: "observation" | "reflection"; timestamp: string; } | { /** * Emitted when a subagent tool invocation begins. The UI can show a * dedicated expandable panel with agent name, task description, and * a loading indicator. Parallel mode will show multiple cards. */ type: "subagent_start"; /** Tool call ID so the frontend can correlate with tool_call/tool_result. */ toolCallId: string; /** Timestamp (ms) when the subagent tool invocation started. */ startedAt?: number; mode: "single" | "parallel" | "chain"; /** Single-mode: the agent name. */ agent?: string; /** Single-mode: the task description. */ task?: string; /** Parallel/chain mode: total task count. */ taskCount?: number; /** Chain mode: total steps. */ totalSteps?: number; } | { /** * Emitted periodically during parallel/chain subagent execution. * Carries a status message and completion counts so the UI can * show live progress (e.g. "2/5 tasks done, 3 running"). */ type: "subagent_progress"; toolCallId: string; mode: "single" | "parallel" | "chain"; completedCount?: number; totalCount?: number; currentStep?: number; currentAgent?: string; message: string; /** Streaming assistant text accumulated so far (single mode). Updated on text_delta. */ streamingText?: string; /** Streaming assistant thinking accumulated so far (single mode). Updated on thinking_delta. */ thinkingText?: string; /** Responses API reasoning metadata for thinkingText (single mode). */ thinkingMetadata?: Record; systemPrompt?: string; firstMessage?: string; /** Per-subagent live snapshot (parallel mode). */ subagents?: Array<{ agent: string; task?: string; step?: number; /** -1 (or undefined) while running; 0 on success; non-zero on error. */ exitCode?: number; streamingText?: string; thinkingText?: string; systemPrompt?: string; firstMessage?: string; }>; } | { /** * Emitted when a subagent tool invocation completes (all subagents * finished). Carries structured results with usage stats so the * frontend can render rich expandable panels with per-agent output, * token counts, and model info. */ type: "subagent_end"; toolCallId: string; /** Timestamp (ms) when the subagent tool invocation started. */ startedAt?: number; /** Timestamp (ms) when the subagent tool invocation finished. */ finishedAt?: number; mode: "single" | "parallel" | "chain"; results: Array<{ agent: string; agentSource: "user" | "project" | "unknown"; task: string; exitCode: number; /** Final output text from the subagent. */ output: string; model?: string; errorMessage?: string; stopReason?: string; /** Chain mode: step number (1-indexed). */ step?: number; /** Number of child-run segments (>1 when a resume retry fired after a * transient provider error; the child resumed at the failed turn). */ attempts?: number; usage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; cost: number; turns: number; /** Estimated Aexol credits used by this subagent (per-model rates). */ creditsUsed?: number; }; systemPrompt?: string; firstMessage?: string; }>; /** Aggregate usage across all subagents in this invocation. */ totalUsage: { inputTokens: number; outputTokens: number; cacheReadTokens: number; cacheWriteTokens: number; cost: number; turns: number; /** Sum of per-subagent creditsUsed (folded into the parent turn's totals). */ creditsUsed?: number; }; } | { /** * Emitted while a subagent is running to surface the internal tool * calls it makes (tool name, arguments, result, status). Lets the * frontend sheet show a live activity log without changing the * subagent's final tool_result contract. */ type: "subagent_tool_progress"; /** Tool call ID of the parent `subagent` tool invocation. */ toolCallId: string; /** Internal tool call ID inside the subagent's loop. */ stepId: string; /** Tool name. */ name: string; /** Tool arguments. */ args: unknown; /** Current execution status of the step. */ status: "running" | "success" | "error" | "aborted"; /** Tool result, present for success/error statuses. */ result?: unknown; /** Partial tool result streamed while status is still running. */ partialResult?: unknown; /** True when the step ended with an error. */ isError?: boolean; /** Optional human-readable status / progress message. */ message?: string; /** Timestamp (ms) when the step started running. */ startedAt?: number; /** Timestamp (ms) when the step finished. */ finishedAt?: number; } | { /** * Broadcast when the prompt queue for this session changes * (enqueue, remove, clear, auto-dequeue). Carries the full * current queue state so clients can replace their local state * atomically. */ type: "queue_changed"; queue: Array<{ id: string; content: string; images?: ImageAttachment[]; position: number; createdAt: string; }>; } | { /** * Broadcast machine-wide when a tracked dev process changes status. * `process` carries the canonical summary when the registry has it; * `processId` is always set for correlation on kill/exit frames. */ type: "dev_processes_changed"; processId?: string; process?: DevProcessSummary; change: "started" | "status" | "exited" | "killed" | "ports"; } | { /** * Broadcast machine-wide while a tracked dev process emits output. * `chunk` is the newest delta only; `tail` is the bounded rolling * tail (never the full output). */ type: "dev_process_output"; processId: string; chunk: string; tail: string; }; //# sourceMappingURL=wire.d.ts.map