///|
/// One event on the engine's stdout JSONL stream — the wire contract between
/// `openseek run`/`openseek serve` and every client that reads them (the TUI,
/// the desktop host, and any script consuming `run`'s stdout).
///
/// The stream doubles as the process log: `emit` routes through `@xlog`, whose
/// handler serializes an `Entry` per line as
/// `{"timestamp":…,"level":…,"source":…,}` — structured fields are
/// hoisted to the top level, so an event's own fields sit alongside the
/// envelope rather than nested under it.
///
/// Every variant owns its log level, so a call site cannot report the same
/// event at two severities. `emit` is the only writer; `parse` is its inverse.
pub(all) enum Event {
// ── agent loop ─────────────────────────────────────────────────────────
AgentSetupFailed(error~ : String)
AgentStep(step~ : Int)
AgentAborted(reason~ : String)
AgentFinished(answer~ : String)
MaxStepsExhausted
TurnFailed(error~ : String)
AssistantDelta(content~ : String)
/// One transient provider reasoning fragment. Consumers may render or ignore
/// these high-volume progress events; completed reasoning still emits once
/// as `ReasoningMessage` and is stored with its assistant response.
ReasoningDelta(content~ : String)
AssistantMessage(content~ : String)
ReasoningMessage(content~ : String)
Usage(usage~ : Usage)
/// `brief` is always written, `null` when the tool reported none: an absent
/// key and a null one are indistinguishable to every decoder, so the shape
/// stays uniform across the three call sites that emit a tool result.
ToolResult(
tool_call_id~ : String,
tool_name~ : String,
is_error~ : Bool,
content~ : String,
brief~ : String?
)
ToolCallDecodeError(
tool_call_id~ : String,
tool_name~ : String,
error~ : String
)
// ── approval ───────────────────────────────────────────────────────────
/// A tool is asking the controller for permission and is BLOCKED until an
/// `approval` command carrying this `id` comes back. The one event on this
/// stream that is a question rather than a report, and the only one a
/// controller MUST answer: there is no deadline behind it, so a controller
/// that reads events and never writes one leaves the turn stopped until
/// somebody cancels it.
///
/// `id` is minted by the engine and is unique within one engine process.
/// `body` is what would actually RUN, verbatim — the `mbtx` program —
/// while `detail` describes only what would be granted. A controller that
/// renders one without the other is asking someone to approve a permission
/// without showing them what it is for.
ApprovalRequested(
id~ : String,
tool_name~ : String,
detail~ : String,
body~ : String?
)
/// How a previously requested approval settled — including when it settled
/// without the controller's answer — the turn was interrupted, or a second
/// controller answered first. Purely informational: a controller uses it to
/// retire the prompt it is showing.
///
/// `outcome` is `"allowed_once"`, `"rejected"`, or `"cancelled"`. The
/// runtime's fourth outcome, `unavailable`, never appears here: it is what an
/// engine with no controller answers, and such an engine emits no request in
/// the first place.
ApprovalResolved(id~ : String, outcome~ : String)
// ── steering ───────────────────────────────────────────────────────────
SteerApplied(kind~ : String, content~ : String)
SteerDropped(content~ : String)
BackgroundNotice(content~ : String)
// ── goal ───────────────────────────────────────────────────────────────
GoalUpdated(goal~ : String?)
GoalBlocked(reason~ : String)
GoalUnblocked
GoalCheck(content~ : String)
GoalReminder(content~ : String)
GoalContinue(remaining~ : Int)
GoalBudgetExhausted(turns~ : Int)
// ── plan ───────────────────────────────────────────────────────────────
PlanReminder(content~ : String)
// ── compaction ─────────────────────────────────────────────────────────
CompactionStarted(from_sequence~ : Int, to_sequence~ : Int)
CompactionFinished(
from_sequence~ : Int,
to_sequence~ : Int,
summary~ : String
)
/// Reported at `Error` even when the cause was cancellation: no decoder
/// reads the envelope's level, so one severity per event costs nothing and
/// keeps the level a property of the event rather than of the call site.
CompactionFailed(error~ : String)
AutoCompactionStarted(from_sequence~ : Int, to_sequence~ : Int)
AutoCompactionFinished(
from_sequence~ : Int,
to_sequence~ : Int,
summary~ : String
)
AutoCompactionFailed(error~ : String)
ContextYield(to_sequence~ : Int, answer~ : String)
// ── subrun ─────────────────────────────────────────────────────────────
/// A nested sub-run (a review, an explore) began inside this run. `id` is
/// unique within the stream so overlapping sub-runs pair each start with
/// its finish; `kind` names the surface ("review", "explore"); `label` is
/// a short, DISPLAY-BOUNDED string (typically a truncated, sanitized
/// query) — never unbounded raw input.
SubrunStarted(id~ : String, kind~ : String, label~ : String)
/// The paired completion. The emitter's contract (the sub-run runner —
/// wire-first: this variant ships ahead of it): emit on success, failure,
/// AND cancellation, and BEFORE the parent turn's own terminal event (the
/// TUI's stale-run guard drops run-tagged events after a terminal), so a
/// reader never shows a sub-run as running forever. `status` is the
/// sub-run terminal in snake_case ("captured", "no_report", "max_steps",
/// "context_yield", "timed_out", "failed", "cancelled"); `steps` and the
/// token counts are the child's actual spend, for cost attribution.
SubrunFinished(
id~ : String,
status~ : String,
steps~ : Int,
prompt_tokens~ : Int,
completion_tokens~ : Int
)
// ── session / workspace ────────────────────────────────────────────────
/// `workspace_root` is optional because it postdates the event: engines
/// between d11e04c2 (which added `session_started`) and d5d0b208 (which
/// added `--dir`) emit only `session` and `session_root`. No client reads it.
SessionStarted(
session~ : String,
session_root~ : String,
workspace_root~ : String?
)
SessionError(error~ : String)
WorkspaceCreated(dir~ : String)
CommandError(error~ : String)
FleetStarted(runs~ : Int, task~ : String)
// ── mcp ────────────────────────────────────────────────────────────────
McpConfigIgnored(reason~ : String)
McpConfigUnreadable(path~ : String, error~ : String)
McpConfigInvalid(path~ : String, error~ : String)
McpToolsRegistered(servers~ : Int, tools~ : Int, names~ : Array[String])
McpToolDuplicate(server~ : String, tool~ : String)
McpToolRenamed(from~ : String, to~ : String)
McpToolsCapped(server~ : String, kept~ : Int)
McpServerSkippedOverCap(server~ : String)
/// `error` is always written, `null` when the connection produced no error
/// value (the server simply yielded nothing) — same uniformity rule as
/// `ToolResult::brief`.
McpConnectFailed(server~ : String, error~ : String?)
McpListToolsFailed(server~ : String, error~ : String)
McpListToolsTimeout(server~ : String)
McpNoTools(server~ : String)
} derive(Eq, Debug)
///|
pub extend Event with Debug::{to_repr}
///|
pub extend Event with Eq::{equal, not_equal}