Skip to main content

preview

The preview module is Digitorn's universal live-canvas transport layer. Agents push state, named resources, and canvas nodes to a per-session stream the client consumes via Socket.IO. Powers zero-code live previews for builder apps, React sandboxes, slides, timelines, YAML panels, and any workflow editor / multi-agent orchestrator.

PropertyValue
Module idpreview
Action count17 (all internal=True)
LLM-visible actions0 - every action is internal
Callerworkspace module (Python-direct), widget module
TransportSocket.IO (room session:{id})
IsolationPer-session via PreviewSessionStore

Architecture

The agent never calls preview.* directly - every action is internal=True and stripped from the tool schema sent to the LLM. The workspace module (and any other shell-layer module) calls preview as Python methods via its injected self._preview reference:

await self._preview.set_resource(SetResourceParams(
channel="files",
id="src/App.tsx",
payload={"content": "...", "language": "tsx"},
))

Per-session isolation handled by PreviewSessionStore - each session_id gets its own PreviewSessionState with independent state, resources, events ring buffer, and monotonic seq counter. On reconnect, the client receives a full preview:snapshot replay then resumes on live events.

The 17 internal actions

State map (5)

ActionParamsPurpose
set_statekey, valueUpsert one scalar.
patch_statepatch: dictMerge fields into the state map.
get_state-Return full snapshot (state + resources).
clear-Wipe state + resources + events.
emitevent_type, dataPush a free-form event.

Named resources (6)

Generic channel primitive - channels are dicts keyed by id with arbitrary JSON-serialisable payloads (files, slides, cells, nodes, edges, ...).

ActionParamsPurpose
set_resourcechannel, id, payloadUpsert.
patch_resourcechannel, id, patchMerge fields; create if absent.
delete_resourcechannel, idRemove one.
list_resourceschannelDump every id + payload.
bulk_set_resourceschannel, items: dict[id, payload], replace: bool=FalseSnapshot import.
clear_channelchannelDrop every resource in a channel.

ReactFlow canvas (6)

Thin wrappers over set_resource("nodes", ...) / set_resource("edges", ...).

ActionParamsPurpose
push_nodeid?, type="default", label="", position={x, y}, data={}, status="idle"Add / replace a canvas node.
update_nodeid, updates: dictPartial update. Unknown keys merge into data.
highlight_nodeid, status: idle|running|done|errorShortcut to set node status.
remove_nodeidDrop the node + any touching edges.
push_edgeid?, source, target, label="", data={}Add / replace an edge.
remove_edgeidDrop one edge.

When push_node.id is omitted it's auto-derived by slugifying label (fallback node-{N+1}). Edge ids default to "{source}->{target}".

Socket.IO event types

Every mutation appends a PreviewEvent to the session's ring buffer with an incrementing seq, then publishes on the bus as:

{ "type": "preview:<event_type>", "data": { ...payload, "preview_seq": 42 } }
EventEmitted by
preview:state_changedset_state.
preview:state_patchedpatch_state.
preview:clearedclear.
preview:resource_setset_resource, push_node, push_edge.
preview:resource_patchedpatch_resource, update_node, highlight_node.
preview:resource_deleteddelete_resource, remove_node, remove_edge.
preview:resource_bulk_setbulk_set_resources.
preview:channel_clearedclear_channel.
preview:snapshotServer → client on join_session (replay).

Clients use the monotonic preview_seq to reconcile after a reconnect: ask for the snapshot first, then drop any live event whose preview_seq <= snapshot.seq.

Configuration

The preview module is pure plumbing - no user-facing config.

tools:
modules:
preview: {} # just enable it; no config

Two attributes are wired in by the daemon bootstrap:

  • preview._event_bus - the SocketIOBus used to publish events.
  • preview._bus_app_id - the app id used as the bus routing key.

If either is missing (dev / tests without bus), events are logged + dropped with a preview_event_dropped warning.

Session isolation

HookBehaviour
set_active_session(sid, uid)Called by the agent loop before each tool dispatch - binds the next action to this session.
_sessionResolves to PreviewSessionState via the store; creates one on demand.
cleanup_session(sid)Drops all state for a session (on session end).
snapshot_for(sid, uid)Returns the replay payload used by the Socket.IO join_session handler.

When no active session has been set (dev / tests), a synthetic _default_ session is used. In production this never happens - the agent loop always sets the active session before dispatching.

Integration notes

  • Agents never see these tools. All 17 actions are internal=True - the schema is never shipped to the LLM. Agents manipulate the preview indirectly through workspace.* (WsWrite, WsRead, WsEdit, WsGlob, WsGrep, WsDelete).
  • No SSE. All streaming moved to Socket.IO; there are no fallback HTTP / SSE paths.
  • No legacy workbench. Every live UI now uses this module.

Cross-references