Stately Inspector
xstate-inspect (feature inspect) emits the @statelyai/inspect protocol (pinned to 0.7.2):
actor registration, events and snapshots. The message bytes are identical to the Python library’s
InspectorPlugin — golden recordings written by Python are compared line by line with what the
Rust observer emits for the same run.
InspectorObserver— anObserveryou attach withinterp.observe(...). Context and event payloads are deny-by-default (allow-lists plus key-based redaction).- Sinks:
MemorySinkfor in-process collection, and.jsonlrecording. SseSink— a hardened loopback SSE server that streams a live machine to the Stately Inspector page (xsm inspect --liveis built on it). Non-loopback binds require a token.- Replay:
xsm replay run.jsonl --livefeeds a recording back into the Inspector UI.
#![allow(unused)]
fn main() {
use std::sync::Arc;
use xstate_statemachine::prelude::*;
use xstate_statemachine::inspect::{InspectorObserver, MemorySink};
let machine = Machine::from_json(r#"{"id":"m","initial":"a","states":{"a":{"on":{"GO":"b"}},"b":{}}}"#)?;
let sink = Arc::new(MemorySink::new());
let mut interp = SyncInterpreter::new(machine, MachineLogic::dynamic());
interp.observe(Arc::new(InspectorObserver::with_shared_sink(sink.clone())));
interp.start()?;
interp.send("GO")?;
assert_eq!(sink.messages().len(), 5); // actor, event, snapshot, event, snapshot
Ok::<(), Box<dyn std::error::Error>>(())
}
The axum integration also serves the Inspector over a token-gated WebSocket endpoint; see
HTTP integration (axum) and the CLI (xsm inspect --live, xsm replay). The
security model (loopback binds, Host checks, token comparison) is on the
security hardening page.