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 ¶
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.
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."
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 ¶
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 ¶
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.