Documentation
¶
Overview ¶
Package ws implements the daemon's binary WebSocket protocol for PTY input, output, resizing, state updates, and bounded reconnect replay. The public wire contract is documented in docs/protocol.md.
Index ¶
- Constants
- func AuthGate(ctx context.Context, c *websocket.Conn) error
- func DecodeDeltaRequest(payload []byte) (epoch, resumeOffset uint64, ok bool)
- func DecodeResize(payload []byte) (rows, cols uint16, ok bool)
- func DeflateRaw(src []byte) []byte
- func EncodeControlState(hasDriver bool, rows, cols uint16) []byte
- func EncodeDeltaEnd(epoch, totalWritten uint64) []byte
- func EncodeFrame(t byte, payload []byte) []byte
- func EncodeReplayEnd(epoch, totalWritten uint64) []byte
- func InflateRaw(src []byte) ([]byte, error)
- type Handler
Constants ¶
const ( MsgInput byte = 0x00 // C→S: raw PTY input bytes MsgOutput byte = 0x00 // S→C: raw PTY output bytes MsgResize byte = 0x01 // C→S: JSON {"cols":N,"rows":N} MsgTitle byte = 0x01 // S→C: UTF-8 OSC window title MsgReplayStart byte = 0x02 // S→C: replay bracket start (uncompressed) MsgReplayEnd byte = 0x03 // S→C: replay end + delta anchor [u64 epoch BE][u64 totalWritten BE] MsgSessionsUpdate byte = 0x04 // S→C: JSON live sessions list MsgNotify byte = 0x05 // S→C: JSON done-for-you alert MsgUnifiedUpdate byte = 0x06 // S→C: JSON unified sessions list MsgAuth byte = 0x07 // C→S: raw bearer token bytes (first frame only) MsgClaudeState byte = 0x08 // S→C: JSON per-tab attention state MsgDeltaRequest byte = 0x09 // C→S: [u64 epoch BE][u64 resumeOffset BE] MsgDeltaStart byte = 0x0A // S→C: delta bracket start MsgDeltaEnd byte = 0x0B // S→C: delta end + new anchor [u64 epoch BE][u64 totalWritten BE] MsgClearTerminal byte = 0x0C // S→C: clear screen+scrollback (agent /clear) MsgReplayStartCompressed byte = 0x0D // S→C: replay bracket start (raw-deflate) // 0x0E/0x0F are reserved-deferred for the attachment-lease feature — avoid. MsgControlState byte = 0x10 // S→C: JSON {"hasDriver":bool,"cols":N,"rows":N} — current PTY driver (Option D "tap to drive") )
MSG_* frame-type bytes. The first byte of every WS binary frame is one of these; the rest is the type-specific payload. Values are wire contracts and must not be renumbered.
Note 0x00 and 0x01 are reused for input/output and resize/title respectively — direction (C→S vs S→C) disambiguates, never both on the same side.
Variables ¶
This section is empty.
Functions ¶
func AuthGate ¶
AuthGate reads one frame from c and verifies it is MsgAuth + the correct bearer. On success returns nil — the caller then drops all read timeouts and arms replay + the I/O loop. On failure closes c and returns a descriptive error for the daemon log (the close reason sent to the peer is uniform "unauthorized" regardless of failure mode).
The passed context is the ACCEPT-time request context; AuthGate wraps it in a 5 s timeout for the auth read only.
func DecodeDeltaRequest ¶
DecodeDeltaRequest parses the payload of a MsgDeltaRequest frame (the 16 bytes AFTER the type byte). Returns ok=false if the payload is not exactly 16 bytes — iOS always sends exactly epoch+offset, so any other length is a protocol error and the handler falls back to full replay.
func DecodeResize ¶
DecodeResize parses the JSON payload of a MsgResize frame. Returns ok=false on short/long/malformed JSON. The handler ignores a bad resize rather than dropping the connection — a malformed resize must not kill an otherwise healthy session.
func DeflateRaw ¶
DeflateRaw compresses src into a fresh byte slice using raw deflate. Returns nil when:
- src is below the compression threshold (caller sends uncompressed), OR
- compression didn't shrink src (incompressible data — caller sends uncompressed so iOS's flate path isn't taken at all).
The returned slice is safe to retain; the pool's Writer is reset before next use.
func EncodeControlState ¶
EncodeControlState builds the MsgControlState[JSON] frame. The JSON payload is the stable control-state payload consumed by compatible clients.
func EncodeDeltaEnd ¶
EncodeDeltaEnd builds the MsgDeltaEnd[epoch BE][offset BE] frame. Same layout as replay-end; distinct type byte so iOS knows the bytes it just received were a delta, not a full snapshot.
func EncodeFrame ¶
EncodeFrame returns a single binary frame: [type byte][payload]. payload may be nil for type-only frames (replay/delta start).
func EncodeReplayEnd ¶
EncodeReplayEnd builds the MsgReplayEnd[epoch BE][offset BE] frame. iOS uses these two values as its delta anchor for the next reconnect.
func InflateRaw ¶
InflateRaw is the inverse of deflateRaw. Production never decompresses its own output, but tests use it to verify round-trip parity with what iOS will decompress, and a future debug tool could call it to inspect a captured compressed replay. Exported for those callers.
Types ¶
type Handler ¶
type Handler struct {
// contains filtered or unexported fields
}
Handler is the WebSocket endpoint. One instance serves all /ws/{id} requests; per-connection state lives in the ServeHTTP goroutine + the reader/writer/pinger children it spawns.
func NewHandler ¶
NewHandler builds the WS handler. epoch is the daemon-start epoch used in replay/delta end-frames; iOS anchors against it to detect daemon restarts.
func (*Handler) ServeHTTP ¶
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP upgrades to WS, authenticates, and runs the per-connection loop. The HTTP layer does NOT auth-gate this route (it's bare on the mux like /api/health) — auth happens in the WS first frame so the bearer never appears in URL/proxy logs and the upgrade response itself leaks nothing.