/** * Agent Events — the unified event stream. * * These are the real-time events emitted by agent drivers (bridge, vm-agent) * and consumed by runners, gateways, and frontends. Persistent events are * wrapped in a {@link Message} envelope by the host's persistence layer. */ import type { CapabilityDeregisterEvent, CapabilityInvokedEvent, CapabilityRegisterEvent, RenderInvokedEvent } from "./capabilities.js"; import type { IncompatibleProtocolEvent, ProtocolInfoEvent, SessionInitAckEvent, SessionInitMismatchEvent } from "./lifecycle.js"; import type { LogEntry } from "./logging.js"; /** * Discriminated union of every event the Skaile agent runtime can emit. * * Persistent events are wrapped in a `MessageV2` envelope by the host's persistence * layer. Transient events are forwarded over the transport but not appended to the * message store. Narrow via `event.type` or a `switch` statement. * * @docLink packages/types/events#agent-event */ export type AgentEvent = TextEvent | ToolCallEvent | ToolResultEvent | QuestionEvent | QuestionReplyEvent | ErrorEvent | StatusEvent | DevServerEvent | FileChangeEvent | SubagentEvent | FinishedEvent | A2AMessageEvent | StateChangedEvent | HandoffEvent | ConnectorResponseEvent | ConnectorQueryResponseEvent | ConnectorMutateResponseEvent | PromptAckEvent | ConnectorsAvailableEvent | LifecycleCompleteEvent | TypingEvent | UserMessageEvent | SyncStatusEvent | SecretsReadyEvent | ConfiguredEvent | MountMountedEvent | MountUnmountedEvent | MountChangedEvent | MountSyncStatusEvent | DebugResultEvent | LogEntryEvent | ValidationWarningEvent | SkillsChangedEvent | CommandsAvailableEvent | FlowBreadcrumbEvent | FlowPromptEvent | SessionInfoEvent | ReactionAddedEvent | ReactionRemovedEvent | ResourceStatusEvent | ConnectorStatusEvent | CustomMessageEvent | SnapshotEvent | CompactionAttemptEvent | RuntimeSessionEvent | ResumeAttemptedEvent | ResumeFailedEvent | CapabilityRegisterEvent | CapabilityDeregisterEvent | CapabilityInvokedEvent | RenderInvokedEvent | ProtocolInfoEvent | IncompatibleProtocolEvent | SessionInitAckEvent | SessionInitMismatchEvent | SystemPromptComposedEvent | ProviderResponseSeenEvent; /** * A text message from the agent to the user. * * The most common event type, containing plain-text content generated by the agent. * * @docLink packages/types/events#text-event */ export type TextEvent = { type: "text"; content: string; }; /** * A tool call request from the agent. * * Indicates the agent wants to invoke a tool with the given name and input arguments. * The platform should execute the tool and return the result via {@link ToolResultEvent}. * * @property tool - The name of the tool to invoke (must match a registered tool) * @property input - The input arguments to pass to the tool (structure depends on the tool) * @property summary - Short human-readable summary of the tool call * * @see {@link TextEvent} for plain text messages * @see {@link ToolResultEvent} for tool execution results * @category Events * @since 1.0.0 * @docLink packages/types/events#tool-call-event */ export type ToolCallEvent = { type: "tool_call"; tool: string; input: unknown; summary: string; }; /** * The result of a tool execution. * * Contains the output returned by a tool after it has been executed. * This event is typically sent by the platform in response to a {@link ToolCallEvent}. * * @property tool - The name of the tool that was executed * @property output - The result data returned by the tool (structure depends on the tool) * @property summary - Short human-readable summary of the result * * @see {@link ToolCallEvent} for the corresponding tool invocation * @category Events * @since 1.0.0 * @docLink packages/types/events#tool-result-event */ export type ToolResultEvent = { type: "tool_result"; tool: string; output: unknown; summary: string; }; /** * A question posed by the agent to the user. * * Used when the agent needs clarification or input from the user to proceed. * The question may include multiple-choice options to guide the user's response. * * @property question - The question text to display to the user * @property options - Optional array of suggested answers (when provided, user should select one) * * @see {@link QuestionReplyEvent} for the user's response * @category Events * @since 1.0.0 * @docLink packages/types/events#question-event */ /** Durable native approval presentation, never tool identity or permission authority. */ export type NativeApprovalDescription = { kind: "command" | "file" | "permissions" | "mcp"; details: string[]; }; export type QuestionEvent = { /** Opaque correlated request identity; managed replies must echo it unchanged. */ requestId?: string; /** Fixed Approve once/Deny card; clients must preserve this request's exact identity. */ approval?: NativeApprovalDescription; type: "question"; question: string; options?: string[]; }; /** * The user's reply to a question asked by the agent. * * Sent in response to a {@link QuestionEvent} when the user provides an answer. * * @property answer - The user's response text * * @see {@link QuestionEvent} for the original question * @category Events * @since 1.0.0 * @docLink packages/types/events#question-event */ export type QuestionReplyEvent = { type: "question_reply"; answer: string; }; /** * An error condition encountered during agent execution. * * Indicates something went wrong during the agent's operation. * The severity determines whether execution can continue. * * @property message - Human-readable description of the error * @property fatal - When true, indicates the error is unrecoverable and execution should stop * * @category Events * @since 1.0.0 * @docLink packages/types/events#error-event */ export type ErrorEvent = { type: "error"; message: string; fatal: boolean; category?: ErrorCategory; /** * Short actionable advice for display to the user. Set by the bridge's * error classifier when it recognises the upstream failure mode (e.g. * "Quota exhausted on the configured Anthropic account — try again * after the rate-limit window resets"). Optional. */ hint?: string; /** * Usage of the turn that ended in this error, present only when the error * is a turn result (`error_max_turns`, `error_during_execution`, …) and the * driver tracked usage. The provider bills the turn either way; a consumer * that ledgers `finished` should ledger these too. Same fields and meaning * as on {@link FinishedEvent}. Absent on errors that are not a turn result. * Added in 3.12.0. */ costUsd?: number; /** Main-thread token usage of the failed turn. See {@link FinishedEvent.tokens}. Added in 3.12.0. */ tokens?: TokenUsage; /** Per-model usage of the failed turn. See {@link FinishedEvent.modelUsage}. Added in 3.12.0. */ modelUsage?: Record; /** Context fill of the failed turn. See {@link FinishedEvent.contextTokens}. Added in 3.12.0. */ contextTokens?: number; /** Context window of the main model. See {@link FinishedEvent.contextWindow}. Added in 3.12.0. */ contextWindow?: number; }; /** * A status change notification for the agent. * * Emitted when the agent's execution phase changes (e.g., thinking → working → idle). * * @see {@link AgentPhase} for available phase values * @category Events * @since 1.0.0 * @docLink packages/types/events#supporting-types */ export type StatusEvent = { type: "status"; phase: AgentPhase; }; /** * Emitted when a dev server (e.g. Vite, Next.js) starts, becomes ready, or crashes. * * The platform uses this to surface a preview URL in the session UI. * * @category Events * @since 1.0.0 * @docLink packages/types/events#agent-event */ export type DevServerEvent = { type: "dev_server"; url: string; status: "starting" | "ready" | "crashed"; }; /** * A file system change event. * * Emitted when files are created, edited, deleted, or conflicted. * * @property type - Always "file_changed" * @property path - The file path that changed * @property action - What happened: create, edit, delete, or conflict * @property mountId - Which mount triggered the change, if any * * @category Events * @since 1.0.0 * @docLink packages/types/events#file-change-event */ export type FileChangeEvent = { type: "file_changed"; path: string; action: "create" | "edit" | "delete" | "conflict"; /** Which mount triggered the change, if any. */ mountId?: string; }; /** * Status update for a subagent dispatched by the primary agent. * * Emitted when a named subagent starts or finishes execution. On "finished", * carries structured output with a summary, changed files, and concerns. * * @docLink packages/types/events#agent-event */ export type SubagentEvent = { type: "subagent"; name: string; status: "started" | "finished"; description: string; /** Flow node this subagent is executing (set by orchestrator) */ nodeId?: string; /** Structured output from the subagent (set on "finished") */ output?: { summary: string; filesChanged: string[]; status: string; concerns: string[]; }; }; /** * Per-turn token usage reported by a driver. * * All fields are optional so the type can represent partial data — different * providers expose different subsets: * * | Provider | input | output | cacheRead | cacheCreation | reasoning | * |-----------|-------|--------|-----------|---------------|-----------| * | Anthropic | y | y | y | y | - | * | OpenAI | y* | y | y | - | y | * * `*` Anthropic's `inputTokens` excludes cached/cache-creation reads (they * are reported separately). OpenAI's `inputTokens` is the *total* including * cached tokens — consumers that want a strict "fresh input" count must * subtract `cacheReadTokens` themselves. * * @category Cost * @since 3.1.0 * @docLink packages/types/events#token-usage */ export type TokenUsage = { /** Input/prompt tokens consumed by the turn (provider-specific semantics — see type docstring). */ inputTokens?: number; /** Output/completion tokens generated by the model. */ outputTokens?: number; /** Tokens served from the provider's prompt cache (Anthropic: cache_read_input_tokens; OpenAI: cached_input_tokens). */ cacheReadTokens?: number; /** Tokens written to the prompt cache for future reads (Anthropic-only — OpenAI does not expose this). */ cacheCreationTokens?: number; /** Reasoning tokens consumed by OpenAI reasoning models (o1, o3, ...). */ reasoningTokens?: number; /** Total tokens for the turn when the provider supplies one. */ totalTokens?: number; }; /** * One model's share of a turn, including calls the turn-level `tokens` omit. * * `TokenUsage` on a `FinishedEvent` describes the main thread only: Claude * Code's result `usage` skips every request a Task subagent made, while its * reported cost includes them. `FinishedEvent.modelUsage` fills that gap, * keyed by model id, so consumers can attribute the whole bill and derive the * subagent share as `sum(modelUsage[*]) − tokens`. Field semantics follow * `TokenUsage` (Anthropic: disjoint prompt slices). Drivers emit per-turn * figures here even when the provider reports run-cumulative counters. * * @category Cost * @since 3.11.0 */ export type ModelTokenUsage = Pick & { /** Provider-reported cost of this model's calls in the turn, in USD. */ costUsd?: number; /** Context window of this model, in tokens. */ contextWindow?: number; }; /** * Emitted when the agent completes a turn. * * Carries cost / usage data and optionally a custom message payload for dynamic * rendering via the v2 capability registry (`customType` + `customData` fields). * * `costUsd` semantics: this is per-turn cost in USD. Some providers report a * run-cumulative figure instead of a per-turn one — the driver is responsible * for normalizing it into a per-turn delta before emitting it here. Drivers * that don't surface provider-reported cost emit `0`; consumers distinguishing * "free" from "not reported" should pair `costUsd === 0` with `tokens` being * present/absent. * * @docLink packages/types/events#finished-event */ export type FinishedEvent = { type: "finished"; summary: string; costUsd: number; /** * Per-turn token usage when the driver tracks it. Absent for drivers * that do not surface usage (e.g. omp RPC today). Added in 3.1.0. */ tokens?: TokenUsage; /** * Per-model usage for the turn, subagent calls included, when the driver * reports it. See {@link ModelTokenUsage}. Added in 3.11.0. */ modelUsage?: Record; /** * Prompt size of the turn's last main-thread API call — the real context * fill — when the driver tracks it. `tokens` cannot answer this: it sums * every call in the turn. Added in 3.11.0. */ contextTokens?: number; /** Context window of the main model, in tokens, when known. Added in 3.11.0. */ contextWindow?: number; /** True when the response was cancelled by the user (partial content). */ cancelled?: boolean; /** Custom message type (e.g. "gif") — clients render via a registered dynamic component. */ customType?: string; /** Structured data for custom message types — used as component props. */ customData?: Record; }; /** * An agent-to-agent (A2A) message exchanged between two linked sessions. * * Persisted as a `Message` row in BOTH the caller and target session * conversations — each side stores its own copy with the matching * `direction`. `inbound` is the copy in the receiving session's * conversation; `outbound` is the copy in the initiating session's * conversation. The two copies share the same `exchangeId`. * * Hosts (the Skaile platform) write this event into `Message.payload` * alongside the dedicated `a2aExchangeId` / `peerSessionId` / `a2aDirection` * columns. The chat UI's Network / All views render it as a badged A2A card. * * @docLink packages/types/events#a2a-message-event */ export type A2AMessageEvent = { type: "a2a_message"; /** `inbound` = this session received it; `outbound` = this session sent it. */ direction: "inbound" | "outbound"; /** The other end of the exchange — the peer session's id. */ peerSessionId: string; /** Human-readable name of the peer session, for UI display. */ peerName: string; /** The A2A exchange this message belongs to (`A2AExchange.id`). */ exchangeId: string; /** The message body — the question, answer, or async message text. */ text: string; }; /** * Emitted when a shared state store's snapshot changes. * * Carries the full serialized state snapshot for the named store (e.g. "flow", "session"). * Frontends and the platform gateway subscribe to build reactive state views. * * @docLink packages/types/events#agent-event */ export type StateChangedEvent = { type: "state_changed"; /** Store identifier, e.g. "flow", "session" */ store: string; /** Serialized state snapshot */ state: Record; }; /** * Emitted when the orchestrator hands execution off to a specific flow node and skill. * * Carries the context sources and approximate context size used to curate * the handoff prompt so operators can diagnose context-window decisions. * * @docLink packages/types/events#agent-event */ export type HandoffEvent = { type: "handoff"; /** Flow node being handed off to */ nodeId: string; /** Skill being executed */ skillId: string; /** Node IDs that contributed context to this handoff */ contextSources: string[]; /** Approximate size of curated context in characters */ contextSize: number; }; /** * Response to a `ConnectorRequestCommand` (wire type: `"resource_response"`). * * Correlates to the originating request via `requestId` and carries the * operation result or an error string. The wire `type:` string stays as * `"resource_response"` for protocol stability. * * @docLink packages/types/events#agent-event */ export type ConnectorResponseEvent = { type: "resource_response"; /** Correlates to the originating resource_request. */ requestId: string; /** Which connector handled the request. */ resourceId: string; /** Which operation was performed. */ operation: ConnectorOperation; /** Result data — shape depends on the operation. */ data?: unknown; /** Error message if the operation failed. */ error?: string; }; /** * Response to a `ConnectorQueryCommand`. Carries the JSON-encoded result of * a `ConnectorManager.executeOp` call, correlated by `requestId`. * * Persistence: transient. Hosts that want to record the query side effects * should rely on the runner's structured log stream, not on this event. * * @docLink packages/types/events#connector-query-response-event * @since 2.2.0 */ export type ConnectorQueryResponseEvent = { type: "connector_query_response"; /** Correlates to the originating ConnectorQueryCommand. */ requestId: string; /** Whether the underlying executeOp resolved without throwing. */ ok: boolean; /** JSON-encoded executeOp result, when `ok === true`. */ result?: string; /** Error message when `ok === false`. */ error?: string; }; /** * Machine-readable failure class carried by {@link ConnectorMutateResponseEvent}. * Present iff `ok === false`. Hosts branch on this rather than on the free-text * `error` string, which is a runner/adapter message and may change. * * @category Protocol * @since 3.7.0 * @docLink packages/types/events#connector-mutate-response-event */ export type ConnectorMutateFailureCode = /** The target connector never became available within the readiness wait. */ "connector_unavailable" /** `planFlowMutate` rejected the op (malformed or conflicting payload). */ | "rejected" /** The adapter threw — illegal transition, unknown node, read-only violation. */ | "op_failed" /** `sessionReady` rejected: the session build failed, the container is unusable. */ | "session_unavailable"; /** * Outcome of replaying `payload.pendingDecisions` during a flow `hydrate`. * * Invariant: every supplied entry that carried a string `nodeId` appears in * exactly one of `applied` / `skipped`. A skip is a deliberate structural * decision, never a failure — the hydrate itself has already succeeded by the * time reconciliation runs. Gotcha: the skip entry deliberately carries only * `nodeId` + `reason`; the decision payload is user content and is never echoed. * * @category Protocol * @since 3.7.0 * @docLink packages/types/events#connector-mutate-response-event */ export type FlowHydrateReconciliation = { /** nodeIds whose pending decision was applied during this hydrate. */ applied: string[]; /** nodeIds whose pending decision was deliberately not applied. */ skipped: Array<{ nodeId: string; reason: "not_parked" | "already_decided" | "unknown_node" | "payload_invalid"; }>; }; /** * Response to a `ConnectorMutateCommand` that carried a `requestId`. * * Emission invariant: **exactly one** per command that set `requestId`, on every * terminal path (including the paths that previously only logged), and **never** * for a command without one — that opt-in is the whole 3.6 back-compat story. * * Persistence: transient. Like `connector_query_response` this is system RPC, * not chat; hosts must keep it out of the message store. * * @category Protocol * @since 3.7.0 * @docLink packages/types/events#connector-mutate-response-event */ export type ConnectorMutateResponseEvent = { type: "connector_mutate_response"; /** Correlates to the originating ConnectorMutateCommand.requestId. */ requestId: string; /** Whether the mutation was applied. */ ok: boolean; /** JSON-encoded executeOp result, when ok === true and the op returned one. */ result?: string; /** Human-readable failure message when ok === false. */ error?: string; /** Machine-readable failure class. Present iff ok === false. */ code?: ConnectorMutateFailureCode; /** * Outcome of `payload.pendingDecisions` reconciliation. Present only for * `op === "hydrate"` on the flow connector, and only when the host supplied * pending decisions. Absent is NOT "nothing applied" — it is "not attempted". */ reconciled?: FlowHydrateReconciliation; }; /** * Receipt acknowledgement for a `prompt` command that carried a `messageId`. * * Emission invariant: **exactly one** per prompt that set `messageId`, emitted * as the runner's first act — before the session-readiness gate, the secrets * check, and the driver start, each of which can otherwise drop the prompt with * no trace. It says "the frame arrived and was decoded", nothing about the turn * ever running; turn-level supervision is a separate concern. * * Persistence: transient. System RPC, not chat. * * @category Protocol * @since 3.8.0 */ export type PromptAckEvent = { type: "prompt_ack"; /** Echoes the originating `prompt` command's `messageId` verbatim. */ messageId: string; }; /** * Emitted after session boot listing all connected mounts and connectors. * * Consumers use this event to populate the resource explorer and build initial * connector status maps. The wire type is `"resources_available"`. * * @docLink packages/types/events#agent-event */ export type ConnectorsAvailableEvent = { type: "resources_available"; /** All connected mounts with their metadata. */ mounts: MountInfo[]; /** All connected connectors with their metadata. */ connectors: ConnectorInfo[]; /** All runner-managed MCP servers with their metadata. */ mcp_servers: McpServerInfo[]; }; /** * Emitted whenever a connector's connection state changes. * * Transient — not persisted to the message store. Consumed exclusively by * `ResourceClient._handleEvent()` in `@skaile/workspaces/store` to maintain the * reactive `connectorStatus` map. * * @docLink packages/types/events#connector-status-event */ export interface ConnectorStatusEvent { type: "connector_status"; /** Matches ConnectorDeclaration.id. */ connectorId: string; /** Current connection state. */ status: "connecting" | "connected" | "error" | "disconnected"; /** Present when status === "error". */ error?: string; /** Round-trip latency in milliseconds from the last health check. */ latencyMs?: number; } /** * Emitted when a lifecycle phase (hibernate, close, compact) completes across all resources. * * Carries per-resource results so the platform can surface which resources failed * during the phase. The `phase` field identifies which lifecycle stage completed. * * @docLink packages/types/events#agent-event */ export type LifecycleCompleteEvent = { type: "lifecycle_complete"; /** Which lifecycle phase completed. */ phase: LifecyclePhase; /** Per-resource results. */ results: LifecycleResult[]; }; /** * Transient — broadcast when a user or agent adds an emoji reaction to a message. * * Not persisted as a message. Consumed by frontends to update the reaction display in real time. * * @docLink packages/types/events#reaction-events */ export type ReactionAddedEvent = { type: "reaction_added"; /** ID of the message being reacted to. */ messageId: string; /** Sequence number of the target message (for efficient frontend lookup). */ messageSeq: number; emoji: string; userId: string; userName: string; }; /** * Transient — broadcast when a user removes a reaction from a message. * * Not persisted as a message. Consumed by frontends to update the reaction display in real time. * * @docLink packages/types/events#reaction-events */ export type ReactionRemovedEvent = { type: "reaction_removed"; messageId: string; messageSeq: number; emoji: string; userId: string; }; /** * Transient typing indicator — not persisted. * * Broadcast to all session subscribers when a participant starts or stops typing. * Frontends use this to render the "X is typing..." indicator. * * @docLink packages/types/events#typing-user-message-secrets-ready-configured-events */ export type TypingEvent = { type: "typing"; userId: string; userName: string; isTyping: boolean; }; /** * Transient echo of a user message broadcast to all session subscribers — not persisted. * * Carries reply-threading fields so clients can detect parent-message relationships * and route mention-style notifications. * * @docLink packages/types/events#typing-user-message-secrets-ready-configured-events */ export type UserMessageEvent = { type: "user_message"; senderId: string; senderName: string; content: string; /** Set when this user message is a reply to a previous message. */ parentMessageId?: string; /** Sender id of the parent message — lets clients detect "reply to me". */ parentSenderId?: string; /** When true, clients should treat this reply as a mention-style notification target. */ notifyParent?: boolean; /** * Routing mode of the message envelope. * - `"Public"` (default when omitted) — broadcast to every session * subscriber (humans + agent). * - `"Private"` — visible only to the sender plus everyone listed in * `privateRecipientIds`. * - `"HumansOnly"` — visible to all human subscribers; the agent is * excluded from broadcast and notifications. * * **Invariant (NOT enforced by the type system):** producers MUST populate * a non-empty `privateRecipientIds` whenever this is `"Private"`. The * shape is intentionally flat (rather than a discriminated union) to * keep wire compat with older consumers, but this means TypeScript will * not catch violations at the call site. **Persistence-layer * implementations MUST validate this invariant on intake** and reject / * downgrade / log a violation rather than persist a Private message * with no recipients (which would be visible to nobody and effectively * lost). * * Reference platform implementation: `PlatformMessageStore.append` * throws on `Private + empty recipientIds` to fail fast. */ visibilityMode?: "Public" | "Private" | "HumansOnly"; /** * Recipient list for `visibilityMode: "Private"`. Each entry is either a * user id (matching the platform's user-id shape) or the sentinel * `"__agent__"` when the agent itself is an explicit recipient (i.e. the * sender wrote `@agent_`). * * MUST be non-empty when `visibilityMode === "Private"` — see the * invariant note on `visibilityMode` above. * * Empty / absent for `Public` and `HumansOnly` — recipient sets in those * modes are implicit (everyone / everyone-except-agent). */ privateRecipientIds?: string[]; }; /** * Transient signal that provisioned secrets have been stored and resources initialized. * * The platform waits for this event after sending a `SecretProvisionCommand` before * allowing further session interaction. * * @docLink packages/types/events#typing-user-message-secrets-ready-configured-events */ export type SecretsReadyEvent = { type: "secrets_ready"; }; /** * Transient ACK emitted once the agent has finished processing a `configure` * command (shared state stores registered, MCP tools rebuilt, active flows * rehydrated). Backends await this before releasing the session so prompts * can't race the configure handler and start the driver with stale tools. * * @docLink packages/types/events#typing-user-message-secrets-ready-configured-events */ export type ConfiguredEvent = { type: "configured"; }; /** * Emitted when a mount is successfully attached to the workspace. * * Carries the mount id, filesystem path, driver name, and access level. * * @docLink packages/types/events#agent-event */ export type MountMountedEvent = { type: "mount_mounted"; id: string; mountPath: string; driver: string; access: string; }; /** * Emitted when a mount is detached from the workspace. * * @docLink packages/types/events#agent-event */ export type MountUnmountedEvent = { type: "mount_unmounted"; id: string; }; /** * Emitted when a file changes within a mounted source. * * Similar to `FileChangeEvent` but scoped to a specific mount by `id`. * * @docLink packages/types/events#agent-event */ export type MountChangedEvent = { type: "mount_changed"; id: string; path: string; action: "create" | "edit" | "delete" | "conflict"; }; /** * Progress update for a mount's initial synchronization phase. * * Emitted repeatedly during the sync lifecycle (enumerating, stubs_ready, * downloading, initial_sync_complete). Carries file counts for progress display. * * @docLink packages/types/events#agent-event */ export type MountSyncStatusEvent = { type: "mount_sync_status"; id: string; phase: SyncStatusPhase; totalFiles: number; downloadedFiles: number; stubFiles: number; message: string; }; /** * Persisted chat event summarizing a flow lifecycle milestone. * * The gateway diffs each incoming flow state_changed event against the * previous snapshot and emits breadcrumbs for user-meaningful transitions * — flow start/end, approval requests, input requests, and node failures. * Per-node traffic (running/complete/skipped) is NOT emitted as a * breadcrumb to keep the chat stream readable; that detail lives on the * flow execution panel. * * Breadcrumbs are persisted via the dispatcher's normal message store path * and rendered on the frontend as collapsed cards that expand inline. * * @docLink packages/types/events#flow-breadcrumb-event */ export type FlowBreadcrumbEvent = { type: "flow_breadcrumb"; kind: "flow_started" | "flow_finished" | "flow_failed" | "flow_cancelled" | "approval_pending" | "input_pending" | "approval_resolved" | "input_resolved" | "node_failed"; runId: string; flowId: string; flowName: string; /** Node that triggered this breadcrumb, if the kind is node-scoped. */ nodeId?: string; /** Human-readable node label (from flow definition `data.label`). */ nodeName?: string; /** User ID of the actor that caused the transition (if applicable). */ actorUserId?: string; /** One-line summary for the collapsed card rendering. */ summary?: string; }; /** * Persisted chat event carrying the orchestrator prompt for one flow turn. * * The flow runner reassembles the full `# Flow Execution Context` prompt on * every turn and hands it to the agent driver. Without this event a host * consuming the stream sees the agent replying to input it cannot read, so the * runner emits the prompt verbatim immediately before delivering it — the * event therefore always precedes that turn's reply events in the stream. * * `prompt` is byte-identical to the string given to the driver: never * redacted, truncated, or re-rendered. Everything the agent saw — user text, * node input, stimulus paragraph — is already inside it, which is why the * stimulus is carried only as its kind plus the node it concerns. * * Emitted for main-agent turns and inline sub-flow turns alike; a sub-flow turn * carries the **child** flow's `flowId`/`flowName`. Subprompt nodes run on their * own isolated driver and emit nothing here. * * Persisted via the dispatcher's normal message store path. * * @docLink packages/types/events#flow-prompt-event */ export type FlowPromptEvent = { type: "flow_prompt"; /** Flow run this turn belongs to. */ runId: string; /** Flow definition id. */ flowId: string; /** Human-readable flow name from the definition, which always carries one. */ flowName: string; /** * Why the runner drove this turn. Mirrors the runner's `TurnStimulus["kind"]`, * re-declared here because this package must not depend on `factory-assets`. */ stimulus: "flow_started" | "approval_received" | "input_received" | "retry_requested" | "user_message" | "resumed_after_hibernation" | "cancelled" | "state_changed"; /** Compact stimulus label for logs/telemetry, e.g. `approval_received:review:approved`. */ stimulusLabel: string; /** Node the stimulus concerns, when the stimulus is node-scoped. */ nodeId?: string; /** The verbatim orchestrator prompt handed to the agent driver. */ prompt: string; }; /** * The session lifecycle phase being executed. Carried in `LifecycleCompleteEvent.phase` * and the `lifecycle` command's `phase` field. * * @docLink packages/types/events#supporting-types */ export type LifecyclePhase = "hibernate" | "close" | "compact"; /** * Per-resource result of a lifecycle phase, carried in `LifecycleCompleteEvent.results`. * * @docLink packages/types/events#supporting-types */ export type LifecycleResult = { id: string; ok: boolean; error?: string; /** Human-readable detail (e.g. commit SHA, branch name). */ detail?: string; }; /** * Metadata for a mounted filesystem source, carried in `ConnectorsAvailableEvent.mounts`. * * Re-declared here to avoid a dependency on `@skaile/workspaces/connectors`; mirrors the * connector package's `MountInfo` type. * * @docLink packages/types/events#supporting-types */ export type MountInfo = { id: string; driver: string; /** Human-readable connection name; the resource-explorer tab renders this over `id`/`driver` when present. */ label?: string; access: "read-only" | "read-write"; mountPath: string; mounted: boolean; source: string; }; /** * Metadata for a runner-managed MCP server, carried in `ConnectorsAvailableEvent.mcp_servers`. * * `live` indicates whether the active driver session already picked up the server's tools * without requiring a restart — false means the tools are registered but not yet active. * * @docLink packages/types/events#supporting-types */ export type McpServerInfo = { id: string; transport: "stdio" | "sse" | "http"; live: boolean; toolCount: number; }; /** * Operations a connector can perform. Used in `ConnectorRequestCommand.operation` * and `ConnectorResponseEvent.operation`. * * @docLink packages/types/events#supporting-types */ export type ConnectorOperation = "list" | "read" | "write" | "delete" | "search"; /** * A single entry in a connector directory listing (result of a `list` operation). * * Re-declared here to avoid a dependency on `@skaile/workspaces/connectors`. * * @docLink packages/types/events#supporting-types */ export type ConnectorEntry = { name: string; path: string; type: "file" | "directory" | "key" | "row" | "message"; size?: number; modifiedAt?: string; }; /** * File or key content returned by a connector `read` operation. * * Re-declared here to avoid a dependency on `@skaile/workspaces/connectors`. * * @docLink packages/types/events#supporting-types */ export type ConnectorContent = { data: string; encoding?: "utf-8" | "binary"; contentType?: string; metadata?: Record; }; /** * Metadata for a connected connector, carried in `ConnectorsAvailableEvent.connectors`. * * Re-declared here to avoid a dependency on `@skaile/workspaces/connectors`; mirrors * the connector package's `ConnectorInfo` type. * * @docLink packages/types/events#supporting-types */ export type ConnectorInfo = { id: string; driver: string; /** Human-readable connection name; the resource-explorer tab renders this over `id`/`driver` when present. */ label?: string; access: "read-only" | "read-write"; operations: string[]; }; /** * Options for a connector `list` operation. * * @docLink packages/types/events#supporting-types */ export type ListOptions = { recursive?: boolean; glob?: string; limit?: number; /** * Include entries whose name starts with `.`. Off by default: a browsing * listing hides dotfiles. A caller materialising a build context from a * mount (where `.env.example`, `.npmrc`, ... must survive) opts in. * * Honored only by the runner's filesystem-mount `list`; connector drivers * (tool-face `list`) ignore it. It is not a security boundary: `read` * serves any path regardless. With `recursive` it also walks `.git`, whose * entries count against the listing cap, so a caller staging a tree should * list one directory at a time and prune before descending. */ includeHidden?: boolean; }; /** * Options for a connector `search` operation. * * @docLink packages/types/events#supporting-types */ export type SearchOptions = { regex?: boolean; caseSensitive?: boolean; maxResults?: number; }; /** * Persistent — a custom-typed message emitted by the agent via a capability * tool invocation (`platform.custom_message`). The runner's serve layer * forwards these so the platform can persist and render them via the * dynamic component pipeline. * * @category Events * @since 2.0.0 * @docLink packages/types/events#agent-event */ export type CustomMessageEvent = { type: "custom_message"; /** Plain-text summary (e.g. "[gif: funny cat]"). */ content: string; /** Custom message type identifier (e.g. "gif"). */ customType: string; /** Structured data — used as component props. */ customData: Record; }; /** * Trigger source for a session compaction. Carried in `SnapshotEvent.trigger` * and `CompactionAttemptEvent.trigger`. * * @docLink packages/types/events#snapshot-event */ export type CompactionTrigger = "threshold" | "hibernate" | "manual"; /** * Persistent — a compacted conversation snapshot produced by the LLM. * On session restore, only the last snapshot + subsequent messages are replayed. * * @docLink packages/types/events#snapshot-event */ export type SnapshotEvent = { type: "snapshot"; /** LLM-generated structured summary of the conversation. */ summary: string; /** What triggered this compaction. */ trigger: CompactionTrigger; /** Seq of previous snapshot (0 if first compaction). */ coversFromSeq: number; /** Last seq this snapshot covers. */ coversToSeq: number; /** Estimated context token count before compaction. */ tokensBefore: number; /** Token count of the summary. */ tokensAfter: number; }; /** * Persistent -- emitted on EVERY compaction attempt, success or failure. * * Replaces the in-process circuit breaker in `CompactionOrchestrator` with * an observable, owner-controllable record. The platform gateway listens * for this event and inserts a `Compaction` row plus updates the * `Session.lastCompactionStatus` companion field. * * `SnapshotEvent` continues to fire on success-only paths (back-compat * until Phase 4); both events ride alongside each other through Phase 4. * * Spec: `_devlog/specs/2026-05-05-session-resume-restart-design.md` * * @category Compaction * @since 3.1.0 * @docLink packages/types/events#compaction-attempt-event */ export type CompactionAttemptEvent = { type: "compaction_attempt"; /** * Outcome of the attempt: * - `success`: completed and validated; eligible for tier-2 resume * - `failed`: compaction call returned an error or output failed validation * - `partial`: reserved (e.g. streaming compaction emitting partial output) * - `dirty`: was success at write time but later flagged */ status: "success" | "failed" | "partial" | "dirty"; /** * When `status !== 'success'`, a stable identifier for the failure mode. * Standard codes: * - `rate_limit | auth | account | timeout | parse_error` (from bridge error classifier) * - `empty | too_short | poor_ratio` (from validateCompactionOutput) * * Note: `auth` covers both real credential failures AND payment-blocked * OAuth subscriptions that Anthropic mislabels as auth errors. `account` * covers a 401 that recurs after a successful credential refresh (hidden-tier * rate limit, billing block, anti-abuse throttle) — the credential is fine. * Owners investigate further when a Compaction row shows `auth` or `account`. * * Read alongside {@link CompactionAttemptEvent.elapsedMs}: `empty` at a * near-zero elapsed time is an infrastructure failure, not a model one. * * `null` on success. */ errorCode: string | null; /** * Wall-clock ms spent inside the compaction `prompt()` call. An `empty` * failure at ~0 ms means the turn was settled by a stale consumer rather * than by the model. Optional — absent on events from older runners. */ elapsedMs?: number; /** * Truncated diagnostic text for a failed attempt — the driver error the * classifier saw, or which validation check rejected the output. Absent on * success and on events from older runners. * * The same text is mirrored into {@link CompactionAttemptEvent.summary} so a * consumer that only persists `summary` still explains the failure. That is * safe because a failed attempt is never restored into a session: restoration * requires `status: 'success'` with `validatedAt` set. */ errorMessage?: string; /** What triggered this attempt. */ trigger: CompactionTrigger; /** LLM-generated summary text. Empty string on failure. */ summary: string; /** Model name used for the call (e.g. "claude-sonnet-4"). null when unknown. */ model: string | null; /** Provider id used for the call (e.g. "anthropic"). null when unknown. */ provider: string | null; /** Estimated context size before the compaction call — the whole prompt, cached tokens included (= tokensBefore). */ inputTokens: number; /** Output token count of the summary or 0 on failure (= tokensAfter). */ outputTokens: number; /** Seq of previous successful snapshot (0 if first compaction). */ coversFromSeq: number; /** Last seq this attempt covers (current message seq). */ coversToSeq: number; /** * Driver session id at the time of the call (for tier-1 native SDK resume). * Captured from `driver.runtimeSessionId`. null when the driver does not * expose one (e.g. echo, codex). */ driverSessionIdAtCompaction: string | null; /** * Model name at compaction time. Distinct from `model` (which records what * was used for THIS compaction call). For tier-1 resume, the wake-time * comparison is against the driver's CURRENT model — if mismatch, falls * through to tier 2. */ modelAtCompaction: string | null; /** * Capability registry signature at compaction time. null in Phase 1 * (the `computeCapabilitySignature` helper ships in Phase 3). */ capabilitySignatureAtCompaction: string | null; /** * ISO timestamp set when `validateCompactionOutput` passed. * `null` when the attempt failed validation OR produced an error. */ validatedAt: string | null; }; /** * Transient — emitted by the runner when it honors a * `AgentReconfigureOptions.resumeSessionId` in a configure command. * * The runner emits this once it has decided to recreate the driver with * the platform-supplied session id (after capability signature validation * passes, if `expectedCapabilitySignature` was provided). The platform * gateway records the attempt on the {@link Session} so the UI can show * "Resumed via SDK session" when wake completes. * * Spec: `_devlog/specs/2026-05-05-session-resume-restart-design.md` * § "Tier 1: native resume plumbing". * * @category Resume * @since 3.2.0 * @docLink packages/types/events#resume-attempted-event */ export type ResumeAttemptedEvent = { type: "resume_attempted"; /** Driver session id the runner is resuming. */ resumeSessionId: string; /** Capability signature computed at attempt time (advisory). */ capabilitySignature: string; }; /** * Transient — emitted by the runner or driver when a tier-1 native resume * could not be honored. * * Reasons: * - `signature_mismatch`: the runner's current capability signature differs * from {@link AgentReconfigureOptions.expectedCapabilitySignature}; the * runner dropped the {@link AgentReconfigureOptions.resumeSessionId} hint * before driver creation. * - `model_mismatch`: reserved — wake-time model comparison happens on the * platform side; runner does not currently emit this code, but it is * reserved for future driver-level checks. * - `jsonl_lost`: emitted by the claude-sdk driver after the SDK reported * "No conversation found with session ID …"; the driver internally * fell back to `continue: true` so the wake still completes. * - `jsonl_poisoned`: emitted by the claude-sdk driver after a replayed * resume hit a `400 invalid_request_error` from a transcript image block * with a wrong `media_type`. The driver repaired the on-disk JSONL * transcript in place (correcting media types, stubbing unprocessable * images) and retried the resume — conversation context is preserved. * * - `native_state_unavailable`: managed Codex positively classified unavailable * versioned state and completed one fresh same-binding initialization; the host * may restore DB history. Unknown native/auth/policy/transport errors are fatal. * * @category Resume * @since 3.2.0 * @docLink packages/types/events#resume-failed-event */ export type ResumeFailedEvent = { type: "resume_failed"; /** Driver session id the runner attempted to resume (and dropped). */ resumeSessionId: string; /** Stable reason code; see TSDoc above for the enumeration. */ reason: "signature_mismatch" | "model_mismatch" | "jsonl_lost" | "jsonl_poisoned" | "native_state_unavailable"; }; /** * Emitted by the runner when the driver's SDK session id first becomes known * (after the first turn) and whenever it changes thereafter. Carries the * tier-1 native-resume token so the platform can persist it on the Session * itself — independently of compaction. Without this the resume token is * captured ONLY as a side effect of a compaction (see * {@link CompactionAttemptEvent}'s `driverSessionIdAtCompaction`), so a * session that never compacts and only auto-hibernates has no stored token * and falls through to lossy raw-tail resume on wake even though its JSONL * survives on the persistent `.claude` mount. * * Low frequency: emitted on first-known + change, not per turn. Consumers * that do not care ignore it (additive to the union). * * Spec: platform `_devlog/specs/2026-06-25-tier1-resume-without-compaction.md`. * * @category Resume * @since 3.3.0 */ export type RuntimeSessionEvent = { type: "runtime_session"; /** Driver SDK session id, usable as a native `--resume` token. */ driverSessionId: string; /** * Model in effect at emit time. The wake-time tier-1 guard compares this * against the driver's current model; mismatch forces tier 2. null when * the driver does not expose a model. */ model: string | null; /** Capability registry signature at emit time (wake-time signature validation). */ capabilitySignature: string; }; /** * Transient ACK emitted by the runner after processing an add_resource or * remove_resource command. The gateway awaits this to confirm the operation * completed (or failed) before returning to the caller. * * @docLink packages/types/events#resource-status-event */ export type ResourceStatusEvent = { type: "resource_status"; resourceId: string; status: "mounting" | "active" | "error" | "removed"; mountPath?: string; error?: string; }; /** * String union of all `AgentEvent["type"]` discriminators. * * Useful for declaring event type constants or exhaustive switch coverage checks. * * @docLink packages/types/events#supporting-types */ export type AgentEventType = AgentEvent["type"]; /** * The current execution phase of the agent, carried in `StatusEvent.phase`. * * `"cancelling"` is store-local (never sent by the backend as a `StatusEvent.phase` * value): `AgentStore.cancel()` sets it optimistically, and only a later, * genuine `status` event carries the phase on to `"idle"`. * * @docLink packages/types/events#supporting-types */ export type AgentPhase = "thinking" | "working" | "idle" | "compacting" | "cancelling"; /** * Classification of an agent error, carried in `ErrorEvent.category`. * * The bridge's error classifier maps upstream errors to these categories so * consumers can apply per-category retry and display policies. * * @docLink packages/types/events#error-event */ export type ErrorCategory = "auth" | "account" | "rate_limit" | "model" | "network" | "config" | "process" | "validation" | "unknown"; /** * Structured error object used internally when classifying driver errors. * * Carries the error category, an optional HTTP status code, a retryability flag, * and an optional actionable hint for the user. * * @docLink packages/types/events#error-event */ export type AgentError = { message: string; category: ErrorCategory; statusCode?: number; retryable: boolean; hint?: string; }; /** * Lifecycle phases of a mount's initial sync operation. * * Carried in `MountSyncStatusEvent.phase` and `SyncStatusEvent.phase`. * * @docLink packages/types/events#supporting-types */ export type SyncStatusPhase = "enumerating" | "stubs_ready" | "downloading" | "initial_sync_complete" | "error"; /** * Legacy alias for `MountSyncStatusEvent` — progress update for a mount sync operation. * * Carries the mount id, lifecycle phase, and file counts. Prefer `MountSyncStatusEvent` * for new code. * * @docLink packages/types/events#supporting-types */ export type SyncStatusEvent = { type: "sync_status"; mountId: string; phase: SyncStatusPhase; totalFiles: number; downloadedFiles: number; stubFiles: number; message: string; }; /** * Transient response to a `DebugCommand` — not persisted. * * Carries the original query string and the raw diagnostic data returned * by the runner's debug handler. * * @docLink packages/types/events#debug-result-event */ export type DebugResultEvent = { type: "debug_result"; query: string; data: unknown; }; /** * Transient — a single structured log entry from the in-container LogStore. * Forwarded by the runner's WsLogSink over the agent transport. The gateway * caches a fixed-size ring of these per session for late panel openers. * NEVER persisted to the message store — historical reads go through SQLite. * * @docLink packages/types/events#log-entry-event */ export type LogEntryEvent = { type: "log_entry"; entry: LogEntry; }; /** * Transient — emitted when an asset fails schema validation during discovery. * * Carries the asset name, reason, and an optional suggestion for fixing the issue. * Not persisted. * * @docLink packages/types/events#agent-event */ export type ValidationWarningEvent = { type: "validation_warning"; asset: string; reason: string; suggestion?: string; }; /** * Transient — emitted when the skills catalog changes during a session. * * Carries the list of skills that were added or removed. Not persisted. * * @docLink packages/types/events#agent-event */ export type SkillsChangedEvent = { type: "skills_changed"; skills: Array<{ name: string; action: string; }>; }; /** * Transient event carrying the agent's discovered slash commands — not persisted. * * Emitted after the runner discovers the available commands from skills and MCP servers. * Frontends use this to populate the slash-command picker. * * @docLink packages/types/events#commands-available-event */ export type CommandsAvailableEvent = { type: "commands_available"; commands: Array<{ name: string; description: string; argumentHint?: string; }>; }; /** * Transient event emitted by the OMP driver after each turn completes. * Carries the OMP session ID that can be passed as `resumeSessionId` on the * next spawn so OMP restores its full conversation context natively — without * needing injected `` blocks. Not persisted. * * @docLink packages/types/events#session-info-event */ export type SessionInfoEvent = { type: "session_info"; /** The OMP session identifier (from `get_state` RPC response). */ driverSessionId: string; /** Absolute path to the JSONL session file written by OMP, if known. */ sessionFile?: string; }; /** * One labeled section of the assembled system prompt, as broken down by * the runner's session-builder. Single-entry labels (`context`, `prompt`, * `environment`, `resources`) appear once; multi-entry labels * (`prompt-extensions`, `composition`, `additional`) appear once per * source. * * @since 2026-05 */ export type SystemPromptSection = { label: "context" | "prompt" | "environment" | "resources" | "mcp-guidance" | "prompt-extensions" | "composition" | "additional"; value: string; /** File path, block id, or descriptor — present for multi-entry labels. */ source?: string; }; /** * Emitted by the runner after every `createAgentSession` call (initial * configure + every re-build path: `add_resource`, `remove_resource`, * `configure`). Carries the labeled section breakdown of the system * prompt the agent will receive. Transient — not persisted. * * @since 2026-05 */ export type SystemPromptComposedEvent = { type: "system_prompt_composed"; sections: SystemPromptSection[]; /** ISO timestamp when the breakdown was produced. */ composedAt: string; }; /** * Transient — the runner saw the AI provider reject a live request with a 401 * (credential) or a 429 / subscription-limit rejection, attributed to the AI * provider config the session is pinned to. * * Exists because the platform's per-config health counters were fed only by a * slow sampled cron probe: a seat could be exhausted in live traffic and still * read as healthy, because the probe checks auth, not capacity. This is the * live signal (skaile-ai/workspaces#368, skaile-ai/platform#3539). * * Fire-and-forget: the runner never waits for it and never retries it, and a * platform with no handler for it broadcasts it harmlessly. Every field beyond * `status` is optional, and consumers MUST ignore fields they do not know — the * two halves of this signal ship independently. * * @category Events * @since 3.9.0 */ export type ProviderResponseSeenEvent = { type: "provider_response_seen"; /** 401 — the credential was rejected. 429 — an upstream rate/session limit. */ status: 401 | 429; /** Platform `AIProviderConfig.id` the session is pinned to, when known. */ configId?: string; /** Upstream limit window (`five_hour`, `seven_day`, …) when reported. */ limitType?: string; /** Fraction of the limit window consumed (0–1) when reported. */ utilization?: number; /** Epoch seconds at which the limit window resets, when reported. */ resetsAt?: number; /** ISO-8601 timestamp of the observation. */ observedAt: string; }; //# sourceMappingURL=events.d.ts.map