feat(serial): cursor-based reads and wait_for - #26
Merged
Conversation
Serial reads were a lossy snapshot: read() returned the tail of a rolling
buffer, so repeated reads overlapped, anything beyond the 512KB cap vanished
silently, and capturing a long run meant polling and hoping the window never
slid. There was also no way to wait for expected output - agents polled for
that too.
Reads are now cursor-based. Every read/wait_for response carries a global
`cursor`; passing it back as `since` returns the FIRST max_lines complete
lines at or after that offset plus a new cursor just past them - lossless
paging. Buffer truncation and clear() advance a bufferStartOffset instead of
destroying position, so a stale cursor reports `dropped: <chars>` rather than
silently mapping onto unrelated output. `has_more` says more complete lines
are already buffered; a trailing partial line is held back until its newline
arrives. Plain read() without since is unchanged (tail snapshot) apart from
the new fields.
New wait_for action: blocks until a line matching a substring (or regex with
is_regex:true) arrives, with an optional `since` so already-buffered output
is scanned first - output that arrived between calls cannot be missed.
Resolves with {matched, line, cursor, elapsed_ms}; a timeout or a dropped
connection resolves (not errors) with timed_out/disconnected flags. Timeout
defaults to 30s, clamped 1-120 - deliberately lower than task waits since it
holds an MCP request open. Waiters are flushed on disconnect(), ws close and
ws error, so none can leak.
The line scanner (processChunk/handleCompleteLine) assembles complete lines
across chunk boundaries with a carried remainder; crash-signature detection
will plug into the same funnel next.
Verified on the ESP32-S3: paged reads returned exactly t:3..t:7 with
dropped:0 and no overlap; wait_for matched "idle tempC" in 535ms; a
never-matching pattern timed out cleanly with a cursor; a pre-clear cursor
reported dropped:396 and clean lines; disconnecting mid-wait resolved
disconnected:true.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Second of the v0.6.0 series (pain point 2: lossy snapshot serial reads).
What changed
Cursor-based paging. Every
read/wait_forresponse carries a globalcursor; pass it back assincefor the next lines after that offset — lossless. Truncation andclearadvance an offset instead of destroying position, so a stale cursor reportsdropped: <chars>instead of silently returning the wrong window.has_moreflags buffered backlog; trailing partial lines are held back until complete.wait_for— theserial_expectprimitive competitors ship. Blocks until a line matches a substring/regex;sincescans already-buffered output first so nothing between calls is missed. Resolves (never errors) withmatched/line/cursor/elapsed_ms,timed_out, ordisconnected. Timeout 30s default, clamp 1–120. Waiters flush on disconnect, ws close, and ws error — no leaks.The new line scanner is the shared funnel that crash-signature detection (PR3) plugs into.
Backward compatible: plain
readis the same tail snapshot plus new fields.Verified on hardware (ESP32-S3)
t:3..t:7— consecutive,dropped: 0, no overlapwait_for "idle tempC"matched in 535 mstimed_out: true+ cursorclearcursor →dropped: 396, clean lines, no garbagedisconnected: true🤖 Generated with Claude Code
EOF