Managoat.Sandbox.Daytona.LogStream (managoat_sandbox v0.1.0)

Copy Markdown View Source

Streams a Daytona session command's journaled log into the Managoat.Sandbox owner-frame contract — by polling the plain HTTP log endpoint, deliberately not the websocket.

The daemon's ?follow=true websocket was measured live (v0.204.0) to be unusable as a transport: it closes the stream almost immediately, replays a nondeterministic prefix of the journal on each connect (0, 627, 2042 or 9207 bytes of a 15692-byte journal, varying per attempt), and never serves content the plain GET already returns in full. The GET is authoritative and complete, so this process fetches the whole journal on a fixed cadence and emits exactly the bytes the owner has not seen — delivered never resets, which makes the dedupe byte-exact and also gives attach its replay-from-start semantics for free (a fresh LogStream starts at zero delivered).

The journal is a single multiplexed byte stream with 3-byte channel markers (0x01 0x01 0x01 → stdout, 0x02 0x02 0x02 → stderr); demux/2 is the pure decoder.

Command end: the daemon publishes no exit code on command records, so the spawn shim writes an exit sentinel file, polled here alongside the journal. On exit: one final fetch drains the tail, then the terminal frame — exactly one, and never a fabricated exit 0. A sandbox that stops answering entirely surfaces {:error, ...} after a bounded run of consecutive failures.

Cost note: no Range support server-side, so each poll transfers the full journal — O(n²) over a turn's life. Turn output is bounded by the conversation byte budget, and typical ACP turns are tens of KB; if a provider-side Range ever appears, this is the one place to use it.

Summary

Functions

Returns a specification to start this module under a supervisor.

Split a marker-multiplexed chunk into {stream, bytes} segments.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

demux(data, current)

Split a marker-multiplexed chunk into {stream, bytes} segments.

Returns {segments, current_stream, carry}; carry holds a trailing partial marker (a suffix of 0x01 0x01 0x01 / 0x02 0x02 0x02) that must be prepended to the next chunk when decoding incrementally.

start(opts)