Extension events, typed views, and live pipelines
Canonical Quirl project documentation synced from docs/extension-events-and-live-views.md.
Quirl 0.1 exposes a versioned, deny-unknown extension protocol without giving plugins direct terminal or mutable shell access. The machine-readable installed contract is available with:
quirl events schema --format jsonImmutable event records
Every record carries protocol_version, a strictly increasing sequence, and
one typed payload. The current event kinds cover session start/restore,
directory changes, command plans, execution progress, output, cancellation,
result, and error. QUIRL_SESSION_ID opts an invocation into an explicit
session-restore record. quirl events validate trace.json validates a complete trace without
loading an extension. Output text containing terminal control bytes is rejected.
Lua handlers register through quirl.events.subscribe with a stable name,
event kinds, a 1–250 ms deadline, and explicit capabilities. Observation is the
baseline right. Plan rewrites, environment changes, output reads, and execution
blocking are separate grants. A handler returns only declared action records;
Rust validates every action against its grants. Handlers execute in stable name
order within a plugin, while separate plugin runtimes are observed in parallel
and their actions are composed back in stable plugin order. Plan rewrite,
environment mutation, and blocking actions are applied before execution;
result annotations render separately from command output. A timeout, malformed
return, or denied action becomes that handler's diagnostic and does not prevent
later handlers from running.
The interactive host runs callbacks on four persistent workers plus one deadline monitor; plugin count never creates threads. One event turn admits at most 16 loaded trusted-Lua runtimes and 64 handlers through a 64-job queue. The 250 ms aggregate deadline starts when the batch is submitted, expired or stale work is cancelled, and completed actions are composed in stable plugin order. Queue saturation and aggregate timeout are resource-limit diagnostics. Before process, terminal, job, recovery, or persistence transitions, Quirl cancels background callback work and requires it to quiesce at a bounded safe point.
quirl.events.subscribe {
name = "deployment_guard",
events = { "command_plan", "result" },
capabilities = { "events_observe" },
deadline_ms = 20,
observe = function(event)
return { { action = "diagnose", message = "observed " .. event.data.kind } }
end,
}Output payload text is redacted unless the handler has output.read. Extension
registrations are capability-gated by the plugin lock; declaring a right in the
handler does not create that right.
Contributions and terminal safety
quirl.extension.contribute registers the Phase 3 surfaces composed today:
catalog, completion, or panel providers. Every registration has a bounded
deadline and a kind-specific permission (catalog.register,
completion.register, or ui.panel). Names collide only within the same
contribution kind. Panel providers must declare a non-empty plain-text
fallback. Catalog results use complete CommandSpec records and cannot shadow
installed paths or IDs; completion items and panels use deny-unknown shapes.
Plugins return typed values or plain text, never terminal paint. The core and UI models reject ESC, CSI, NUL, and other raw control bytes. Quirl retains ownership of layout, color, focus, accessibility, and terminal cleanup.
Prompt callbacks use the same bounded worker pool. First paint reads only the last completed snapshot and starts a newest-generation refresh; it never waits for Lua. A refresh retains at most 64 segments and 256 KiB, keeps the prior snapshot when a provider fails or misses the 100 ms aggregate deadline, and discards results from replaced plugin or prompt generations.
Line-oriented panels
The initial directory and process panels are useful without a full-screen TUI:
quirl view directory .
quirl view directory . --format json
quirl view processes
quirl view panel clusterText output is a stable line-oriented table. JSON returns the same validated panel model. Empty models render their required plain fallback.
On the rich interactive path, panel callbacks are refreshed asynchronously on
the existing fixed worker pool. First paint uses only the last complete cache;
rendering never invokes Lua. The host publishes a complete monotonic snapshot,
preserves the prior per-plugin model on failure or timeout, and publishes an
empty replacement when a provider is removed. The UI accepts at most eight
panels, 16 columns and 128 rows per panel, 4 KiB fields, and 128 KiB of text per
generation. It applies no more than eight updates per event-loop turn, rejects
stale generations, retains four LiveBuffer generations, and shows at most six
rows for the focused panel.
Bounded live pipelines
quirl watch repeatedly evaluates a native typed data expression. Sampling is
finite by default, Ctrl-C cancels between pipeline stages and during refresh
waits, intervals are bounded, and completed samples use a capacity-limited
retention queue. When completed samples exceed retention capacity, the oldest
samples are dropped and the snapshot reports the count. This is bounded sample
history, not producer/consumer stream backpressure.
quirl watch pwd --samples 3 --interval-ms 250
quirl watch 'ls . | length' --samples 20 --capacity 5 --format jsonText mode emits one accessible JSON value per line. JSON mode emits the bounded
snapshot with capacity, dropped, cancelled, and ordered samples fields.