tymux

package
v1.48.0 Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package tymux contains the TymuxBackend implementation of session.ProcessManager (session/backend_tymux.go), which delegates to a tymuxd instance over gRPC via the generated Connect-Go client in github.com/tstapler/tymux/clients/go.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotImplemented = errors.New("tymux: not implemented")

ErrNotImplemented is returned by tymuxGRPCSession methods that a later epic (2.3+, standing Attach stream / control-mode) has not yet implemented.

View Source
var ErrNotSupportedOnTymuxBackend = errors.New("not supported by the tymux backend")

ErrNotSupportedOnTymuxBackend is returned by GetPTY/GetPanePID (Story 2.2.5, Pattern Decisions): tymux has no local PTY file descriptor or OS pane PID to hand back — every terminal I/O path goes over gRPC, not a local file/process the caller could read or signal directly. Returning this explicit, typed error (never a bare nil/zero value or a panic) lets a generic ProcessManager caller distinguish "not supported by this backend" from "supported but currently unavailable."

View Source
var ErrTymuxdUnreachable = errors.New("tymux: tymuxd unreachable")

ErrTymuxdUnreachable indicates an RPC could not reach a tymuxd daemon at all — connection refused, or nothing listening on the configured socket/address — as opposed to a request that reached tymuxd but was rejected there (a live daemon telling us "no" is a different failure mode than no daemon to ask).

Story 2.2.6 / adversarial-review.md Blocker: stapler-squad does not start or supervise tymuxd itself (a deliberate scope decision — no ensureServerRunning-equivalent, unlike BackendTmux's recoverFromServerFailure). It assumes an out-of-band, already-running daemon. Callers match this sentinel with errors.Is to distinguish "daemon not started/crashed/misconfigured" from "daemon rejected the request" — research/ux.md:218-224's "should not present as the same 'reconnecting' transient state."

Functions

func CellsToSGR

func CellsToSGR(grid []*v1.Row) (string, error)

CellsToSGR renders a PaneSnapshot's cell grid (PaneSnapshot.grid) into an ANSI/SGR-encoded string matching `tmux capture-pane -p -e`'s attribute-preserving output shape — the renderer CapturePaneContent() (Task 2.2.2d) needs and CapturePaneContentRaw() deliberately doesn't (that method only joins Cell.text, no SGR).

Rows are newline-joined (mirroring rowsToPlainText's join). Within a row, an SGR escape sequence is emitted only when a cell's fg/bg/attrs differ from the previous cell's (Task 2.6.1a/Story 2.6.1) — an unbroken attribute run gets exactly one sequence before it, not one per cell.

func NewRealTransport

func NewRealTransport(addr string) rpcTransport

NewRealTransport constructs an rpcTransport backed by a real Connect-Go gRPC client against a live tymuxd. addr is the tymuxd base URL (e.g. "http://127.0.0.1:7419"); pass "" to use tymuxdAddr()'s TYMUXD_ADDR-or-default resolution.

func ReconnectMetricsSnapshot

func ReconnectMetricsSnapshot() map[string]int64

ReconnectMetricsSnapshot returns a point-in-time copy of tymux_attach_stream_reconnects_total, keyed by cause — exported so a future Observability Plan wiring (e.g. an HTTP /metrics handler) can read it without reaching into package-private state, mirroring session/tmux's own ForkPressureSnapshot() convention.

Types

type ClientFanout

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

ClientFanout is a local, in-process multi-subscriber broadcast for one standing Attach stream's output events (Story 2.3.2): the GoF Observer pattern per Pattern Decisions, satisfying SubscribeToControlModeUpdates' multi-subscriber contract from a single upstream stream instead of opening a second Attach call per subscriber.

Broadcast is non-blocking (drop-if-full) so one slow subscriber can never stall the standing stream's reader goroutine — matching the existing lossy-broadcast precedent tymuxd's own output_gap semantics already establish server-side (ADR-003-adjacent design, see Attach's proto doc).

func NewClientFanout

func NewClientFanout() *ClientFanout

NewClientFanout constructs an empty ClientFanout.

func (*ClientFanout) Broadcast

func (f *ClientFanout) Broadcast(data []byte)

Broadcast sends data to every current subscriber's channel with a non-blocking send — a subscriber whose buffer is full drops this frame rather than blocking the caller (the standing stream's reader goroutine).

func (*ClientFanout) Subscribe

func (f *ClientFanout) Subscribe() (string, chan []byte)

Subscribe registers a new subscriber and returns its id (for Unsubscribe) and a channel that receives every subsequent Broadcast call's data. The channel is closed by Unsubscribe, never by Broadcast.

func (*ClientFanout) Unsubscribe

func (f *ClientFanout) Unsubscribe(id string)

Unsubscribe removes and closes the subscriber channel for id. A no-op if id is unknown or was already unsubscribed.

type TymuxManager

type TymuxManager interface {
	// Lifecycle
	Start(dir string) error
	RestoreWithWorkDir(workDir string) error
	Close() error
	IsAlive() bool

	// Identification
	GetSessionIdentifier() string

	// Existence / state
	HasSession() bool

	// Working directory (via pane introspection)
	GetCurrentWorkingDirectory() (string, error)

	// Terminal I/O
	GetPTY() (*os.File, error)
	SendKeys(keys string) (int, error)
	TapEnter() error
	SendPromptWithEnter(prompt string) error
	SendInputViaControlMode(ctx context.Context, data []byte) error

	// Terminal state
	CapturePaneContent() (string, error)
	CapturePaneContentRaw() (string, error)
	CapturePaneContentWithOptions(startLine, endLine string) (string, error)
	CaptureViewport(lines int) (string, error)
	GetCursorPosition() (x, y int, err error)
	GetPaneDimensions() (width, height int, err error)
	SetWindowSize(cols, rows int) error
	SetDetachedSize(width, height int, instanceTitle string) error
	RefreshClient() error

	// Process metadata
	GetPanePID() (int32, error)

	// Content helpers
	HasUpdated() (updated bool, hasPrompt bool, content string)
	FilterBanners(content string) (string, int)
	HasMeaningfulContent(content string) bool

	// Streaming (control mode)
	StartControlMode() error
	StopControlMode() error
	// SubscribeToControlModeUpdates returns a subscription ID and a bidirectional channel.
	// The channel must be bidirectional (chan []byte, not <-chan []byte) because some callers
	// write synthetic frames for testing. Implementations must not write to the channel themselves.
	SubscribeToControlModeUpdates() (string, chan []byte)
	UnsubscribeFromControlModeUpdates(id string)

	// Attach (interactive TUI)
	Attach() (chan struct{}, error)
	DetachSafely() error

	// Exit notifications
	SetOnExitCallback(fn func(string))
	ResetExitOnce()

	// Reconnect state (Task 2.5.2e): exposes ReconnectLoop's live
	// progress — whether this session's standing stream is currently
	// reconnecting, which attempt it's on, and what triggered it — so a
	// future UI (ux.md Surface 2) doesn't have to reverse-engineer it
	// from the aggregate tymux_attach_stream_reconnects_total metric.
	// The one deliberate addition beyond ProcessManager's mirrored
	// method set (requirements.md's constraint keeps ProcessManager
	// itself untouched); state exposure only, no UI here.
	ReconnectState() (reconnecting bool, attempt int, cause string)

	// BackendRestarted reports whether tymuxd was detected to have restarted
	// out from under this session (Story 2.5.3's daemon-restart contract) —
	// the pane's original process is orphaned, not reattached, per
	// Engine::revive_session's always-spawn-fresh behavior on the tymux
	// side. Exposed on the interface (not just the concrete type) so a real
	// caller holding only a TymuxManager/ProcessManager reference can
	// observe this state, not just tests asserting on the unexported type.
	BackendRestarted() (restarted bool, since time.Time)
}

TymuxManager is the interface satisfied by the concrete tymux gRPC session implementation (tymuxGRPCSession, session.go). It mirrors session.ProcessManager's exact method set so TymuxBackend (session/backend_tymux.go) can forward every call one-to-one, the same shape TmuxBackend/TmuxManager use for the tmux backend (session/tmux_backend.go, session/tmux_process_manager.go).

Defined as its own interface here (rather than the session package's ProcessManager directly) because session/tymux is imported BY the session package — depending on session.ProcessManager here would create an import cycle. Duplicating the method set also gives tests a seam to substitute a fake TymuxManager without a live tymuxd daemon.

func NewTymuxGRPCSession

func NewTymuxGRPCSession(transport rpcTransport) TymuxManager

NewTymuxGRPCSession constructs a tymuxGRPCSession using the given rpcTransport, returned as a TymuxManager since tymuxGRPCSession itself is unexported.

Jump to

Keyboard shortcuts

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