Skip to content

feat(server): per-spawn PTY stream epoch for authoritative rewind detection #151

Description

@0xabstracted

Context

Byte-offset scrollback resume (#136) makes the client resume from a raw byte offset it snaps to
the server's attached.next. When a session's PTY is respawned in place (PtyHostService
replaces the entry: kill(sessionId, false) with notify=false, then installs a fresh
OutputBuffer starting at offset 0), a reconnecting client detects the rewind because the new
attached.base is below the offset it already consumed, and resets its terminal.

Problem

Rewind detection is currently inferred from offset arithmetic (base < prevReceived). That works
for the common case but is fragile at the boundaries: a brand-new stream that happens to have
produced enough bytes, or a client whose consumed offset is coincidentally near a new stream's
base, can confuse "same stream, resumed" with "new stream, rewound." There is no explicit
identity for which PTY stream an offset belongs to.

Proposal

Give each PTY spawn a monotonic stream epoch (increment on every spawn/respawn for a session).
Carry it in the attached control frame alongside the offsets. The client treats
epoch !== lastEpoch as an authoritative "different stream — reset and resume from this epoch's
next," independent of offset comparison. Offsets remain per-epoch raw byte counts.

Acceptance criteria

  • PtyHostService assigns a monotonically increasing epoch per session spawn/respawn, exposed
    via getReplay / the attach path.
  • attached frame carries epoch; ptyProtocol.parseControlFrame parses it
    (back-compatible: absent epoch behaves as today).
  • Client resets terminal + decoder + offset when the epoch changes, regardless of base/offset
    arithmetic; a same-epoch reconnect never resets purely due to offset coincidence.
  • Tests: respawn increments epoch and forces a client reset; a same-epoch reconnect with a
    retained offset resumes without reset; an absent-epoch server still works (back-compat).

Refs

Builds on #136 (attached{base,gap,next,hasReplay} handshake). Files:
maestro-server/src/application/services/PtyHostService.ts,
maestro-server/src/infrastructure/websocket/PtyWebSocketServer.ts,
maestro-ui/src/platform/ptyProtocol.ts, maestro-ui/src/platform/terminal.ts.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions