Class BowireRecordingSession
- Namespace
- Kuestenlogik.Bowire.Recording
- Assembly
- Kuestenlogik.Bowire.dll
Server-side active-recording lifecycle (#285). Lives in core as a
singleton so multiple consumers — the workbench HTTP surface, the
MCP tool surface (bowire.record.start / stop / replay), and
the in-process invoke / channel pipelines that need to know "is there
a recording open right now?" — share one source of truth instead of
the pre-#285 browser-localStorage-as-truth pattern.
public sealed class BowireRecordingSession
- Inheritance
-
BowireRecordingSession
- Inherited Members
Remarks
Pre-#285 the active-recording state lived in the browser
(bowire_recording_active, bowire_recording_buffer); the
MCP server had no way to reach it, so the
bowire.record.start / stop / replay tools shipped as deferred
no-ops. Lifting the state into this singleton unblocks the tools
without forcing the workbench to give up its localStorage cache for
completed recordings (the bowire_recordings bucket stays — it's
a flush sink, not the source of truth).
State machine: Idle → Active (Capture | Proxy | Replay) → Idle.
Start(string, BowireRecordingMode, string?, string?) transitions Idle → Active and rejects if a session
is already open (callers must Stop(Func<BowireRecording, BowireRecording>?) first). Stop(Func<BowireRecording, BowireRecording>?)
flushes the buffer to the supplied sink and returns the persisted
recording. SwitchToReplay() flips the mode in-place without
dropping the buffer.
Subscribers (Subscribe()) receive a
ChannelReader<T> of BowireRecordingSessionEvent;
the SSE endpoint translates each event to a text/event-stream
frame for the workbench's recorder UI. The channel is bounded with
drop-oldest semantics — a wedged subscriber can't grow memory
unbounded, and the workbench is interested in "latest state" not "every
transition since it last polled".
Constructors
BowireRecordingSession()
public BowireRecordingSession()
Fields
SubscriberChannelCapacity
Bounded subscriber channel cap. 64 transitions is enough headroom for the slowest browser tab to drain while a CLI agent rapidly starts / stops; DropOldest matches "the UI cares about the latest session state", not "the UI needs every historical transition".
public const int SubscriberChannelCapacity = 64
Field Value
Properties
Active
Currently-active session, or null when the recorder is idle.
Returns an immutable snapshot — callers can inspect without holding
the gate. Mutating tools (AppendStep(BowireRecordingStep),
Stop(Func<BowireRecording, BowireRecording>?)) go through the dedicated methods so the gate
is held for the transition.
public BowireRecordingSessionState? Active { get; }
Property Value
Methods
AppendStep(BowireRecordingStep)
Append a captured step to the active session's buffer. Returns the step's 0-based index. Throws when no session is active.
public int AppendStep(BowireRecordingStep step)
Parameters
stepBowireRecordingStep
Returns
Start(string, BowireRecordingMode, string?, string?)
Start a new recording session. Returns the freshly-created snapshot. Throws when a session is already open — callers must Stop(Func<BowireRecording, BowireRecording>?) or SwitchToReplay() first.
public BowireRecordingSessionState Start(string workspaceId, BowireRecordingMode mode, string? name = null, string? recordingId = null)
Parameters
workspaceIdstringWorkspace the recording lives under. Empty string is accepted (legacy unscoped workspaces), but the caller should prefer the active workspace id so multi-workspace operators don't see cross-leakage.
modeBowireRecordingModeOne of Capture (record live invocations), Proxy (record proxied flows), Replay (drive replay from a pre-existing recording).
namestringOptional human-readable name. Defaults to
"Untitled recording"so the UI always has something to show.recordingIdstringOptional caller-supplied id (used by replay to bind to an existing recording). When null, a fresh
rec_*id is generated.
Returns
Stop(Func<BowireRecording, BowireRecording>?)
Stop the active session and hand the assembled recording to the
supplied flush sink. The sink's return value is propagated back so
the caller (REST endpoint, MCP tool) can surface the persisted
recording id. When flush is null the recording
is returned without being persisted — useful for replay sessions
that don't want a new entry in the recording store.
public BowireRecording? Stop(Func<BowireRecording, BowireRecording>? flush = null)
Parameters
flushFunc<BowireRecording, BowireRecording>
Returns
- BowireRecording
The final BowireRecording the session produced.
nullonly if no session was active in the first place (idempotent stop).
Subscribe()
Subscribe to session-state-change events. Returns the channel reader the SSE producer streams from and an IDisposable that, when disposed, unsubscribes. Each subscriber gets a private bounded channel so a slow browser tab can't back-pressure other subscribers or the producer.
public (ChannelReader<BowireRecordingSessionEvent> Reader, IDisposable Subscription) Subscribe()
Returns
SwitchToReplay()
Flip the active session into Replay. Buffer is preserved (replay reads from it). Throws when no session is active.
public BowireRecordingSessionState SwitchToReplay()