Documentation
¶
Index ¶
- Constants
- Variables
- func AnyAgentDeckSessionWithEnvValue(envKey, envValue string) (string, bool)
- func BadgeUpdatesDir() string
- func BindMouseStatusRightDetach() error
- func BindSwitchKey(key, targetSession string) error
- func BindSwitchKeyWithAck(key, targetSession, sessionID string) error
- func CleanPaneTitle(title string) string
- func CleanupOrphanedLogs() (removed int, freedBytes int64, err error)
- func ClearStatusLeft(sessionName string) error
- func ClearStatusLeftGlobal() error
- func DefaultSocketName() string
- func DetachClientsOnSockets(sockets ...string) (bool, error)
- func DetectTerminal() string
- func EmitITermBadgeViaTty(title string, configEnabled bool)
- func EnsurePIDsDead(pids []int, timeout time.Duration)
- func Exec(socketName string, args ...string) *exec.Cmd
- func ExecContext(ctx context.Context, socketName string, args ...string) *exec.Cmd
- func ExpirePaneInfoCacheForTest(t testing.TB)
- func GetAckSignalPath() (string, error)
- func GetActiveSession() (string, error)
- func GetAttachedSessions() ([]string, error)
- func GetAttachedSessionsOnSockets(sockets ...string) []string
- func HasSession(name string) bool
- func HasSessionOnSocket(socketName, name string) bool
- func IndexCtrlQ(data []byte) int
- func IndexDetachKey(data []byte, detachByte byte) int
- func InitializeStatusBarOptions() error
- func IsAtuinPTYProxy() bool
- func IsServerAlive() bool
- func IsTmuxAvailable() error
- func KillSessionsWithEnvValue(envKey, envValue, excludeName string)
- func ListAgentDeckSessions() ([]string, error)
- func ListSessionNamesOnSocket(socketName string) (map[string]struct{}, error)
- func LogDir() string
- func ReadAndClearAckSignal() string
- func RefreshExistingSessions()
- func RefreshPaneInfoCache()
- func RefreshSessionCache()
- func RefreshStatusBarImmediate() error
- func ResetSocketSessionCacheForTest()
- func ResetVersionWarningOnceForTest()
- func RunLogMaintenance(maxSizeMB int, maxLines int, removeOrphans bool)
- func SeedPaneInfoCacheForTest(t testing.TB, info map[string]PaneInfo)
- func SetDefaultSocketName(name string)
- func SetHideCwdPrefixInTitle(hide bool)
- func SetPipeManager(pm *PipeManager)
- func SetStatusHints(detach, switchKey string, switchEnabled bool)
- func SetStatusLeft(sessionName, text string) error
- func SetStatusLeftGlobal(text string) error
- func SpinnerRuneSet() []rune
- func StartAttachPTY(cmd *exec.Cmd, tty *os.File) (*os.File, error)
- func StopServiceUnit(sessionName string) error
- func StripANSI(content string) string
- func StripSpinnerRunes(s string) string
- func SupportsHyperlinks() bool
- func SweepStaleControlClients(socketName string)
- func SwitchAttachedClients(socket, targetSession, sessionID string) (bool, error)
- func TruncateLargeLogFiles(maxSizeMB int, maxLines int) (truncated int, err error)
- func TruncateLogFile(logPath string, maxLines int) error
- func UnbindKey(key string) error
- func UnbindMouseStatusClicks()
- func WarnIfVulnerableTmux()
- func WatchBadgeUpdates(ctx context.Context, tmuxSessionName string, w io.Writer, configEnabled bool, ...)
- func WriteAckSignal(sessionID string) error
- func WriteBadgeUpdate(tmuxSessionName, title string) error
- type AttachOptions
- type ControlPipe
- func (cp *ControlPipe) CapturePaneVia() (string, error)
- func (cp *ControlPipe) Close()
- func (cp *ControlPipe) Done() <-chan struct{}
- func (cp *ControlPipe) IsAlive() bool
- func (cp *ControlPipe) LastOutputTime() time.Time
- func (cp *ControlPipe) OutputEvents() <-chan struct{}
- func (cp *ControlPipe) SendCommand(command string) (string, error)
- func (cp *ControlPipe) WindowEvents() <-chan struct{}
- type KeySender
- type PaneInfo
- type PipeManager
- func (pm *PipeManager) CapturePane(sessionName string) (string, error)
- func (pm *PipeManager) Close()
- func (pm *PipeManager) Connect(sessionName, socketName string) error
- func (pm *PipeManager) ConnectedCount() int
- func (pm *PipeManager) ConnectedSessions() []string
- func (pm *PipeManager) Disconnect(sessionName string)
- func (pm *PipeManager) GetPipe(sessionName string) *ControlPipe
- func (pm *PipeManager) GetWindowActivity(sessionName string) (int64, error)
- func (pm *PipeManager) IsConnected(sessionName string) bool
- func (pm *PipeManager) LastOutputTime(sessionName string) time.Time
- func (pm *PipeManager) RefreshAllActivities() (map[string]int64, map[string][]WindowInfo, error)
- func (pm *PipeManager) RefreshAllPaneInfo() (map[string]PaneInfo, map[string]map[int]string, error)
- func (pm *PipeManager) SetWantPipe(fn func(sessionName string) bool)
- func (pm *PipeManager) SetWindowChangeCallback(cb func())
- type PromptDetector
- type RawPatterns
- type ResolvedPatterns
- type Session
- func DiscoverAllTmuxSessions() ([]*Session, error)
- func ListAllSessions() ([]*Session, error)
- func NewSession(name, workDir string) *Session
- func ReconnectSession(tmuxName, displayName, workDir, command string) *Session
- func ReconnectSessionLazy(tmuxName, displayName, workDir, command string, previousStatus string) *Session
- func ReconnectSessionWithStatus(tmuxName, displayName, workDir, command string, previousStatus string) *Session
- func (s *Session) Acknowledge()
- func (s *Session) AcknowledgeWithSnapshot()
- func (s *Session) ApplySharedAcknowledged(ack bool)
- func (s *Session) ApplyThemeOptions() error
- func (s *Session) Attach(ctx context.Context, detachByte ...byte) error
- func (s *Session) AttachReadOnly(ctx context.Context) error
- func (s *Session) AttachWindow(ctx context.Context, windowIndex int, detachByte ...byte) error
- func (s *Session) AttachWithOptions(ctx context.Context, opts AttachOptions) (SwitchIntent, error)
- func (s *Session) BackgroundWorkPending() bool
- func (s *Session) CachedSubstate() Substate
- func (s *Session) CaptureFullHistory() (string, error)
- func (s *Session) CaptureHistoryLines(n int) (string, error)
- func (s *Session) CapturePane() (string, error)
- func (s *Session) CapturePaneFresh() (string, error)
- func (s *Session) CaptureWindowFullHistory(windowIndex int) (string, error)
- func (s *Session) ConfigureStatusBar()
- func (s *Session) ConfigureTerminalTitle()
- func (s *Session) DetectTool() string
- func (s *Session) EnableMouseMode() error
- func (s *Session) EnsureConfigured()
- func (s *Session) Exists() bool
- func (s *Session) ExistsCached() bool
- func (s *Session) ForceDetectTool() string
- func (s *Session) GetCachedWindowActivity() int64
- func (s *Session) GetEnvironment(key string) (string, error)
- func (s *Session) GetLastActivityTime() time.Time
- func (s *Session) GetMouse() bool
- func (s *Session) GetStatus() (string, error)
- func (s *Session) GetSubstate() Substate
- func (s *Session) GetWaitingSince() time.Time
- func (s *Session) GetWindowActivity() (int64, error)
- func (s *Session) GetWorkDir() string
- func (s *Session) HasUpdated() (bool, error)
- func (s *Session) InvalidateEnvCache()
- func (s *Session) IsAcknowledged() bool
- func (s *Session) IsAltScreen() (bool, error)
- func (s *Session) IsClaudeRunning() bool
- func (s *Session) IsConfigured() bool
- func (s *Session) IsPaneDead() bool
- func (s *Session) Kill() error
- func (s *Session) KillAndWait() error
- func (s *Session) LastObservedActivity() (time.Time, bool)
- func (s *Session) LogFile() string
- func (s *Session) OpenKeySender() (KeySender, error)
- func (s *Session) ResetAcknowledged()
- func (s *Session) Resize(cols, rows int) error
- func (s *Session) RespawnPane(command string) error
- func (s *Session) SendCommand(command string) error
- func (s *Session) SendCtrlC() error
- func (s *Session) SendCtrlU() error
- func (s *Session) SendEnter() error
- func (s *Session) SendKeys(keys string) error
- func (s *Session) SendKeysAndEnter(keys string) error
- func (s *Session) SendKeysAndEnterToWindow(windowIndex int, keys string) error
- func (s *Session) SendKeysChunked(content string) error
- func (s *Session) SendNamedKey(key string) error
- func (s *Session) SetClearOnRestart(clear bool)
- func (s *Session) SetCustomPatterns(toolName string, busyPatterns, promptPatterns, detectPatterns []string)
- func (s *Session) SetDetectPatterns(toolName string, detectPatterns []string)
- func (s *Session) SetEnvironment(key, value string) error
- func (s *Session) SetInjectStatusLine(inject bool)
- func (s *Session) SetMouse(enabled bool)
- func (s *Session) SetPatterns(p *ResolvedPatterns)
- func (s *Session) SetTerminalChromeEnabled(enabled bool)
- func (s *Session) SplitShellPane(workdir string) error
- func (s *Session) Start(command string) error
- func (s *Session) StreamOutput(ctx context.Context, w io.Writer) error
- func (s *Session) WaitForReady(timeout time.Duration) bool
- func (s *Session) WaitForShellPrompt(timeout time.Duration) bool
- type SessionState
- type SpinnerActivityTracker
- type StateTracker
- type Substate
- type SwitchIntent
- type TerminalInfo
- type TitleState
- type VersionProbe
- type WindowInfo
Constants ¶
const SessionPrefix = "agentdeck_"
Variables ¶
var ErrCaptureTimeout = errors.New("capture-pane timed out")
ErrCaptureTimeout is returned when CapturePane exceeds its timeout. Callers should preserve previous state rather than transitioning to error/inactive.
Functions ¶
func AnyAgentDeckSessionWithEnvValue ¶ added in v1.9.19
AnyAgentDeckSessionWithEnvValue reports whether any agentdeck-prefixed tmux session carries envKey=envValue. Returns the matching session name (or "") and a bool. Issue #1040: the spawn-guard's in-lock "already alive" gate uses this to detect that a sibling Restart has already produced a live session before this caller does so again. The probe is read-only — no kill. envValue == "" short-circuits to false because matching every session with an unset variable is never the intent.
func BadgeUpdatesDir ¶ added in v1.9.25
func BadgeUpdatesDir() string
BadgeUpdatesDir returns the directory where rename-hook signals live. Uses the XDG data directory, falling back to a legacy badge-updates dir.
func BindMouseStatusRightDetach ¶ added in v0.26.4
func BindMouseStatusRightDetach() error
BindMouseStatusRightDetach binds a mouse click on the status-right area to detach. Only fires inside agentdeck sessions (guards against detaching the user's outer tmux).
func BindSwitchKey ¶ added in v0.8.43
BindSwitchKey binds a number key to switch to target session. Uses prefix table (default) so Ctrl+b N works. The key should be a single character like "1", "2", etc. Deprecated: Use BindSwitchKeyWithAck for notification bar integration.
func BindSwitchKeyWithAck ¶ added in v0.8.52
BindSwitchKeyWithAck binds a number key to switch to target session AND writes a signal file so agent-deck can acknowledge the session was selected. This enables proper acknowledgment when user presses Ctrl+b 1-6 shortcuts.
func CleanPaneTitle ¶ added in v1.9.58
CleanPaneTitle strips spinner/done-marker characters from a tmux pane title and returns the task description. Returns "" for empty or generic tool titles ("Claude Code", "Gemini CLI", "Codex CLI").
This is the canonical implementation shared by internal/ui (TUI) and internal/web (web server). Both packages import internal/tmux, so placing the logic here avoids a circular dependency.
func CleanupOrphanedLogs ¶ added in v0.5.3
CleanupOrphanedLogs removes log files for sessions that no longer exist A log is considered orphaned if: 1. No tmux session with matching name exists 2. The log file is older than 1 hour (to avoid race conditions during session creation)
func ClearStatusLeft ¶ added in v0.8.43
ClearStatusLeft resets status-left to default for a session. Called when notifications are cleared or acknowledged.
func ClearStatusLeftGlobal ¶ added in v0.8.65
func ClearStatusLeftGlobal() error
ClearStatusLeftGlobal restores the original global status-left value. If the original value was captured, it is restored so the user's theme/plugin (e.g., tmux-oasis) is preserved. Falls back to unsetting the option only if no original value was captured.
func DefaultSocketName ¶ added in v1.7.50
func DefaultSocketName() string
DefaultSocketName returns the process-wide default socket name, or "" when the user has not configured isolation. Safe for concurrent use.
func DetachClientsOnSockets ¶ added in v1.10.9
DetachClientsOnSockets detaches every non-control client attached on any of the given sockets ("" = default server). Returns detached=true iff at least one client was detached.
This is the cross-server companion to SwitchAttachedClients: switch-client cannot move a client between tmux servers, so when a notification target lives on a different socket than the attached session, detaching that client makes agent-deck's paused attach (tea.Exec) return. The TUI then resumes and consumes the focus_request to attach the target on its OWN socket — a detach-and-reattach, which is the only way to "switch while attached" across servers. Control-mode clients (PipeManager pipes) are left alone.
func DetectTerminal ¶ added in v0.3.0
func DetectTerminal() string
DetectTerminal identifies the current terminal emulator from environment variables Returns terminal name: "warp", "iterm2", "kitty", "alacritty", "vscode", "windows-terminal", or "unknown"
func EmitITermBadgeViaTty ¶ added in v1.7.72
EmitITermBadgeViaTty writes the SetBadgeFormat OSC to /dev/tty wrapped in a tmux DCS passthrough envelope. Used by Claude rename-hook subprocesses that run inside a tmux pane: their stdout is owned by the hook protocol (writing OSC there would corrupt the response), but their controlling tty is the tmux pane's pty, so a wrapped OSC routes through tmux out to iTerm2 — this is what closes the gap when Claude renames the session mid-attach.
Silent no-op when:
- not iTerm2 (or env / config disabled)
- the process has no controlling tty (e.g. detached daemon)
- the user isn't attached to this pane in iTerm2 (tmux buffers the pane output but iTerm2 never sees it — fine, the next attach emit in pty.go will catch up)
Debug: set AGENTDECK_ITERM_BADGE_DEBUG=1 to log decisions to /tmp/agent-deck-iterm-badge.log (timestamp, gate values, /dev/tty open result). Useful for diagnosing the multi-process chain Claude → hook subprocess → /dev/tty → tmux → iTerm2.
func EnsurePIDsDead ¶ added in v1.7.68
EnsurePIDsDead blocks until every pid in `pids` is dead (signal-0 probe fails) or `timeout` elapses. Escalates SIGTERM → SIGKILL with a 500ms pause between stages. A zero-length slice is a no-op.
Callers in CLI processes should use this instead of scheduling ensureProcessesDead on a goroutine — see issue #59.
The PID-reuse guard in isOurProcess is preserved: if a captured PID has been recycled into an unrelated process, it's skipped.
func Exec ¶ added in v1.7.50
Exec is the public package counterpart to tmuxExec. Call sites outside internal/tmux (the session package, CLI helpers, web terminal bridge) use this when they have a socket name — typically Instance.TmuxSocketName — and need to spawn a one-off tmux subprocess. Pass "" for the user's default server.
This keeps the `-L <name>` plumbing centralised: there is exactly one place in the codebase that knows how to assemble a tmux argv, so a future socket-selection change (phase 2/3 — per-conductor sockets, env var fallback) only needs to be made here.
func ExecContext ¶ added in v1.7.50
ExecContext is the context-aware variant of Exec.
func ExpirePaneInfoCacheForTest ¶ added in v1.8.3
ExpirePaneInfoCacheForTest leaves the cache contents intact but rewinds the timestamp past the freshness threshold so GetCachedPaneInfo treats it as stale. Used to model the case where backgroundStatusUpdate hasn't run for a while (e.g. navigation hot-window) and the snapshot rebuild path must not blow away previously-known pane titles. t.Cleanup restores the timestamp so calling Expire alone (without a prior Seed that owns its own cleanup) is also safe.
func GetAckSignalPath ¶ added in v0.8.52
GetAckSignalPath returns the path to the acknowledgment signal file
func GetActiveSession ¶ added in v0.8.43
GetActiveSession returns the session name the user is currently attached to. Returns empty string and error if not attached to any session.
func GetAttachedSessions ¶ added in v0.8.65
GetAttachedSessions returns the names of tmux sessions that have real clients attached on the default socket. Used to detect which session the user is currently viewing. Filters out control mode clients (from PipeManager) which are not real user sessions.
func GetAttachedSessionsOnSockets ¶ added in v1.9.73
GetAttachedSessionsOnSockets returns the union of attached (non-control) session names across the given sockets. The default socket is always consulted, then each distinct extra socket; "" denotes the default socket. Sockets with no running server (or any list-clients error) are skipped silently. Order is unspecified and duplicates are removed.
This is the socket-aware counterpart to GetAttachedSessions: a session attached on an isolated agent-deck socket (TmuxSocketName != "") is invisible to a default-socket-only query, so callers that must pin every attached session regardless of socket use this instead.
func HasSession ¶ added in v1.7.8
HasSession is a lightweight public probe for session presence on the user's default tmux server. Exported so packages outside internal/tmux (e.g., the reviver) can answer "does this tmux session exist right now?" without reaching into unexported helpers. Runs a direct `tmux has-session -t <name>` — skips the cache on purpose because the reviver's purpose is to detect a mismatch between our cached view and ground truth.
Use HasSessionOnSocket when the caller knows the session's stored TmuxSocketName — critical for the reviver, which must not ask the default server about sessions that live on an isolated socket.
func HasSessionOnSocket ¶ added in v1.7.50
HasSessionOnSocket probes for a session on an explicit tmux server. Pass Instance.TmuxSocketName (or Session.SocketName) verbatim; empty means the user's default server.
func IndexCtrlQ ¶ added in v0.26.4
IndexCtrlQ returns the index of a Ctrl+Q sequence in data, or -1 if not found. This is a convenience wrapper around IndexDetachKey with the default Ctrl+Q byte.
func IndexDetachKey ¶ added in v0.26.4
IndexDetachKey returns the index of a control-key sequence in data, or -1 if not found. detachByte is the raw ASCII byte (e.g. 0x11 for Ctrl+Q). Handles three encodings:
- Raw byte
- xterm modifyOtherKeys: ESC[27;5;{keyCode}~
- CSI u (kitty keyboard protocol): ESC[{keyCode};5u
func InitializeStatusBarOptions ¶ added in v0.8.67
func InitializeStatusBarOptions() error
InitializeStatusBarOptions sets optimal status bar options for agent-deck. Fixes truncation by setting adequate status-left-length globally. Should be called once during startup.
func IsAtuinPTYProxy ¶ added in v1.10.9
func IsAtuinPTYProxy() bool
IsAtuinPTYProxy checks if the current shell session is running under atuin's pty-proxy. Atuin pty-proxy acts as a PTY MITM between the terminal and the shell, intercepting all I/O. It sets ATUIN_PTY_PROXY_ACTIVE when active.
agent-deck's Bubble Tea TUI is incompatible with atuin pty-proxy because:
- os.Stdin/os.Stdout are pipes to atuin's proxy, not direct terminal FDs
- Alternate screen sequences (tea.WithAltScreen) may be swallowed
- Mouse tracking sequences (tea.WithMouseCellMotion) may not be forwarded
Users should use `atuin init zsh` (or bash/fish) instead of `atuin pty-proxy init zsh` when running agent-deck.
func IsServerAlive ¶ added in v1.3.1
func IsServerAlive() bool
IsServerAlive returns whether the tmux server was recently reachable. Result is cached for 5 seconds to avoid redundant checks.
func IsTmuxAvailable ¶
func IsTmuxAvailable() error
IsTmuxAvailable checks if tmux is installed and accessible Returns nil if tmux is available, otherwise returns an error with details
func KillSessionsWithEnvValue ¶ added in v1.7.2
func KillSessionsWithEnvValue(envKey, envValue, excludeName string)
KillSessionsWithEnvValue kills agentdeck tmux sessions that have the given environment variable set to the given value, excluding the session named `excludeName`. This prevents duplicate tmux sessions running the same Claude conversation (#596).
func ListAgentDeckSessions ¶ added in v0.8.62
ListAgentDeckSessions returns the names of all agentdeck tmux sessions. This is used to update notification bars across ALL sessions, not just those in the current profile. This ensures consistent notification bars when users switch between sessions.
func ListSessionNamesOnSocket ¶ added in v1.10.9
ListSessionNamesOnSocket returns the set of live session names on one tmux socket using a SINGLE bounded `list-sessions`. It is the socket-complete alternative to probing each session with `has-session`: callers that need liveness for N sessions should group them by socket and call this once per distinct socket (O(sockets) subprocesses instead of O(sessions)).
An error means the probe was indeterminate (timed out). Callers MUST treat that as "assume alive" — never as "no sessions" — or a briefly-wedged server will look like a pile of dead sessions. A successful probe returning an empty set is authoritative (server present, no sessions / no server running).
func LogDir ¶ added in v0.3.0
func LogDir() string
LogDir returns the directory containing all session logs
func ReadAndClearAckSignal ¶ added in v0.8.52
func ReadAndClearAckSignal() string
ReadAndClearAckSignal reads the session ID from the signal file and deletes it. Returns empty string if no signal file exists or on error.
func RefreshExistingSessions ¶ added in v0.6.0
func RefreshExistingSessions()
RefreshExistingSessions is an alias for RefreshSessionCache for backwards compatibility
func RefreshPaneInfoCache ¶ added in v0.14.0
func RefreshPaneInfoCache()
RefreshPaneInfoCache updates the cache of pane titles and commands for all sessions. Call this ONCE per tick (from backgroundStatusUpdate), then use GetCachedPaneInfo() to read cached values. Tries PipeManager first, falls back to subprocess.
func RefreshSessionCache ¶ added in v0.6.0
func RefreshSessionCache()
RefreshSessionCache updates the cache of existing tmux sessions and their activity Call this ONCE per tick, then use Session.Exists() and Session.GetWindowActivity() which read from cache. This reduces 30+ subprocess spawns to just 1 per tick cycle.
Tries PipeManager first (zero subprocess), falls back to subprocess.
NOTE: We use window_activity (not session_activity) because window_activity updates when there's actual terminal output, while session_activity only updates on session-level events. This is critical for detecting when Claude is actively working.
func RefreshStatusBarImmediate ¶ added in v0.8.67
func RefreshStatusBarImmediate() error
RefreshStatusBarImmediate forces an immediate status bar redraw for ALL connected clients. This bypasses the status-interval timer (default 15s) for instant visual feedback. Uses -S flag which only refreshes the status line (lightweight operation ~1-2ms per client). Filters out control mode clients (from PipeManager) which don't have a visible status bar.
func ResetSocketSessionCacheForTest ¶ added in v1.10.9
func ResetSocketSessionCacheForTest()
ResetSocketSessionCacheForTest clears the per-socket cache between tests.
func ResetVersionWarningOnceForTest ¶ added in v1.7.70
func ResetVersionWarningOnceForTest()
ResetVersionWarningOnceForTest clears the sync.Once so tests can exercise the repeat-call path. Not for production use.
func RunLogMaintenance ¶ added in v0.5.3
RunLogMaintenance performs all log maintenance tasks based on settings This should be called once at startup and optionally periodically
func SeedPaneInfoCacheForTest ¶ added in v1.8.3
SeedPaneInfoCacheForTest replaces the package's pane info cache with the supplied data and marks it fresh. Test cleanup wipes the cache back to its pristine zero state so concurrent or follow-on tests do not see seeded data.
Production callers must use RefreshPaneInfoCache; this exists so packages outside internal/tmux (notably internal/ui) can drive snapshot/render tests without standing up a real tmux server.
func SetDefaultSocketName ¶ added in v1.7.50
func SetDefaultSocketName(name string)
SetDefaultSocketName seeds the process-wide socket used by package-level tmux calls. Called once from main.go after config load and CLI flag parsing. Whitespace is trimmed; a blank or whitespace-only input clears the default (falls back to pre-v1.7.50 behavior).
func SetHideCwdPrefixInTitle ¶ added in v1.9.46
func SetHideCwdPrefixInTitle(hide bool)
SetHideCwdPrefixInTitle configures whether the terminal title includes the "[<cwd-basename>]" prefix. Pass true to drop it (display.include_cwd_prefix = false). Safe to call concurrently; intended to run once at startup.
func SetPipeManager ¶ added in v0.11.0
func SetPipeManager(pm *PipeManager)
SetPipeManager sets the global PipeManager instance (called once at startup).
func SetStatusHints ¶ added in v1.9.58
SetStatusHints updates the detach/switch key labels shown in the tmux status-right bar. Empty labels are ignored (the existing value is kept).
func SetStatusLeft ¶ added in v0.8.43
SetStatusLeft sets the left side of tmux status bar for a session. Used by NotificationManager to display waiting session notifications.
func SetStatusLeftGlobal ¶ added in v0.8.65
SetStatusLeftGlobal sets the left side of tmux status bar globally. This is a MAJOR performance optimization: ONE tmux call instead of 100+. All agentdeck sessions inherit this global setting. On first call, captures the existing status-left so ClearStatusLeftGlobal can restore it.
func SpinnerRuneSet ¶ added in v0.9.2
func SpinnerRuneSet() []rune
spinnerRuneSet returns the full set of spinner runes for content normalization. Includes both the "active-only" chars (used for busy detection) and the additional chars (·, ✻) that appear in done/other states but still need stripping for stable hashing.
func StartAttachPTY ¶ added in v1.9.33
StartAttachPTY starts cmd attached to a new PTY pre-sized to tty's current dimensions.
#1167: tmux clients connect at their PTY's size. A detached `new-session` (no -x/-y) is born at tmux's 80x24 default-size, and a bare pty.Start creates the attach client's PTY at the same 80x24 default — so window-size=largest pins the window to 80 cols, ~half of a wide terminal, until an async SIGWINCH grows it. Reading the controlling terminal's real size up front and starting the PTY with it makes the client full-width from frame one.
When tty is not a terminal (size probe fails), it falls back to a plain start at the default size: a degraded attach is still better than no attach.
func StopServiceUnit ¶ added in v1.7.21
StopServiceUnit best-effort stops + resets-failed the transient user-level service for the given session name. Called by agent-deck remove on service-mode sessions to guarantee the unit does not Restart=on-failure its way back into existence after removal. Errors are returned but callers typically log-and-continue.
Returns nil on non-systemd hosts (no-op), on already-stopped units, and on hosts where systemctl is missing — removal must not block on systemd availability.
The unit name derivation mirrors startCommandSpec's service branch: "agentdeck-tmux-" + sanitized(sessionName) + ".service".
func StripANSI ¶
StripANSI removes ANSI escape codes from content using O(n) single-pass algorithm. This is important because terminal output contains color codes.
PERFORMANCE: Uses strings.Builder with pre-allocation for O(n) time complexity. Previous implementation used string concatenation in loops which was O(n²) and caused 2-11 second UI freezes on large terminal output (Issue #39).
NOTE: We intentionally avoid regex here because complex ANSI regex patterns can cause catastrophic backtracking on malformed escape sequences.
func StripSpinnerRunes ¶ added in v0.12.3
StripSpinnerRunes removes all spinner characters in a single O(n) pass using strings.Map, replacing 16 sequential strings.ReplaceAll calls.
func SupportsHyperlinks ¶ added in v0.3.0
func SupportsHyperlinks() bool
SupportsHyperlinks returns true if the current terminal supports OSC 8 hyperlinks
func SweepStaleControlClients ¶ added in v1.10.9
func SweepStaleControlClients(socketName string)
SweepStaleControlClients reaps orphaned control-mode clients across EVERY session on the tmux server selected by socketName (pass "" for the default server), not just one named session. killStaleControlClients only sweeps the single session passed to PipeManager.Connect(), so orphans belonging to sessions the TUI never reconnects to accumulate indefinitely: each prior crashed / SIGKILL'd / OOM-killed TUI leaves one orphaned `tmux -C` client per session, and only the sessions actively reopened ever get cleaned. Observed in the wild as 176 orphaned control clients exhausting the macOS pty cap (kern.tty.ptmx_max=511), blocking all new tmux/terminal sessions.
Run once at TUI startup, this server-wide sweep clears the entire backlog left by previous dead TUIs. Live sibling TUIs' clients (instances.allow_multiple=true) are preserved via the same isControlClientOrphan check used by killStaleControlClients (#927).
Bounded by staleControlSweepTimeout: this runs on the boot path, so a hung or unresponsive tmux server must not stall startup. A timed-out (or otherwise failed) list-clients is treated as best-effort and skipped — the next launch sweeps again.
func SwitchAttachedClients ¶ added in v1.10.9
SwitchAttachedClients moves every non-control client attached on socket into targetSession and writes the ack-signal for sessionID. Returns switched=true iff at least one client was attached and switched.
This is the programmatic equivalent of the Ctrl+b N quick-switch: like that path it works while the TUI is suspended inside tea.Exec (tmux drives the switch, not Bubble Tea). And like that path it only works when the attached client and target session share a tmux server — querying clients on the target's own socket means a client attached elsewhere yields switched=false, so the caller falls back to a focus_request rather than mis-switching.
func TruncateLargeLogFiles ¶ added in v0.5.3
TruncateLargeLogFiles checks all log files and truncates any that exceed maxSizeMB
func TruncateLogFile ¶ added in v0.5.3
TruncateLogFile truncates a log file to keep only the last maxLines lines This is called when a log file exceeds maxSizeBytes
func UnbindKey ¶ added in v0.8.43
UnbindKey removes a key binding and restores default behavior. After unbinding, attempts to restore the default behavior where number keys select windows. The restore is best-effort since it may fail in environments without windows (e.g., CI) and agent-deck rebinds keys every 2s anyway.
func UnbindMouseStatusClicks ¶ added in v0.26.4
func UnbindMouseStatusClicks()
UnbindMouseStatusClicks removes mouse click bindings from the status bar.
func WarnIfVulnerableTmux ¶ added in v1.7.70
func WarnIfVulnerableTmux()
WarnIfVulnerableTmux prints a one-time stderr warning when the host tmux is known-vulnerable to the CONTROL_SHOULD_NOTIFY_CLIENT NULL-deref (tmux #4980, stability row S14, issue #737). No-op on non-macOS, no-op when AGENTDECK_SUPPRESS_TMUX_WARNING=1/true. Safe to call from main() unconditionally; gated by sync.Once so repeat invocations are free.
func WatchBadgeUpdates ¶ added in v1.9.25
func WatchBadgeUpdates(ctx context.Context, tmuxSessionName string, w io.Writer, configEnabled bool, ready chan<- struct{})
WatchBadgeUpdates blocks until ctx is cancelled. While running, it emits an iTerm2 SetBadgeFormat OSC to w every time the badge-update file for tmuxSessionName changes. The same two gates as emitITermBadge apply (iTerm2 detection + configEnabled), so the watcher cannot bypass the user's opt-out.
ready, if non-nil, is closed once the fsnotify watch is registered. Callers that race a Write against the watcher's startup (i.e. tests) must wait on ready before writing — fsnotify drops events that fire before Add() returns.
Called from Attach() in its own goroutine; the ctx cancel that runs on detach is what stops it.
func WriteAckSignal ¶ added in v1.10.9
WriteAckSignal writes sessionID to the ack-signal file so the TUI's background sync (which runs even while the TUI is paused inside tea.Exec) acknowledges the session, updates the notification bar, and records the switch for detach cursor-sync. This is the programmatic equivalent of the `echo <id> > signal` step in buildAckSwitchScript; pairs with SwitchAttachedClients.
func WriteBadgeUpdate ¶ added in v1.9.25
WriteBadgeUpdate atomically writes title under tmuxSessionName so the attach-side watcher can pick it up. Called by the Claude rename hook (which has no controlling tty) instead of the doomed EmitITermBadgeViaTty path.
Atomic via tmp + rename — same idiom as WriteStatusEvent — so the fsnotify CREATE/WRITE the watcher sees never points at a partial file. tmuxSessionName is used verbatim as the filename; tmux session names are constrained enough (no slashes) that the path stays single-segment.
Types ¶
type AttachOptions ¶ added in v1.9.58
type AttachOptions struct {
// DetachByte is the raw control byte that detaches (0 => default Ctrl+Q).
DetachByte byte
// SwitchKeyByte is the control byte (e.g. Ctrl+S, 0x13) that hands control
// back to the caller to open the in-attach session switcher. 0 disables it.
//
// This is deliberately a plain control byte, not Ctrl+Tab: terminals only
// emit a distinct sequence for Ctrl+Tab under an enhanced keyboard protocol
// that is not reliably available during attach, so a control byte is the
// only portable trigger (the cycling/commit UX then lives in the TUI).
SwitchKeyByte byte
// ScrollbackKeyByte is a control byte (e.g. Ctrl+G, 0x07) that hands control
// back to the caller to open the in-view scrollback pager (#1491). 0
// disables the control-byte trigger; the PageUp trigger below is
// independent.
ScrollbackKeyByte byte
// ScrollbackOnPageUp, when true, makes a bare PageUp (ESC[5~) open the
// scrollback pager. Modified PageUp (Shift/Ctrl/Alt) always passes through.
// This is the default trigger because it is exactly the key a user presses
// expecting to scroll back through the session.
ScrollbackOnPageUp bool
// ScrollbackGate, when non-nil, is consulted the moment a bare PageUp is
// seen (with ScrollbackOnPageUp set). Returning true opens the pager — the
// behaviour when the gate is nil; returning false leaves the PageUp for the
// attached program. It exists so the pager never hijacks PageUp from a
// full-screen app (e.g. Claude fullscreen) that scrolls itself and keeps no
// tmux scrollback for the pager to show. It is invoked ONLY when a PageUp is
// actually present, so the per-press tmux query it typically performs is
// cheap and never runs on ordinary keystrokes. It is NOT consulted for the
// ScrollbackKeyByte chord, which is an explicit user opt-in.
ScrollbackGate func() bool
}
AttachOptions configures AttachWithOptions. The zero value attaches with the default Ctrl+Q detach key and no session-switch key.
type ControlPipe ¶ added in v0.11.0
type ControlPipe struct {
// contains filtered or unexported fields
}
ControlPipe wraps a persistent `tmux -C attach-session -t <name>` process. It provides event-driven output detection via %output events and zero-subprocess command execution through the stdin/stdout pipe.
func NewControlPipe ¶ added in v0.11.0
func NewControlPipe(sessionName, socketName string) (*ControlPipe, error)
NewControlPipe starts a tmux control mode pipe attached to the given session on the given socket. socketName is the tmux `-L <name>` selector captured at session-creation time (Instance.TmuxSocketName / Session.SocketName); pass "" to target the user's default tmux server. Blocks until the initial handshake completes (or a short timeout), so the pipe is ready for SendCommand immediately after return. Retries a few times to smooth over transient tmux/control-mode startup failures.
func (*ControlPipe) CapturePaneVia ¶ added in v0.11.0
func (cp *ControlPipe) CapturePaneVia() (string, error)
CapturePaneVia sends capture-pane through the control mode pipe. Returns the pane content without spawning any subprocess.
func (*ControlPipe) Close ¶ added in v0.11.0
func (cp *ControlPipe) Close()
Close shuts down the control mode pipe.
Teardown is staged: (1) close stdin so the `tmux -C attach-session` child sees EOF and orderly-detaches via the control protocol's %exit path; (2) wait up to controlPipeEOFExitGrace (200ms) for that to complete — the vast majority of cases settle in 1-4ms; (3) only on timeout, escalate to softKillProcessGroup (SIGTERM+grace, SIGKILL fallback) for stuck or wedged clients.
The previous implementation went straight from stdin.Close() to softKillProcessGroup with no wait. Even the SIGTERM-with-grace form races tmux's server-side control_notify_client_detached walk (tmux/tmux#4980, present in macOS Homebrew tmux 3.6a) — the bug is server-side, so any signal-driven detach can trigger it. Letting the child self-exit on EOF goes through the protocol's orderly-detach codepath instead, which empirically does not trigger the crash. See ~/.claude/scratchpad/agent-deck/tmux-issues/PLAN.md "Empirical validation" for the measurements.
func (*ControlPipe) Done ¶ added in v0.11.0
func (cp *ControlPipe) Done() <-chan struct{}
Done returns a channel that closes when the pipe exits.
func (*ControlPipe) IsAlive ¶ added in v0.11.0
func (cp *ControlPipe) IsAlive() bool
IsAlive returns true if the control mode process is still running.
func (*ControlPipe) LastOutputTime ¶ added in v0.11.0
func (cp *ControlPipe) LastOutputTime() time.Time
LastOutputTime returns the time of the most recent %output event.
func (*ControlPipe) OutputEvents ¶ added in v0.11.0
func (cp *ControlPipe) OutputEvents() <-chan struct{}
OutputEvents returns a channel that fires when the session produces output. Multiple rapid outputs may be coalesced into fewer channel sends.
func (*ControlPipe) SendCommand ¶ added in v0.11.0
func (cp *ControlPipe) SendCommand(command string) (string, error)
SendCommand sends a command through the control mode pipe and waits for the response. Commands are serialized via cmdMu. Returns the response text or an error. Timeout is slightly relaxed to reduce false negatives when tmux is busy.
func (*ControlPipe) WindowEvents ¶ added in v0.21.0
func (cp *ControlPipe) WindowEvents() <-chan struct{}
WindowEvents returns a channel that fires when a window is added or closed.
type KeySender ¶ added in v1.9.24
type KeySender interface {
// SendKeys forwards `text` as literal keystrokes (tmux send-keys -l).
SendKeys(text string) error
// SendNamedKey forwards a tmux named key (e.g. "Up", "BSpace", "C-c").
SendNamedKey(key string) error
// SendEnter forwards a single Enter keystroke.
SendEnter() error
// Close releases the underlying subprocess. Subsequent Send calls fail.
Close() error
}
KeySender pushes keystrokes to a tmux pane over a persistent connection, amortizing the per-call fork+exec cost of `tmux send-keys` (#1102, follow-up to #1096). #1096 added 15ms rune batching, but at realistic typing speeds (>15ms between keys) every keystroke still triggers its own fork+exec — on macOS each one costs 10-50ms, so the user feels per-keystroke lag despite the batch window. KeySender opens one `tmux -L <socket> -C` subprocess at the start of an insert-mode session and streams send-keys commands over stdin for the lifetime of that mode, dropping per-call dispatch to a stdin write (<1ms regardless of platform).
func OpenKeySender ¶ added in v1.9.24
OpenKeySender starts a persistent tmux control-mode client on `socket` (empty = the user's default tmux server) and returns a KeySender bound to `target` (a tmux session/pane selector like "my-session" or "id:0.0").
The subprocess does NOT attach to any session — it stays in control mode, reads commands from stdin, and emits status notifications on stdout (which are drained and discarded). At least one session must exist on the server for it to remain running; this is always true at the call site because insert mode targets an existing session.
Returns a started KeySender on success. On any setup failure the subprocess is cleaned up and an error is returned, so callers can fall back to per-call fork+exec (the legacy path via Session.SendKeys).
type PaneInfo ¶ added in v0.14.0
PaneInfo holds pane title and current command for a tmux session.
func GetCachedPaneInfo ¶ added in v0.14.0
GetCachedPaneInfo returns cached pane info for a session. Returns (info, true) if found and cache is fresh, (zero, false) otherwise.
func GetCachedPaneInfoSnapshot ¶ added in v1.9.58
GetCachedPaneInfoSnapshot is GetCachedPaneInfo plus the time the cache snapshot was taken. Callers that promote state based on pane info (e.g. the shell foreground running indicator) use the snapshot time to reject entries that predate an event of interest — a pane snapshot taken before a session was (re)started describes the previous same-name session, not the current one. The same 4s freshness window applies (2 refresh ticks).
type PipeManager ¶ added in v0.11.0
type PipeManager struct {
// contains filtered or unexported fields
}
PipeManager manages ControlPipes for all active tmux sessions. It provides zero-subprocess CapturePane and event-driven output detection. Falls back to subprocess execution when pipes are unavailable.
func GetPipeManager ¶ added in v0.11.0
func GetPipeManager() *PipeManager
GetPipeManager returns the global PipeManager instance. Returns nil if not initialized (control pipes disabled or not yet started).
func NewPipeManager ¶ added in v0.11.0
func NewPipeManager(ctx context.Context, onOutput func(sessionName string)) *PipeManager
NewPipeManager creates a new PipeManager. The onOutput callback is invoked whenever a connected session produces terminal output (via %output events).
func (*PipeManager) CapturePane ¶ added in v0.11.0
func (pm *PipeManager) CapturePane(sessionName string) (string, error)
CapturePane routes capture-pane through the control mode pipe if available. Falls back to subprocess execution if the pipe is nil, dead, or errors.
func (*PipeManager) Close ¶ added in v0.11.0
func (pm *PipeManager) Close()
Close shuts down all pipes and cancels the context.
func (*PipeManager) Connect ¶ added in v0.11.0
func (pm *PipeManager) Connect(sessionName, socketName string) error
Connect creates a control mode pipe for the given tmux session. If a pipe already exists and is alive, this is a no-op. Uses reconnecting map to prevent concurrent pipe creation for the same session. Connect opens a control-mode pipe to sessionName on the tmux server selected by socketName (Session.SocketName). Pass "" to target the user's default server. Safe to call repeatedly; a live pipe short-circuits and returns nil.
func (*PipeManager) ConnectedCount ¶ added in v0.11.0
func (pm *PipeManager) ConnectedCount() int
ConnectedCount returns the number of alive pipes.
func (*PipeManager) ConnectedSessions ¶ added in v1.9.73
func (pm *PipeManager) ConnectedSessions() []string
ConnectedSessions returns the names of sessions with an alive pipe.
func (*PipeManager) Disconnect ¶ added in v0.11.0
func (pm *PipeManager) Disconnect(sessionName string)
Disconnect closes and removes the pipe for the given session.
func (*PipeManager) GetPipe ¶ added in v0.11.0
func (pm *PipeManager) GetPipe(sessionName string) *ControlPipe
GetPipe returns the ControlPipe for a session, or nil if not connected.
func (*PipeManager) GetWindowActivity ¶ added in v0.11.0
func (pm *PipeManager) GetWindowActivity(sessionName string) (int64, error)
GetWindowActivity sends a display-message command through the pipe to get the window_activity timestamp. Falls back to error if pipe unavailable.
func (*PipeManager) IsConnected ¶ added in v0.11.0
func (pm *PipeManager) IsConnected(sessionName string) bool
IsConnected returns true if a session has an alive pipe.
func (*PipeManager) LastOutputTime ¶ added in v0.11.0
func (pm *PipeManager) LastOutputTime(sessionName string) time.Time
LastOutputTime returns the last output time for a session from its pipe. Returns zero time if no pipe or no output recorded.
func (*PipeManager) RefreshAllActivities ¶ added in v0.11.0
func (pm *PipeManager) RefreshAllActivities() (map[string]int64, map[string][]WindowInfo, error)
RefreshAllActivities sends a list-windows command through one pipe per distinct socket to get activity timestamps for ALL sessions across every tmux server we have a live pipe to. This replaces the subprocess call in RefreshSessionCache. Session names carry random suffixes, so cross-socket name collisions are effectively impossible and merging by name is safe.
func (*PipeManager) RefreshAllPaneInfo ¶ added in v0.14.0
RefreshAllPaneInfo sends a single list-panes command through any available pipe to get pane titles and current commands for ALL sessions. This provides the data needed for title-based state detection without subprocess spawns. Also returns per-window tool detection data for enriching the window cache.
func (*PipeManager) SetWantPipe ¶ added in v1.9.73
func (pm *PipeManager) SetWantPipe(fn func(sessionName string) bool)
SetWantPipe installs the predicate that decides which sessions hold a live pipe. Call once at startup before Connect. nil-safe: an unset predicate means every session is wanted (legacy behaviour).
func (*PipeManager) SetWindowChangeCallback ¶ added in v0.21.0
func (pm *PipeManager) SetWindowChangeCallback(cb func())
SetWindowChangeCallback sets the callback for window add/close events. Must be called before Connect to ensure all pipes forward events.
type PromptDetector ¶
type PromptDetector struct {
// contains filtered or unexported fields
}
PromptDetector checks for tool-specific prompts in terminal content Based on Claude Squad's exact implementation: https://github.com/smtg-ai/claude-squad/blob/main/session/tmux/tmux.go
func NewPromptDetector ¶
func NewPromptDetector(tool string) *PromptDetector
NewPromptDetector creates a detector for the specified tool
func (*PromptDetector) ClassifySubstate ¶ added in v1.9.66
func (d *PromptDetector) ClassifySubstate(content string) Substate
ClassifySubstate returns the additive Substate for the given pane content. Claude-only (the heuristics are Claude Code renderings); other tools return SubstateNone.
Precedence (most-actionable first). With only a fixed text window and no timestamps, a stale line and a current line cannot be ordered perfectly; this ordering picks the verdict that is RIGHT in the realistic case for each pair:
- auth-401 — a TERMINAL auth/connection failure banner. Checked FIRST: a 401 is unrecoverable and stops the spinner, so when the banner is present a busy cue elsewhere in the window is the stale one. hasClaudeErrorBanner already excludes prose / quoted (⎿) / input-line / behind-spinner-retry banners, so an in-flight retry (which IS still working) does not match here and correctly falls through to the busy check below.
- running — a genuine, UNAMBIGUOUS active-work cue (interrupt hint / braille spinner / "… tokens" timing). Wins over a stale model-unavailable no-op: if the session is crunching NOW, an older "Crunched for 0s" / "unavailable" line is stale. Deliberately does NOT treat a bare "✶" as a cue, so the no-op completion line's decorative asterisk does not match.
- model-unavailable — the Fable-down no-op loop with no live busy cue.
- idle-at-empty-prompt — sitting at the prompt with nothing happening.
- none — no distinct refinement.
func (*PromptDetector) HasErrorBanner ¶ added in v1.9.58
func (d *PromptDetector) HasErrorBanner(content string) bool
HasErrorBanner reports whether the terminal content shows an error banner rendered by the tool itself — NOT conversation text discussing an error. Used by status detection to classify a visibly broken session (auth failure, dead connection) as "error" instead of "waiting": after such a failure the tool redraws its input prompt below the banner, so prompt detection alone reports waiting while the session cannot actually make progress (#1400).
Currently implemented for Claude Code only; other tools return false.
func (*PromptDetector) HasPrompt ¶
func (d *PromptDetector) HasPrompt(content string) bool
HasPrompt checks if the terminal content contains a prompt waiting for input These patterns are derived from Claude Squad + additional research for edge cases
type RawPatterns ¶ added in v0.9.2
type RawPatterns struct {
BusyPatterns []string // plain strings + "re:" prefixed regex
PromptPatterns []string
SpinnerChars []string
WhimsicalWords []string
}
RawPatterns holds string-form patterns before compilation. Patterns prefixed with "re:" are compiled as regex; everything else uses strings.Contains.
func DefaultRawPatterns ¶ added in v0.9.2
func DefaultRawPatterns(toolName string) *RawPatterns
DefaultRawPatterns returns the built-in detection patterns for a known tool. Returns nil for unknown tools (they have no defaults).
func MergeRawPatterns ¶ added in v0.9.2
func MergeRawPatterns(defaults, overrides, extras *RawPatterns) *RawPatterns
MergeRawPatterns merges defaults with overrides and extras.
- If overrides has a field set (non-nil slice, even if empty), it replaces the default.
- extras fields are appended to the result (after defaults or overrides).
- If defaults is nil, only overrides/extras are used.
type ResolvedPatterns ¶ added in v0.9.2
type ResolvedPatterns struct {
BusyStrings []string
BusyRegexps []*regexp.Regexp
PromptStrings []string
PromptRegexps []*regexp.Regexp
SpinnerChars []string
// Pre-built combo patterns (from WhimsicalWords + SpinnerChars)
ThinkingPattern *regexp.Regexp
ThinkingPatternEllipsis *regexp.Regexp
SpinnerActivePattern *regexp.Regexp
}
ResolvedPatterns holds the compiled, ready-to-use patterns for status detection.
func CompilePatterns ¶ added in v0.9.2
func CompilePatterns(raw *RawPatterns) (*ResolvedPatterns, error)
CompilePatterns compiles raw string patterns into ready-to-use ResolvedPatterns. Patterns prefixed with "re:" are compiled as regex. Invalid regex patterns are logged as warnings and skipped (never crash).
type Session ¶
type Session struct {
Name string
DisplayName string
WorkDir string
Command string
Created time.Time
InstanceID string // Agent-deck instance ID for hook callbacks
// SocketName is the tmux `-L <name>` socket selector for this session.
// When empty (pre-v1.7.50 default), every tmux call targets the user's
// default server at $TMUX_TMPDIR/tmux-<uid>/default, preserving the
// historical behavior exactly. When non-empty, every tmux subprocess
// spawned by methods on this Session carries `-L <SocketName>` so the
// agent-deck tmux server is fully isolated from the user's interactive
// tmux.
//
// SocketName is populated at session-creation time from (in precedence
// order) the CLI flag `--tmux-socket`, then `[tmux].socket_name` in
// config.toml, then empty. It is persisted per-instance in SQLite so
// subsequent restarts/revives reach the correct server even if the
// installation-wide config later changes. See RFC socket-isolation
// phase 1 and Instance.TmuxSocketName. Never mutate after Start().
SocketName string
// OptionOverrides are user-specified tmux set-option overrides from config.
// Applied AFTER all defaults in Start(), so they take precedence.
// Keys are tmux option names, values are their settings.
// Example: {"allow-passthrough": "all", "history-limit": "50000"}
OptionOverrides map[string]string
// RunCommandAsInitialProcess launches Start(command) as the pane's initial
// process instead of sending it via SendKeysAndEnter after session creation.
// Sandbox sessions enable this so pane-dead detection can restart exited tools.
RunCommandAsInitialProcess bool
// VimMode guarantees the inner agent's input composer is in insert mode
// before any text/Enter is delivered. When the inner tool (Claude Code with
// `"editorMode": "vim"`) leaves its prompt in vim NORMAL mode — the default
// state after a turn finishes — a bracketed paste still lands in the input
// widget, but the trailing Enter is interpreted as a navigation keystroke
// instead of submit, so the message is typed but never sent (issue #1264).
// When true, SendEnter and SendKeysAndEnter prepend an Escape + `i` sequence
// so the prompt is guaranteed to be in insert mode. The sequence is
// idempotent: Escape always lands in normal mode and `i` always enters
// insert, so it is safe even when the prompt is already in insert mode.
// Off by default (zero value) — non-vim sessions and other tools are
// unaffected. Populated at session-creation time from [claude].vim_mode.
VimMode bool
// LaunchInUserScope starts the tmux server through systemd-run --user --scope
// so the server is owned by the user's systemd manager instead of the current
// login session scope.
LaunchInUserScope bool
// LaunchAs overrides the spawn form (v1.7.21+). Valid values:
// "scope", "service", "direct", "auto", or "" (defer to
// LaunchInUserScope). "service" uses systemd-run --user --unit
// <NAME>.service with Type=forking + Restart=on-failure so tmux
// auto-restarts on OOM / SIGKILL / unexpected death. Unknown values
// fall through to LaunchInUserScope behavior — populated by callers
// from TmuxSettings.GetLaunchAs which already canonicalises.
LaunchAs string
// contains filtered or unexported fields
}
Session represents a tmux session NOTE: All mutable fields are protected by mu. The Bubble Tea event loop is single-threaded, but we use mutex protection for defensive programming and future-proofing.
func DiscoverAllTmuxSessions ¶
DiscoverAllTmuxSessions returns all tmux sessions (including non-Agent Deck ones)
func ListAllSessions ¶
ListAllSessions returns all Agent Deck tmux sessions
func NewSession ¶
NewSession creates a new Session instance with a unique name
func ReconnectSession ¶
ReconnectSession creates a Session object for an existing tmux session This is used when loading sessions from storage - it properly initializes all fields needed for status detection to work correctly
Note: This runs immediate configuration (ConfigureStatusBar). For lazy loading during TUI startup, use ReconnectSessionLazy instead.
func ReconnectSessionLazy ¶ added in v0.8.95
func ReconnectSessionLazy(tmuxName, displayName, workDir, command string, previousStatus string) *Session
ReconnectSessionLazy creates a Session object without running any tmux configuration. PERFORMANCE: This is used during TUI startup to avoid subprocess overhead. Non-essential configuration (EnableMouseMode, ConfigureStatusBar) is deferred until first user interaction via EnsureConfigured().
Use this for bulk session loading where immediate configuration is not needed. For sessions that need immediate configuration, use ReconnectSession or ReconnectSessionWithStatus.
func ReconnectSessionWithStatus ¶
func ReconnectSessionWithStatus(tmuxName, displayName, workDir, command string, previousStatus string) *Session
ReconnectSessionWithStatus creates a Session with pre-initialized state based on previous status This restores the exact status state across app restarts:
- "idle" (gray): acknowledged=true, cooldown expired
- "waiting" (yellow): acknowledged=false, cooldown expired
- "active" (green): will be recalculated based on actual content changes
func (*Session) Acknowledge ¶
func (s *Session) Acknowledge()
Acknowledge marks the session as "seen" by the user Call this when user attaches to the session
func (*Session) AcknowledgeWithSnapshot ¶
func (s *Session) AcknowledgeWithSnapshot()
AcknowledgeWithSnapshot marks the session as seen and baselines the current content hash. Called when user detaches from session.
func (*Session) ApplySharedAcknowledged ¶ added in v0.14.0
ApplySharedAcknowledged applies acknowledgment state replicated from SQLite. Unlike Acknowledge/ResetAcknowledged, this only synchronizes the ack flag and does not force an immediate status transition. GetStatus() will naturally map to waiting/idle on the next poll based on busy/prompt conditions.
func (*Session) ApplyThemeOptions ¶ added in v0.26.2
func (*Session) Attach ¶
Attach attaches to the tmux session with full PTY support. The configured detach key (default Ctrl+Q) will detach and return to the caller. Pass an optional detachByte to override the default (0x11 / Ctrl+Q).
Attach is a thin wrapper over AttachWithOptions that ignores the returned SwitchIntent — use it when session-switch keys are not needed.
func (*Session) AttachReadOnly ¶
AttachReadOnly attaches to the session in read-only mode
func (*Session) AttachWindow ¶ added in v0.21.0
AttachWindow attaches to a specific window within this tmux session. Selects the target window first, then uses the standard Attach flow.
func (*Session) AttachWithOptions ¶ added in v1.9.58
func (s *Session) AttachWithOptions(ctx context.Context, opts AttachOptions) (SwitchIntent, error)
AttachWithOptions attaches to the tmux session with full PTY support and the session-switch keys configured in opts. It returns the SwitchIntent the user requested (SwitchNone on a normal detach or when the pane process exits) so the caller can open an in-attach session switcher.
func (*Session) BackgroundWorkPending ¶ added in v1.10.9
BackgroundWorkPending reports whether a Claude session at the prompt still has background work in flight (run_in_background shells or a background agent the turn is awaiting). It captures the pane itself — for the UpdateStatus hook fast path, which short-circuits before GetStatus and so has no captured content — and caches the result briefly (bgWorkCacheTTL). Returns false for non-Claude sessions. Safe to call WITHOUT holding s.mu (acquires it internally; releases it for the slow capture).
func (*Session) CachedSubstate ¶ added in v1.9.66
CachedSubstate returns the last substate computed by GetStatus/GetSubstate WITHOUT capturing the pane. Use it on the TUI render hot path, where the background status loop already keeps the value fresh and a per-row capture would be too expensive.
func (*Session) CaptureFullHistory ¶
CaptureFullHistory captures the scrollback history (limited to last 2000 lines for performance)
func (*Session) CaptureHistoryLines ¶ added in v1.10.9
CaptureHistoryLines captures the last n lines of the pane's scrollback, preserving ANSI colors (-e). Unlike CaptureFullHistory (capped at 2000 for the preview pane) this is used by the in-attach scrollback pager (#1491), which needs a deep enough window to reach the start of a long session, so it runs with a generous timeout and an explicit -S -<n> lower bound.
n is clamped to at least 1. A one-off subprocess with a 10s timeout keeps a pathological capture from wedging the UI; on timeout it returns ErrCaptureTimeout so the caller can surface a non-fatal message.
func (*Session) CapturePane ¶
CapturePane captures the visible pane content. Tries control mode pipe first (zero subprocess), falls back to subprocess. Uses singleflight to deduplicate concurrent calls.
func (*Session) CapturePaneFresh ¶ added in v0.19.11
CapturePaneFresh captures pane content via a direct tmux subprocess call. Unlike CapturePane(), this bypasses the control-mode pipe and short-lived cache to provide a fresh snapshot. Use this for send verification where stale pane content can hide unsent composer input.
func (*Session) CaptureWindowFullHistory ¶ added in v0.21.0
CaptureWindowFullHistory captures the scrollback history of a specific window (last 2000 lines).
func (*Session) ConfigureStatusBar ¶ added in v0.3.0
func (s *Session) ConfigureStatusBar()
ConfigureStatusBar sets up the tmux status bar with session info. Shows: notification bar on left (managed by NotificationManager), session info on right. NOTE: status-left is reserved for the notification bar showing waiting sessions. Options defined in tmux options are respected — agent-deck skips those keys.
func (*Session) ConfigureTerminalTitle ¶ added in v1.7.21
func (s *Session) ConfigureTerminalTitle()
ConfigureTerminalTitle sets tmux options that drive the outer terminal tab or window title for this session.
func (*Session) DetectTool ¶
DetectTool detects which AI coding tool is running in the session Uses caching to avoid re-detection on every call
func (*Session) EnableMouseMode ¶
EnableMouseMode enables mouse scrolling, clipboard integration, and optimal settings Safe to call multiple times - just sets the options again
Enables: - mouse on: Mouse wheel scrolling, text selection, pane resizing - set-clipboard on: OSC 52 clipboard integration (works with modern terminals) - allow-passthrough on: OSC 8 hyperlinks, advanced escape sequences (tmux 3.2+) - escape-time 10: Fast Vim/editor responsiveness (default 500ms is too slow)
Terminal compatibility: - Warp, iTerm2, kitty, Alacritty, WezTerm: Full support (hyperlinks, clipboard, true color) - Windows Terminal, VS Code: Full support - Apple Terminal.app: Limited (no hyperlinks or clipboard)
Note: With mouse mode on, hold Shift while selecting to use native terminal selection instead of tmux's selection (useful for copying to system clipboard in some terminals)
func (*Session) EnsureConfigured ¶ added in v0.8.95
func (s *Session) EnsureConfigured()
EnsureConfigured runs deferred tmux configuration if not already done. PERFORMANCE: This should be called before attaching to a session or when the session needs full functionality (e.g., status bar, mouse mode).
Safe to call multiple times - does nothing if already configured or session doesn't exist. Thread-safe via mutex protection.
func (*Session) Exists ¶
Exists checks if the tmux session exists Uses cached session list when available (refreshed by RefreshExistingSessions) Falls back to direct tmux call if cache is stale
func (*Session) ExistsCached ¶ added in v1.10.9
ExistsCached is a cheap, non-blocking liveness check for hot periodic loops that iterate over ALL sessions every tick (e.g. the background configure loop and theme propagation). It NEVER spawns a subprocess on the calling goroutine: a single wedged/absent server multiplied by ~1000 sessions is the exact subprocess-storm that froze the UI main goroutine 4–7s per tick (see the nav-freeze fix). It answers only from in-process state:
- a POSITIVE session-cache hit on the session's OWN socket, or
- a live PipeManager control connection, or
- a POSITIVE hit in the per-socket cache (isolated sockets).
The socket guard mirrors Exists(): the shared sessionCache describes DefaultSocketName() only, so a same-named entry on a different socket is not this session (#755 / multi-socket cache aliasing). Sessions on an isolated socket are therefore answered from a SEPARATE per-socket cache, refreshed in the background by one `list-sessions` per socket per TTL. Without it those sessions would read false FOREVER (never in the default cache; PipeManager only connects wanted/attached sessions), permanently starving them of EnsureConfigured and theme propagation — a bug the "retries next tick" framing hid. Bounding the refresh by SOCKET count, not session count, keeps the no-storm invariant that motivated this method.
A false is still a deliberate under-report (cold cache, dead server, probe failure): the only cost is that a cosmetic loop skips this session until the cache warms. Callers MUST NOT feed ExistsCached() into destructive/restart machinery — use Exists() (which confirms a negative with a real probe).
func (*Session) ForceDetectTool ¶
ForceDetectTool forces a re-detection of the tool, ignoring cache
func (*Session) GetCachedWindowActivity ¶ added in v0.10.6
GetCachedWindowActivity returns the cached window_activity timestamp without spawning a subprocess. Returns 0 if the cache is stale or session not found. This is used for cheap idle-session activity gating in tiered polling.
func (*Session) GetEnvironment ¶ added in v0.5.0
GetEnvironment gets an environment variable from this tmux session. Uses a 30-second cache to avoid spawning tmux show-environment subprocesses on every poll cycle. Call InvalidateEnvCache() after SetEnvironment to clear.
func (*Session) GetLastActivityTime ¶ added in v0.5.6
GetLastActivityTime returns when the session content last changed Returns zero time if no activity has been tracked
func (*Session) GetMouse ¶ added in v1.7.68
GetMouse reports whether tmux mouse mode is currently enabled for this session. Used by tests and by the Start / EnableMouseMode code paths to decide whether to set `mouse on`.
func (*Session) GetSubstate ¶ added in v1.9.66
GetSubstate captures the pane once and returns the additive Honest-Status-v2 substate (see Substate). It is an independent read used by the status-reporting layers (CLI status --json, TUI label/glyph, transition events); it does NOT influence the canonical status returned by GetStatus, so existing status behavior stays byte-stable. Returns SubstateNone on a dead/absent pane, a capture failure, or a non-claude tool.
func (*Session) GetWaitingSince ¶ added in v0.8.51
GetWaitingSince returns when the session transitioned to waiting status Returns zero time if session has never been waiting
func (*Session) GetWindowActivity ¶ added in v0.3.0
GetWindowActivity returns Unix timestamp of last tmux window activity Uses cached data when available (refreshed by RefreshSessionCache) Falls back to direct tmux call if cache is stale
func (*Session) GetWorkDir ¶
GetWorkDir returns the current working directory of the tmux pane This is the live directory from the pane, not the initial WorkDir
func (*Session) HasUpdated ¶
HasUpdated checks if the pane content has changed since last check
func (*Session) InvalidateEnvCache ¶ added in v0.9.2
func (s *Session) InvalidateEnvCache()
InvalidateEnvCache clears the environment variable cache for this session. Should be called after SetEnvironment to ensure fresh reads.
func (*Session) IsAcknowledged ¶ added in v0.16.0
IsAcknowledged returns whether the session has been acknowledged by the user. Used by the hook fast path to distinguish waiting (orange) from idle (gray).
func (*Session) IsAltScreen ¶ added in v1.10.9
IsAltScreen reports whether the pane is currently showing the alternate screen — i.e. a full-screen TUI such as Claude in fullscreen mode. Such panes keep NO tmux scrollback (history_size stays 0 regardless of history-limit), so the in-attach scrollback pager (#1491) has nothing to capture and, worse, would swallow the PageUp the app itself uses to scroll. The attach loop consults this (via AttachOptions.ScrollbackGate) to leave bare PageUp for the app when it is fullscreen.
Bounded so a wedged server can never hang the keystroke that triggered the query. On error it returns (false, err); the caller preserves the pager on error rather than silently disabling a configured feature.
func (*Session) IsClaudeRunning ¶ added in v0.5.3
IsClaudeRunning checks if Claude appears to be running in the session Returns true if Claude indicators are found
func (*Session) IsConfigured ¶ added in v0.8.95
IsConfigured returns whether the session has been fully configured. Used for debugging and testing.
func (*Session) IsPaneDead ¶ added in v0.19.17
IsPaneDead returns true if the session's pane process has exited. Uses the cached pane info (refreshed once per tick) for zero-cost lookups. Falls back to a direct tmux query targeting pane 0.0 (the primary pane) to avoid false positives in multi-pane layouts.
func (*Session) Kill ¶
Kill terminates the tmux session. Like RespawnPane, this captures the process tree first and ensures all processes actually die. tmux kill-session sends SIGHUP which some CLI tools (e.g. Claude Code 2.1.27+) ignore, leaving orphan processes.
func (*Session) KillAndWait ¶ added in v1.7.68
KillAndWait is the synchronous variant of Session.Kill. When it returns, tmux kill-session has been run AND every pane process we captured before the kill has been verified dead (or reaped via SIGTERM/SIGKILL). Intended for short-lived CLI processes where the goroutine scheduled by Kill would be aborted on exit.
See issue #59 and the package-level docs above.
func (*Session) LastObservedActivity ¶ added in v1.9.48
LastObservedActivity returns the last time a real busy spike was observed for this tracker, plus a bool reporting whether such a spike has ever happened in this tracker's lifetime. When the bool is false the time is the zero value, so callers that miss the bool check still get a sentinel they can detect.
func (*Session) LogFile ¶ added in v0.3.0
LogFile returns the path to this session's log file Logs are stored under the XDG data directory, falling back to legacy logs.
func (*Session) OpenKeySender ¶ added in v1.9.24
OpenKeySender opens a persistent tmux control-mode client bound to this session's pane. Used by TUI insert mode (#1102) to amortize the fork+exec cost of `tmux send-keys` across a typing burst. Returns nil and an error when the user's tmux can't be reached or the session no longer exists; callers should fall back to per-call SendKeys / SendEnter / SendNamedKey.
func (*Session) ResetAcknowledged ¶
func (s *Session) ResetAcknowledged()
ResetAcknowledged marks the session as needing attention Call this when a hook event indicates the agent finished (Stop, AfterAgent) This ensures the session shows yellow (waiting) instead of gray (idle)
func (*Session) RespawnPane ¶ added in v0.5.4
RespawnPane kills the current process in the pane and starts a new command. This is more reliable than sending Ctrl+C and waiting for shell prompt. The -k flag kills the current process before respawning.
IMPORTANT: After respawn, this function verifies that old processes actually died. Some CLI tools (notably Claude Code 2.1.27+) ignore SIGHUP sent by tmux respawn-pane, leaving orphan processes that consume CPU indefinitely. If old processes survive, we escalate through SIGTERM → SIGKILL.
func (*Session) SendCommand ¶ added in v0.5.1
SendCommand sends a command to the tmux session and presses Enter
func (*Session) SendCtrlC ¶ added in v0.5.1
SendCtrlC sends Ctrl+C (interrupt signal) to the tmux session
func (*Session) SendEnter ¶
SendEnter sends an Enter key to the tmux session. When VimMode is set it first guarantees the composer is in insert mode (issue #1264) so the Enter submits the message instead of being consumed as a vim normal-mode motion. This covers the bare-Enter nudges the send-verify retry loop fires against a detected unsent prompt (cmd/agent-deck/session_cmd.go), which would otherwise all no-op in normal mode.
func (*Session) SendKeys ¶
SendKeys sends keys to the tmux session Uses -l flag to treat keys as literal text, preventing tmux special key interpretation
func (*Session) SendKeysAndEnter ¶ added in v0.12.2
SendKeysAndEnter sends literal text followed by Enter as two separate tmux calls with a short delay between them. The delay is necessary because tmux 3.2+ wraps send-keys -l in bracketed paste sequences (\e[200~...\e[201~). Without the delay, Enter arrives in the same PTY buffer as the paste-end marker and gets swallowed by async TUI frameworks (Ink/Node.js, curses).
func (*Session) SendKeysAndEnterToWindow ¶ added in v1.9.56
SendKeysAndEnterToWindow is SendKeysAndEnter aimed at a specific tmux window index rather than the session's active window. Quick-approve (#1369) uses it to deliver "1"+Enter to the exact window showing a Claude prompt, which is often not the active one in a multi-window session.
func (*Session) SendKeysChunked ¶ added in v0.8.80
SendKeysChunked sends large content to the tmux session in chunks to avoid tmux/OS buffer limits. Content ≤4KB is sent directly via SendKeys. Larger content is split at newline boundaries with a short delay between chunks.
func (*Session) SendNamedKey ¶ added in v1.9.23
SendNamedKey sends a single tmux named key (e.g. "BSpace", "Up", "Down", "Left", "Right", "Tab", "BTab", "C-c", "C-d") to the session. Unlike SendKeys it does NOT use the -l flag, so tmux interprets the argument as a key name rather than literal text. Used by insert mode (#1094) to forward Backspace, arrow keys, Tab, and Ctrl-{C,D} from the TUI to the focused pane.
func (*Session) SetClearOnRestart ¶ added in v1.4.1
SetClearOnRestart controls whether RespawnPane clears the scrollback buffer. When false (default), previous output is preserved on restart.
func (*Session) SetCustomPatterns ¶ added in v0.8.27
func (s *Session) SetCustomPatterns(toolName string, busyPatterns, promptPatterns, detectPatterns []string)
SetCustomPatterns sets custom patterns for generic tool support These patterns enable custom tools defined in config.toml to have proper status detection
func (*Session) SetDetectPatterns ¶ added in v0.9.2
SetDetectPatterns sets tool auto-detection patterns (separate from busy/prompt patterns).
func (*Session) SetEnvironment ¶ added in v0.5.0
SetEnvironment sets an environment variable for this tmux session
func (*Session) SetInjectStatusLine ¶ added in v0.15.0
SetInjectStatusLine controls whether ConfigureStatusBar modifies tmux settings. When set to false, the status bar is left unchanged, preserving user's tmux config.
func (*Session) SetMouse ¶ added in v1.7.68
SetMouse controls whether tmux mouse mode is enabled for this session. When false, the inline `mouse on` set-option during Start is skipped AND EnableMouseMode becomes a no-op — required for VS Code Linux integrated terminal click-drag selection (issue #730).
func (*Session) SetPatterns ¶ added in v0.9.2
func (s *Session) SetPatterns(p *ResolvedPatterns)
SetPatterns sets the compiled ResolvedPatterns for configurable status detection. When set, hasBusyIndicator and normalizeContent use these instead of hardcoded values.
func (*Session) SetTerminalChromeEnabled ¶ added in v1.7.72
SetTerminalChromeEnabled controls whether Attach emits outer-terminal chrome (currently the iTerm2 badge) on attach/detach. Mirrors the SetInjectStatusLine plumbing pattern: callers in internal/session read `[terminal].iterm_badge` from user config and forward it here. AGENTDECK_ITERM_BADGE overrides this at runtime; see chrome.go.
func (*Session) SplitShellPane ¶ added in v1.10.9
SplitShellPane adds a vertical split pane to this session running shell in workdir. If workdir is empty the pane inherits the session's current working directory. Issue #1470.
func (*Session) Start ¶
Start creates and starts a tmux session. By default, command is sent after session creation (legacy behavior). When RunCommandAsInitialProcess is true, command is passed directly to tmux new-session and becomes the pane's initial process.
func (*Session) StreamOutput ¶
StreamOutput streams the session output to the provided writer
func (*Session) WaitForReady ¶ added in v0.7.0
WaitForReady polls the terminal until the agent is ready for input Ready state = NO busy indicator AND prompt visible This works for Claude ("> "), Gemini, and other agents
type SessionState ¶
type SessionState string
SessionState represents the detected state of a session
const ( StateIdle SessionState = "idle" // No activity, waiting for user StateBusy SessionState = "busy" // Actively working (output changing) StateWaiting SessionState = "waiting" // Showing a prompt, needs input )
type SpinnerActivityTracker ¶ added in v0.14.0
type SpinnerActivityTracker struct {
// contains filtered or unexported fields
}
SpinnerActivityTracker tracks when the spinner was last detected on screen. Used for the grace period between tool calls where the spinner briefly disappears.
This is intentionally simple: spinner PRESENCE from the curated char set (which excludes ✻ done marker and · non-spinner) is the reliable signal. No movement tracking needed because the char set itself distinguishes active vs done.
func NewSpinnerActivityTracker ¶ added in v0.14.0
func NewSpinnerActivityTracker() *SpinnerActivityTracker
NewSpinnerActivityTracker creates a tracker with default grace period.
func (*SpinnerActivityTracker) InGracePeriod ¶ added in v0.14.0
func (sat *SpinnerActivityTracker) InGracePeriod() bool
InGracePeriod returns true if an active spinner was visible recently. This covers the brief gap between tool calls where the spinner disappears before the next tool starts.
func (*SpinnerActivityTracker) MarkBusy ¶ added in v0.14.0
func (sat *SpinnerActivityTracker) MarkBusy()
MarkBusy records that an active spinner char is currently visible on screen.
type StateTracker ¶
type StateTracker struct {
// contains filtered or unexported fields
}
StateTracker tracks content changes for notification-style status detection
StateTracker implements a simple 3-state model:
GREEN (active) = Content changed within 2 seconds YELLOW (waiting) = Content stable, user hasn't seen it GRAY (idle) = Content stable, user has seen it
type Substate ¶ added in v1.9.66
type Substate string
Substate is an ADDITIVE refinement of the coarse session status (Honest Status v2). It never changes the canonical status string ("running", "waiting", "idle", "error", "stopped"); it explains WHY a session is in that status so a supervisor (human or Maestro) can act precisely.
The motivating failure: a Fable no-op loop ("X is currently unavailable" / "Crunched for 0s") looked alive to a coarse observer. Distinguishing model-unavailable from a genuinely-running session makes "running" trustworthy.
const ( // SubstateNone means no distinct refinement applies (the coarse status is // already the whole story, e.g. a session making real progress). SubstateNone Substate = "" // SubstateRunning marks a session actively working (spinner / // "esc|ctrl+c to interrupt" present). Pairs with status "running". SubstateRunning Substate = "running" // SubstateIdleAtEmptyPrompt marks a session sitting at its input prompt // with no activity — genuinely idle, distinct from a session that LOOKS // idle but is actually wedged. Pairs with status "idle"/"waiting". SubstateIdleAtEmptyPrompt Substate = "idle-at-empty-prompt" // reports unavailable ("X is currently unavailable", "Crunched for 0s") // and the session cannot make progress despite looking alive. The single // most important new signal. Pairs with status "error". SubstateModelUnavailable Substate = "model-unavailable" // SubstateAuth401 marks an auth/connection failure banner ("Please run // /login", "API Error: 401", "socket connection closed"). Pairs with // status "error". Built on the #1400 error-banner detection. SubstateAuth401 Substate = "auth-401" )
type SwitchIntent ¶ added in v1.9.58
type SwitchIntent int
SwitchIntent reports why the attach loop handed control back to the caller: a plain detach/exit, an in-attach session switch, or an in-attach scrollback request (#1491).
const ( // SwitchNone means no switch was requested (normal detach / process exit). SwitchNone SwitchIntent = iota // SwitchRequested means the user pressed the switch key while attached. SwitchRequested // ScrollbackRequested means the user pressed the scrollback key while // attached and the caller should open the in-view scrollback pager. The // deck's Enter-attach owns the viewport, so tmux's own copy-mode is // unreachable there (#1491); this intent is the escape hatch. ScrollbackRequested )
type TerminalInfo ¶ added in v0.3.0
type TerminalInfo struct {
Name string // Terminal name (warp, iterm2, kitty, alacritty, etc.)
SupportsOSC8 bool // Supports OSC 8 hyperlinks
SupportsOSC52 bool // Supports OSC 52 clipboard
SupportsTrueColor bool // Supports 24-bit color
}
TerminalInfo contains detected terminal information
func GetTerminalInfo ¶ added in v0.3.0
func GetTerminalInfo() TerminalInfo
GetTerminalInfo returns detailed terminal capabilities
type TitleState ¶ added in v0.14.0
type TitleState int
TitleState represents the state inferred from the tmux pane title. Claude Code sets pane titles via OSC escape sequences:
- Braille spinner chars (U+2800-28FF) while actively working
- Done markers (✳✻✽✶✢) when a task completes
const ( TitleStateUnknown TitleState = iota // No recognizable pattern (non-Claude tools) TitleStateWorking // Braille spinner detected = actively working TitleStateDone // Done marker detected, fall through to prompt detection )
func AnalyzePaneTitle ¶ added in v0.14.0
func AnalyzePaneTitle(title, _ string) TitleState
AnalyzePaneTitle determines session state from the pane title. Priority: Braille spinner > Done marker > Unknown.
NOTE: We intentionally do NOT use pane_current_command to detect "exited" state. Claude Code frequently spawns bash subprocesses for tool execution, and tmux reports that child process as pane_current_command. This means a waiting Claude session often shows "bash" as the command, making it indistinguishable from "Claude exited and shell is showing". The existing Exists() check handles truly dead sessions reliably.
type VersionProbe ¶ added in v1.7.70
VersionProbe returns the raw output of `tmux -V`, e.g. "tmux 3.6a".
type WindowInfo ¶ added in v0.21.0
type WindowInfo struct {
Index int
Name string
Activity int64
Tool string // Detected tool (claude, gemini, etc.) or empty
}
WindowInfo holds basic info about a tmux window within a session.
func GetCachedWindows ¶ added in v0.21.0
func GetCachedWindows(sessionName string) []WindowInfo
GetCachedWindows returns cached window info for a session with tool data merged in. Returns a copy — callers cannot mutate the cache. Returns nil if not found or cache is stale.