Documentation
¶
Overview ¶
Package sessionhost implements daemon-owned, detachable PTY sessions (the "session-host" flavor described in docs/superpowers/specs/2026-07-03-remote-ssh-design.md §1-2). Unlike a classic `agnt run` session, the PTY child here is spawned and owned by the daemon process itself, so it survives the attaching client disconnecting (SSH drop, terminal close, laptop sleep).
Concurrency mirrors the ProcessManager conventions used elsewhere in this codebase: a sync.Map registry, atomic state fields, and a single mutex reserved for the PTY resize/primary-attach arbitration that cannot be made lock-free without losing correctness.
Index ¶
- Constants
- type CreateConfig
- type ExitData
- type Frame
- type Registry
- type ReplayMarkerData
- type ResizeData
- type Session
- func (s *Session) Attach(bufferedFrames int) (ch <-chan []byte, attachID string, isPrimary bool)
- func (s *Session) AttachedCount() int
- func (s *Session) Close() error
- func (s *Session) Detach(attachID string)
- func (s *Session) Done() <-chan struct{}
- func (s *Session) ExitCode() int
- func (s *Session) IsPrimary(attachID string) bool
- func (s *Session) LastAttached() time.Time
- func (s *Session) Resize(cols, rows int) error
- func (s *Session) Status() Status
- func (s *Session) WriteStdin(data []byte) error
- type Status
Constants ¶
const DefaultScrollback = 1024 * 1024
DefaultScrollback is the default per-session ring buffer capacity (1MB), per spec §1.4. Sized 4x the existing 256KB MaxOutputBuffer default because a session-host ring holds multiplexed interactive output (agent + human + tool output), not one process's stdout/stderr.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type CreateConfig ¶
type CreateConfig struct {
Name string
ProjectPath string
Command string
Args []string
Cols int
Rows int
Env map[string]string
// ScrollbackSize overrides DefaultScrollback; <=0 uses the default.
ScrollbackSize int
}
CreateConfig configures a new session-host session. Mirrors SessionHostCreateConfig in the design spec.
type ExitData ¶
type ExitData struct {
Code int `json:"code"`
}
ExitData is the payload of an "exit" frame.
type Frame ¶
type Frame struct {
Type string `json:"type"`
Data json.RawMessage `json:"data,omitempty"`
}
Frame is the wire envelope for SESSION-HOST ATTACH, one JSON object per WriteChunk/inbound line, discriminated by Type. See spec §1.3.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry is a lock-free (sync.Map-backed) collection of session-host sessions, mirroring ProcessManager's registry conventions.
type ReplayMarkerData ¶
type ReplayMarkerData struct {
Truncated bool `json:"truncated"`
}
ReplayMarkerData is the payload of the one-time "replay-marker" frame.
type ResizeData ¶
ResizeData is the payload of a "resize" frame.
type Session ¶
type Session struct {
ID string
Name string
ProjectPath string
Command string
Args []string
StartedAt time.Time
// SessionPGID is the PTY child's own pgid (== PID, since creack/pty's
// pty.Start sets Setsid on the child). Same containment invariant as
// classic sessions, captured directly instead of over the wire — see
// spec §2.2 invariant 9.
SessionPGID int
// contains filtered or unexported fields
}
Session is a single daemon-owned PTY child plus its scrollback ring and attached-client fan-out set.
func Create ¶
func Create(cfg CreateConfig) (*Session, error)
Create spawns a new daemon-owned PTY child and starts its output-broadcast reader. The returned Session is already registered for output capture but has zero attached clients until Attach is called.
func (*Session) Attach ¶
Attach registers a new subscriber and returns:
- a channel of already-framed JSON payloads to write to the connection (starts with one replay-marker frame, then the full scrollback as one stdout frame, then live frames — spec §1.4 invariant 6),
- the attach id (used by Detach/SendInput to identify this attach),
- whether this attach is primary (first attach, or promoted after the prior primary detached — spec §1.3 invariant 3).
The caller must range over the returned channel until it closes (Detach) or the session exits.
func (*Session) AttachedCount ¶
AttachedCount returns the number of currently attached clients, for `agnt session list` / `SESSION-HOST LIST` display.
func (*Session) Close ¶
Close closes the PTY fd (does not itself kill the child — callers that want the containment guarantee use platform.KillSessionPGID with s.SessionPGID, mirroring killSessionPGID for classic sessions).
func (*Session) Detach ¶
Detach removes an attach's subscription. If it was primary, the primary slot is cleared so a future attach may claim it (spec §1.3 invariant 4 — detach never touches the PTY child).
func (*Session) Done ¶
func (s *Session) Done() <-chan struct{}
Done returns a channel that closes when the PTY child exits.
func (*Session) IsPrimary ¶
IsPrimary reports whether attachID currently holds the primary (write) slot.
func (*Session) LastAttached ¶
LastAttached returns the timestamp of the most recent Attach call, or the zero time if never attached.
func (*Session) Resize ¶
Resize changes the PTY window size for all current attaches (spec §1.3 invariant 8 — no partial re-render, client owns redraw).
func (*Session) WriteStdin ¶
WriteStdin writes raw bytes to the PTY child's stdin. Callers must check IsPrimary first — non-primary stdin is rejected at the protocol layer (spec §1.3 invariant 3), not here.