Skip to content

feat(serial): cursor-based reads and wait_for - #26

Merged
mixelpixx merged 1 commit into
mainfrom
feat-serial-cursors
Jul 27, 2026
Merged

feat(serial): cursor-based reads and wait_for#26
mixelpixx merged 1 commit into
mainfrom
feat-serial-cursors

Conversation

@mixelpixx

Copy link
Copy Markdown
Owner

Second of the v0.6.0 series (pain point 2: lossy snapshot serial reads).

What changed

Cursor-based paging. Every read/wait_for response carries a global cursor; pass it back as since for the next lines after that offset — lossless. Truncation and clear advance an offset instead of destroying position, so a stale cursor reports dropped: <chars> instead of silently returning the wrong window. has_more flags buffered backlog; trailing partial lines are held back until complete.

wait_for — the serial_expect primitive competitors ship. Blocks until a line matches a substring/regex; since scans already-buffered output first so nothing between calls is missed. Resolves (never errors) with matched/line/cursor/elapsed_ms, timed_out, or disconnected. 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 read is the same tail snapshot plus new fields.

Verified on hardware (ESP32-S3)

  • Paged reads returned exactly t:3..t:7 — consecutive, dropped: 0, no overlap
  • wait_for "idle tempC" matched in 535 ms
  • Never-matching pattern → clean timed_out: true + cursor
  • Pre-clear cursor → dropped: 396, clean lines, no garbage
  • Disconnect mid-wait → disconnected: true

🤖 Generated with Claude Code
EOF

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>
@mixelpixx
mixelpixx merged commit 71b029f into main Jul 27, 2026
@mixelpixx
mixelpixx deleted the feat-serial-cursors branch July 27, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant