holdwire

package
v0.9.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package holdwire is the protocol between the daemon and a session holder: framing here, message shapes in msg.go. It is deliberately tiny and version 1 is meant to stay the only version for a long time — a holder keeps running the binary that spawned it across daemon updates, so every verb added here is a compatibility promise to every holder already alive.

Index

Constants

View Source
const (
	TJSON  byte = 1
	TChunk byte = 2
)

Frame types. Control connections carry only TJSON; an attach connection carries one TJSON header and then TChunk frames of raw session output.

View Source
const MaxFrame = 8 << 20

MaxFrame bounds a single frame's payload. The largest legitimate frame is a spawn request carrying a revived ring (2 MiB by default) inside JSON base64, so 8 MiB clears every real message while keeping a corrupt or hostile length prefix from turning into an allocation.

View Source
const Proto = 1

Proto is the protocol version a holder reports in its hello. A daemon that meets a version it does not know must surface the holder as unreachable, never guess at it.

Variables

This section is empty.

Functions

func ReadFrame

func ReadFrame(r io.Reader) (t byte, p []byte, err error)

ReadFrame reads one frame. A cleanly closed peer surfaces as io.EOF from the header read; a connection cut mid-frame is io.ErrUnexpectedEOF, which callers must treat as an error rather than a short frame.

func WriteFrame

func WriteFrame(w io.Writer, t byte, p []byte) error

WriteFrame writes one frame: type byte, big-endian uint32 length, payload.

Types

type Event

type Event struct {
	Event string    `json:"event"` // exit | title | active
	Code  int       `json:"code,omitempty"`
	At    time.Time `json:"at,omitempty"`
	Title string    `json:"title,omitempty"`
}

Event is pushed by the holder on a control connection, unprompted and without an ID. exit arrives once; title on every change; active at most once per second while output flows, so a list's LastActive ordering stays honest without a round trip per chunk.

type Hello

type Hello struct {
	Proto   int    `json:"proto"`
	Version string `json:"version"` // flue version the holder runs
	Pid     int    `json:"pid"`     // the child's pid, 0 before spawn
	// Info is a session.Info, raw for the same reason as SpawnReq.Restore.
	Info    json.RawMessage `json:"info,omitempty"`
	BaseSeq uint64          `json:"baseSeq"`
	EndSeq  uint64          `json:"endSeq"`
}

Hello is the holder's self-description: enough for a daemon that has just started to rebuild its registry entry from the socket alone.

type Req

type Req struct {
	ID   uint64 `json:"id"`
	Verb string `json:"verb"` // hello | spawn | write | resize | signal | tail | close | attach

	Spawn *SpawnReq `json:"spawn,omitempty"`
	// Data carries write payloads. encoding/json base64s []byte, which is
	// what keeps arbitrary terminal input inside a JSON frame.
	Data []byte `json:"data,omitempty"`
	Cols uint16 `json:"cols,omitempty"`
	Rows uint16 `json:"rows,omitempty"`
	// Sig is the raw signal number. Daemon and holder are always the same
	// machine and build architecture, so the number is unambiguous.
	Sig int `json:"sig,omitempty"`
	N   int `json:"n,omitempty"`
	// FromSeq is attach's resume point, Sub semantics exactly.
	FromSeq uint64 `json:"fromSeq,omitempty"`
}

Req is one request on a control connection, or the attach header on an attach connection. Requests are serialized per connection and every one is answered; ID ties the reply back when a caller pipelines.

type Resp

type Resp struct {
	ID  uint64 `json:"id"`
	Err string `json:"err,omitempty"`

	Hello *Hello `json:"hello,omitempty"`
	// Data, Cols, Rows answer tail.
	Data []byte `json:"data,omitempty"`
	Cols uint16 `json:"cols,omitempty"`
	Rows uint16 `json:"rows,omitempty"`
	// StartSeq and Truncated head an attach stream, mirroring session.Sub.
	StartSeq  uint64 `json:"startSeq,omitempty"`
	Truncated bool   `json:"truncated,omitempty"`
}

Resp answers one Req. Err is the whole error contract: empty is success, anything else is a message for the daemon's log and the caller's error.

type SpawnReq

type SpawnReq struct {
	ID       string   `json:"id"`
	Run      []string `json:"run"`
	Argv     []string `json:"argv"`
	Env      []string `json:"env"`
	Cwd      string   `json:"cwd"`
	Cols     uint16   `json:"cols"`
	Rows     uint16   `json:"rows"`
	RingSize int      `json:"ringSize,omitempty"`
	Preload  []byte   `json:"preload,omitempty"`
	// Restore is a session.Info, carried raw: this package sits below the
	// session package (which imports it from the daemon side), so it cannot
	// name the type without a cycle. Both ends marshal the same struct.
	Restore   json.RawMessage `json:"restore,omitempty"`
	Group     string          `json:"group,omitempty"`
	Ephemeral bool            `json:"ephemeral,omitempty"`
}

SpawnReq is a fully resolved spawn: the daemon has already decided the login shell, environment and working directory, and the holder execs exactly what it is told. Run is what execs; Argv is what Info reports — the same split registry.start keeps, for the same reason.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL