pty

package
v0.70.2 Latest Latest
Warning

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

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Index

Constants

View Source
const (
	TerminalBackendUnavailable      = "unavailable"
	TerminalBackendLibghosttyHelper = "libghostty-helper"
)

Stable terminal-screen backend identifiers exposed through daemon diagnostics and lifecycle logs. Keep these values independent of concrete implementation type names so operators can rely on them across releases.

Variables

This section is empty.

Functions

func ClosePinnedTerminalExecutable added in v0.70.0

func ClosePinnedTerminalExecutable()

func DrainTransferredPTY added in v0.70.0

func DrainTransferredPTY(ptmx *os.File, scrollback *Scrollback) error

DrainTransferredPTY performs the bounded final drain for a daemon handoff rejection before the exact inherited descriptors are closed.

func PreparePinnedTerminalExecutable added in v0.70.0

func PreparePinnedTerminalExecutable() error

func ProbeTerminalAdoption added in v0.70.0

func ProbeTerminalAdoption() (int, bool)

func ProcessStartTime added in v0.48.0

func ProcessStartTime(pid int) (int64, error)

ProcessStartTime returns a value that uniquely identifies the process with the given PID (clock ticks since boot from /proc/[pid]/stat field 22). If the PID is recycled, the new process will have a different start time.

func RecoverTerminalSessionsAfterUpgrade added in v0.70.0

func RecoverTerminalSessionsAfterUpgrade(ctx context.Context, sessions []*Session) []error

RecoverTerminalSessionsAfterUpgrade owns a concurrent recovery generation. It joins every started session operation before returning, so cancellation cannot strand helper factories or candidate terminals in detached goroutines.

func ReleasePinnedTerminalExecutablePathForExec added in v0.70.0

func ReleasePinnedTerminalExecutablePathForExec() error

func RestorePinnedTerminalExecutableAfterExec added in v0.70.0

func RestorePinnedTerminalExecutableAfterExec() error

func RunNativeTerminalSelfTest added in v0.70.0

func RunNativeTerminalSelfTest() (returnErr error)

RunNativeTerminalSelfTest exercises the native backend through the exact process-isolated path used by the daemon. Release validation invokes this on the unpacked executable so architecture, helper startup, FFI calls, terminal mutation, snapshot decoding, resize, close, and child reaping are all proven against the final bytes rather than a separate test binary.

func TailFile added in v0.64.5

func TailFile(path string, lines int) ([]byte, error)

TailFile returns the last `lines` lines from a scrollback file on disk without a live Scrollback. It is used to read logs for stopped sessions whose live PTY has already been torn down. A missing file is returned as an error; an existing but empty file returns (nil, nil).

func TerminalAdoptionCapacity added in v0.70.0

func TerminalAdoptionCapacity() (int, bool)

func TerminalBackend added in v0.70.0

func TerminalBackend() string

func ThawTerminalHelpers added in v0.70.0

func ThawTerminalHelpers()

func ValidateTransferredScrollbackFD added in v0.70.0

func ValidateTransferredScrollbackFD(fd uintptr) error

ValidateTransferredScrollbackFD checks a handoff descriptor without taking ownership. The daemon uses it before atomically moving the PTY/log pair out of its post-exec ownership guard.

Types

type AdoptOpts

type AdoptOpts struct {
	ID           string
	Fd           uintptr
	ScrollbackFd uintptr
	PID          int
	// ExpectedPIDStartTime binds adoption to the process identity captured by
	// the old daemon. Zero is accepted only for a legacy helper-free manifest.
	ExpectedPIDStartTime int64
	LogPath              string
	MaxLogSize           int64
	// DefaultRows / DefaultCols are the geometry used when the adopted ptmx size
	// can't be read. Non-positive falls back to the built-in 24x80 (the daemon
	// passes the [lifecycle] default_rows/default_cols policy).
	DefaultRows, DefaultCols uint16
	// HydrationBytes is how much of the scrollback tail is replayed into the
	// adopted session's virtual screen. Negative falls back to the built-in
	// default; 0 disables hydration (the daemon passes the [lifecycle]
	// scrollback_hydration_bytes policy).
	HydrationBytes int
	// PollTimeout / PollInterval bound the adopted-PTY babysit loop. Non-positive
	// falls back to the built-in defaults (adoptedPollTimeout / one second).
	PollTimeout  time.Duration
	PollInterval time.Duration
	// Logger routes this session's diagnostics to the daemon's logger. Nil
	// falls back to slog.Default().
	Logger *slog.Logger
	// DegradedScreen skips synchronous derived-screen construction while still
	// adopting the PTY and starting raw-output drainage. Later screen access or
	// output retries the ordinary backend through screenFactory.
	DegradedScreen bool
	// DeferWait leaves exact-child reaping dormant until StartAdoptedWaiter.
	// Upgrade adoption uses this to finish all rejection-prone manager checks
	// before any waiter can consume the leader status needed by cleanup.
	DeferWait bool
	// contains filtered or unexported fields
}

type Cell added in v0.69.0

type Cell struct {
	Content string
	Style   CellStyle
}

Cell is a single rendered terminal cell. Content is the cell's grapheme cluster (a single rune most of the time, but possibly a wide character, emoji, or base+combining sequence). An empty Content marks the trailing column of a wide (double-width) cell and renders as nothing, since the wide grapheme in the preceding column already occupies the space.

type CellStyle added in v0.69.0

type CellStyle struct {
	FG            Color
	BG            Color
	Bold          bool
	Faint         bool
	Italic        bool
	Underline     bool
	Blink         bool
	Reverse       bool
	Strikethrough bool
}

CellStyle is the visual style of a rendered cell, split out from Cell so the renderer can compare adjacent cells' styles with a single struct equality and only re-emit an SGR sequence when the style changes. All fields are comparable, and the zero value is the terminal's default style.

type Color added in v0.69.0

type Color struct {
	Kind  ColorKind
	Value uint32
}

Color is a backend-neutral terminal cell color. The zero value is the terminal default color, which keeps a freshly rendered cell equal to the unstyled default (see render.go's run-length SGR suppression).

type ColorKind added in v0.69.0

type ColorKind uint8

ColorKind identifies how a Color's Value should be interpreted.

const (
	// ColorDefault is the terminal's default foreground/background — the
	// renderer emits no explicit SGR color for it.
	ColorDefault ColorKind = iota
	// ColorIndexed is an ANSI palette index: 0-7 basic, 8-15 bright, 16-255
	// the 256-color cube/greyscale.
	ColorIndexed
	// ColorRGB is a 24-bit true color packed as 0xRRGGBB in Value.
	ColorRGB
)

type HelperProcessIdentity added in v0.70.0

type HelperProcessIdentity struct {
	PID       int
	StartTime int64
}

HelperProcessIdentity identifies a native terminal helper across an exec. StartTime is mandatory so a non-zombie PID is never signalled after reuse.

func FreezeTerminalHelpers added in v0.70.0

func FreezeTerminalHelpers(context.Context) ([]HelperProcessIdentity, error)

FreezeTerminalHelpers is a no-op when this binary cannot launch native helpers. It shares the daemon upgrade path without exposing the backend.

type ScreenCapture added in v0.2.0

type ScreenCapture struct {
	Frame         string
	CursorX       int
	CursorY       int
	CursorVisible bool
	Cols          int
	Rows          int
}

type Scrollback

type Scrollback struct {
	// contains filtered or unexported fields
}

Scrollback is an append-only log file. Live reads use the exact open inode; path is retained for reversible pre-exec identity validation and deletion.

func AdoptScrollback added in v0.70.0

func AdoptScrollback(fd uintptr, path string, maxSize int64) (*Scrollback, error)

AdoptScrollback takes ownership of an inherited append writer without reopening its pathname. Path identity is validated reversibly before exec; after exec the inherited inode remains authoritative if the path changes.

func NewScrollback

func NewScrollback(path string, maxSize int64) (*Scrollback, error)

func (*Scrollback) Close

func (s *Scrollback) Close() error

func (*Scrollback) DuplicateFD added in v0.70.0

func (s *Scrollback) DuplicateFD() (int, error)

DuplicateFD returns a close-on-exec duplicate owned by the caller.

func (*Scrollback) Remove

func (s *Scrollback) Remove() error

func (*Scrollback) SetLogger added in v0.67.7

func (s *Scrollback) SetLogger(log *slog.Logger)

SetLogger routes the scrollback writer's diagnostics to the daemon's logger. The daemon never calls slog.SetDefault and runs with stderr sent to /dev/null, so without this the writer's logs would be discarded (issue #1087). A nil logger is ignored (keeps the slog.Default() fallback set at construction).

func (*Scrollback) Stats added in v0.28.0

func (s *Scrollback) Stats() (written, maxSize int64, saturated bool)

func (*Scrollback) Tail

func (s *Scrollback) Tail(lines int) ([]byte, error)

func (*Scrollback) TailBytes added in v0.6.1

func (s *Scrollback) TailBytes(maxBytes int64) ([]byte, error)

TailBytes returns up to maxBytes from the end of the scrollback file.

func (*Scrollback) ValidatePathIdentity added in v0.70.0

func (s *Scrollback) ValidatePathIdentity() error

ValidatePathIdentity proves the configured pathname still names this exact private writer. Upgrade calls it before exec, where refusal is reversible.

func (*Scrollback) Write

func (s *Scrollback) Write(data []byte) (int, error)

type Session

type Session struct {
	ID         string
	Cmd        *exec.Cmd
	Ptmx       *os.File
	Scrollback *Scrollback
	// contains filtered or unexported fields
}

func AdoptSession

func AdoptSession(opts AdoptOpts) (*Session, error)

func AdoptSessionWithTerminalFactory added in v0.70.0

func AdoptSessionWithTerminalFactory(
	opts AdoptOpts,
	factory func(cols, rows int) (Terminal, error),
) (*Session, error)

AdoptSessionWithTerminalFactory adopts a PTY with an explicitly supplied terminal factory. Production callers should use AdoptSession; this narrow seam lets package-level tests exercise adoption and recovery independently of the selected production backend.

func NewSession

func NewSession(opts SessionOpts) (*Session, error)

func NewSessionWithTerminalFactory added in v0.70.0

func NewSessionWithTerminalFactory(
	opts SessionOpts,
	factory func(cols, rows int) (Terminal, error),
) (*Session, error)

NewSessionWithTerminalFactory creates a PTY session with an explicitly supplied terminal factory. Production callers should use NewSession; this narrow seam lets package-level tests exercise PTY and daemon lifecycle behavior without selecting a production terminal backend.

func (*Session) Attach

func (s *Session) Attach(w io.Writer)

func (*Session) BytesRead added in v0.67.7

func (s *Session) BytesRead() int64

BytesRead returns the total number of PTY output bytes read this session lifetime. Zero on a running session that has been up for a while is the signature of a silent agent (issue #1087) — alive but rendering nothing.

func (*Session) Close

func (s *Session) Close()

func (*Session) CreatedAt added in v0.67.7

func (s *Session) CreatedAt() time.Time

CreatedAt returns when this PTY session object was constructed (spawned or adopted). Used to age a silent session for the zero-output diagnostic.

func (*Session) Detach

func (s *Session) Detach()

func (*Session) DetachWriter

func (s *Session) DetachWriter(w io.Writer)

func (*Session) Done

func (s *Session) Done() <-chan struct{}

func (*Session) DuplicateFD added in v0.70.0

func (s *Session) DuplicateFD() (int, error)

DuplicateFD returns a close-on-exec duplicate of the PTY master. The duplicate is owned by the caller. Close serializes the original descriptor close with this operation, so a successful return cannot duplicate an already-closed descriptor or a descriptor number reused by another goroutine.

func (*Session) ExitCode

func (s *Session) ExitCode() int

func (*Session) ExitSignal added in v0.51.0

func (s *Session) ExitSignal() syscall.Signal

func (*Session) Exited

func (s *Session) Exited() bool

func (*Session) Fd

func (s *Session) Fd() uintptr

func (*Session) ForceKill

func (s *Session) ForceKill() error

func (*Session) Interrupt added in v0.66.2

func (s *Session) Interrupt(count int, delay time.Duration) error

Interrupt sends the interrupt byte (Ctrl-C, 0x03) to the PTY count times, pausing delay between successive sends. Some agent TUIs (notably Claude) ignore a single Ctrl-C and need two rapid presses to actually interrupt, so the count and delay are configurable per agent (see issue #620). A count below 1 sends once. The whole operation holds writeMu so the presses aren't interleaved with other input.

func (*Session) Kill

func (s *Session) Kill() error

func (*Session) LastOutputAt added in v0.3.0

func (s *Session) LastOutputAt() time.Time

func (*Session) NotifyUserInput added in v0.52.0

func (s *Session) NotifyUserInput()

NotifyUserInput records that the attached user just typed something. Call this from the passthrough data path, not from gr type.

func (*Session) PeakRSSBytes added in v0.51.0

func (s *Session) PeakRSSBytes() int64

func (*Session) Pgid added in v0.67.8

func (s *Session) Pgid() int

Pgid returns the process-group id graith signals on Kill/ForceKill. For graith-spawned sessions (started with Setsid, see NewSession) the child is a session/group leader and its PGID equals its PID. For adopted sessions graith did not set Setsid, so PGID may differ from the reported pid — but Kill/ ForceKill signal -pid regardless, so this remains an honest report of "the group graith signals". Logging pid+pgid together (issue #1104) makes an OS-level signal trace (e.g. `kill -0 -<pgid>`) possible from the daemon log alone. Returns 0 when the pid is unknown.

func (*Session) Poke added in v0.16.5

func (s *Session) Poke()

Poke sends SIGWINCH to the session's process group. This interrupts blocked reads and forces TUI frameworks to re-check stdin, ensuring recently written input is consumed. The child process was started with Setsid, so its PID equals its process group ID.

func (*Session) ProcessPID

func (s *Session) ProcessPID() int

func (*Session) QuiesceIOForUpgrade added in v0.70.0

func (s *Session) QuiesceIOForUpgrade(ctx context.Context) (func(), error)

QuiesceIOForUpgrade drains input and pauses the PTY reader at a lossless safe point. The returned release must be called on every path where syscall.Exec does not replace the process image.

func (*Session) RecentlyAdopted added in v0.6.1

func (s *Session) RecentlyAdopted(grace time.Duration) bool

RecentlyAdopted returns true if the session was adopted (daemon restart) within the last duration and has not yet received fresh PTY output.

func (*Session) RecoverTerminalAfterUpgrade added in v0.70.0

func (s *Session) RecoverTerminalAfterUpgrade() error

RecoverTerminalAfterUpgrade reconstructs a screen that failed while helper generation was frozen. Its daemon-owned retry loop yields between bounded, backed-off batches so a transient helper failure cannot leave an adopted session permanently blank.

func (*Session) RecoverTerminalAfterUpgradeContext added in v0.70.0

func (s *Session) RecoverTerminalAfterUpgradeContext(ctx context.Context) error

RecoverTerminalAfterUpgradeContext is the daemon-owned, cancellation-aware recovery path. Slow helper requests remain outside the session mutex so raw PTY drainage is never coupled to reconstruction.

func (*Session) RejectAdoption added in v0.70.0

func (s *Session) RejectAdoption(ctx context.Context) error

RejectAdoption terminates an adopted exact child and waits for readLoop to finish its final PTY drain before closing either transferred descriptor. It must be called before StartAdoptedWaiter: retaining the daemon's sole waiter keeps the PID reserved between the identity check and process-group signal.

func (*Session) Resize

func (s *Session) Resize(rows, cols uint16) error

func (*Session) ScreenPreview added in v0.2.0

func (s *Session) ScreenPreview() string

func (*Session) ScreenSnapshot added in v0.2.0

func (s *Session) ScreenSnapshot() ScreenCapture

func (*Session) ScrollbackFile added in v0.67.8

func (s *Session) ScrollbackFile() *Scrollback

ScrollbackFile returns the session's scrollback buffer. It exists so a SessionDriver interface can expose the scrollback via a method (interfaces can't have fields), while the exported Scrollback field stays for direct concrete use within this package.

func (*Session) SetInputDelay added in v0.69.1

func (s *Session) SetInputDelay(delay time.Duration)

SetInputDelay updates the pause WriteInputAndSubmit inserts between the typed text and the submit carriage return, so a reloaded [lifecycle] input_delay policy takes effect on a live session's next type operation without a restart (issue #1294). A non-positive delay restores the built-in typeInputDelay default, mirroring construction. The store is atomic and takes no lock, so it never blocks on (or races) an in-flight WriteInputAndSubmit: a write already past its Load runs to completion with the value it read, and the next submit observes the new delay.

func (*Session) StartAdoptedWaiter added in v0.70.0

func (s *Session) StartAdoptedWaiter()

StartAdoptedWaiter starts the adopted process waiter exactly once.

func (*Session) WaitForUserIdle added in v0.52.0

func (s *Session) WaitForUserIdle(idleTimeout, maxWait time.Duration) bool

WaitForUserIdle blocks until at least idleTimeout has elapsed since the last user keystroke, or until maxWait has elapsed (whichever comes first). Returns true if the idle condition was met, false if maxWait expired.

func (*Session) WaitForUserIdleContext added in v0.70.0

func (s *Session) WaitForUserIdleContext(ctx context.Context, idleTimeout, maxWait time.Duration) bool

WaitForUserIdleContext is the daemon-owned variant used by notification tasks. Cancellation wakes the condition wait so an upgrade/shutdown can join the complete task tree rather than waiting for the two-minute idle bound.

func (*Session) WasAdopted added in v0.67.7

func (s *Session) WasAdopted() bool

WasAdopted reports whether this session was adopted from a prior daemon (a daemon upgrade re-attaching to a surviving agent) rather than freshly spawned. An adopted PTY starts at zero bytes read even though the agent may have rendered before the upgrade, so the silent-session diagnostic can't treat its zero-output as "never rendered" (issue #1087).

func (*Session) WriteInput

func (s *Session) WriteInput(data []byte) error

func (*Session) WriteInputAndSubmit added in v0.29.0

func (s *Session) WriteInputAndSubmit(data []byte) error

WriteInputAndSubmit writes text followed by a carriage return, with a brief pause between the two so that TUI frameworks treat them as separate events. The entire operation holds writeMu to prevent interleaving from other sources.

type SessionOpts

type SessionOpts struct {
	ID         string
	Command    string
	Args       []string
	Dir        string
	Env        map[string]string
	Rows, Cols uint16
	LogPath    string
	MaxLogSize int64
	// InputDelay is the pause WriteInputAndSubmit inserts between the typed text
	// and the submit carriage return. Non-positive falls back to typeInputDelay,
	// the built-in default (the daemon passes the [lifecycle] input_delay policy).
	InputDelay time.Duration
	// Logger routes this session's PTY/scrollback diagnostics to the daemon's
	// logger. Nil falls back to slog.Default(). See the Session.log field.
	Logger *slog.Logger
}

type Terminal added in v0.69.0

type Terminal interface {
	// Write feeds raw PTY output bytes into the emulator. It never blocks on
	// terminal query responses (the implementation drains them).
	Write(p []byte) (int, error)
	// Resize changes the screen dimensions to cols columns by rows rows.
	Resize(cols, rows int) error
	// Size returns the current dimensions as (columns, rows).
	Size() (cols, rows int)
	// Cursor returns the cursor column, row (both zero-based), and whether it
	// is currently visible.
	Cursor() (x, y int, visible bool)
	// Cell returns the rendered cell at (x, y). Out-of-range coordinates return
	// a blank cell.
	Cell(x, y int) Cell
	// Close releases resources held by the emulator (e.g. its response-drain
	// goroutine). It is safe to call more than once.
	Close() error
}

Terminal is the terminal-screen emulation surface graith needs: feed it raw PTY output with Write, then read back the rendered screen for previews and snapshots. It deliberately hides the concrete emulator (issue #1211).

Implementations are not required to be safe for concurrent use; callers serialize Write against the Size/Cursor/Cell readers (Session does this with its mutex).

type TerminalSnapshot added in v0.70.0

type TerminalSnapshot struct {
	Cells         []Cell
	CursorX       int
	CursorY       int
	CursorVisible bool
	Cols          int
	Rows          int
}

TerminalSnapshot is one coherent read of a terminal's visible viewport. Cells are stored in row-major order and always contain Cols*Rows entries. Keeping this bulk form behind the narrow Terminal interface avoids one FFI or helper-process round trip per cell.

Jump to

Keyboard shortcuts

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