tui

package
v0.23.2 Latest Latest
Warning

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

Go to latest
Published: Mar 29, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

backlog.go detects when idle agents have ready work in the bead backlog. It runs a periodic check every 2 minutes: if any agent is idle without a bead AND bd has ready beads, it emits EventAgentIdle events so the operator knows dispatch is needed.

help.go renders the full-screen help reference card modal. Opened by typing "help" or "?" in the command modal. Closed by pressing Esc, backtick, or q.

ipc_lifecycle.go contains IPC handlers for pane lifecycle operations: stop, start, restart, add, and remove. These handlers create, replace, or destroy panes in the running TUI session.

Separated from ipc.go (which owns the socket server, router, and message-oriented handlers) to reduce merge conflicts when lifecycle and messaging logic are edited concurrently.

logger.go provides structured application logging for the TUI. Writes to .initech/initech.log with automatic rotation at 10MB. Uses log/slog for leveled, structured output.

Package tui implements a terminal multiplexer with PTY management, VT emulation via charmbracelet/x/vt, and a tcell-based rendering engine.

pane_journal.go contains JSONL session file watching, entry parsing, activity state derivation, and event detection (bead claims, completions, stalls, stuck loops). The watchJSONL goroutine polls for new entries and feeds them into the pane's ring buffer and event detectors.

pane_render.go contains the Render method and visual conversion helpers for drawing a pane's terminal content and ribbon onto the tcell screen.

pid.go manages the .initech/initech.pid file and post-mortem crash detection.

On startup: write current PID. On clean exit: delete it. If the file exists at startup, the previous run exited uncleanly (signal, OOM, cgo crash). We log a warning and query the macOS system log and DiagnosticReports for evidence of what happened.

Package tui resource management.

resource.go is the home for all resource-aware agent lifecycle code: memory monitoring, auto-suspend policy, and resume-on-message. All of this is gated behind the autoSuspend bool on the TUI struct.

When autoSuspend is false (the default), nothing in this file runs. The memory monitor goroutine is never started, the suspend policy never checks, and agents are never automatically suspended or resumed.

signals.go installs OS signal handlers so every external termination leaves a trace in initech.log before the process exits.

Without this, SIGTERM/SIGHUP/SIGKILL from the OS kill the process silently and leave the terminal in raw mode (screen.Fini never runs). The handlers here fix that for catchable signals. SIGKILL still can't be caught — for that case, the PID file + system log check in pid.go provides post-mortem evidence.

stderr_linux.go redirects os.Stderr (fd 2) to .initech/stderr.log at the OS file-descriptor level. This must happen before screen.Init() puts the terminal into raw mode, so that cgo/native crash stack traces are written to a file rather than into the garbled terminal buffer.

Go's own panic handler writes through os.Stderr (Go level), which also goes through fd 2, so this captures both Go panics and cgo crashes.

Uses syscall.Dup3 instead of syscall.Dup2 because Dup2 is not available on linux/arm64 (the kernel exposes only dup3 on that architecture). Dup3 with flags=0 is semantically identical to Dup2.

Index

Constants

View Source
const DefaultStallThreshold = 10 * time.Minute

DefaultStallThreshold is the duration of inactivity before an agent with an assigned bead is considered stalled.

Variables

This section is empty.

Functions

func DeleteLayout

func DeleteLayout(projectRoot string) error

DeleteLayout removes .initech/layout.yaml. Returns nil if the file doesn't exist (idempotent).

func EmitEvent

func EmitEvent(ch chan<- AgentEvent, ev AgentEvent)

EmitEvent sends an event to the TUI's event channel without blocking. If the channel is full, the event is dropped (producers must not stall).

func InitLogger

func InitLogger(projectRoot string, level slog.Level) func()

InitLogger sets up the application logger writing to .initech/initech.log. level is the minimum severity (slog.LevelDebug for verbose, slog.LevelInfo for normal). Safe to call multiple times; subsequent calls replace the logger. Returns a cleanup function that closes the log file.

func LogDebug

func LogDebug(component string, msg string, args ...any)

LogDebug logs at DEBUG level with a component tag.

func LogError

func LogError(component string, msg string, args ...any)

LogError logs at ERROR level with a component tag.

func LogInfo

func LogInfo(component string, msg string, args ...any)

LogInfo logs at INFO level with a component tag.

func LogWarn

func LogWarn(component string, msg string, args ...any)

LogWarn logs at WARN level with a component tag.

func Run

func Run(cfg Config) error

Run starts the TUI event loop. Blocks until the user quits.

func SaveLayout

func SaveLayout(projectRoot string, state LayoutState) error

SaveLayout writes the layout state to .initech/layout.yaml using atomic write (temp file + rename) to prevent corruption. Creates .initech/ if it doesn't exist.

func SocketPath

func SocketPath(projectRoot, projectName string) string

SocketPath returns the socket path for a project. The socket is placed inside the project's .initech/ directory (not /tmp/) so it is scoped to the project and not world-visible.

Types

type ActivityState

type ActivityState int

ActivityState describes what an agent is doing based on JSONL session tailing.

const (
	StateRunning   ActivityState = iota // Claude is processing.
	StateIdle                           // Waiting for input.
	StateDead                           // Process has exited; pane is no longer alive.
	StateSuspended                      // Auto-suspended by resource policy. Eligible for resume.
)

func (ActivityState) String

func (s ActivityState) String() string

String returns a human-readable label for the state.

type AgentEvent

type AgentEvent struct {
	Type   EventType
	Pane   string    // Agent name (e.g., "eng1").
	BeadID string    // Relevant bead ID (empty if N/A).
	Detail string    // Human-readable description.
	Time   time.Time // When the event was detected.
}

AgentEvent represents a semantic event from an agent's activity. Emitted by JSONL watchers and consumed by the TUI main loop.

type AgentInfo

type AgentInfo struct {
	Name            string
	Status          string        // Display text: activity string or bead ID.
	Activity        ActivityState // Actual activity state for dot color.
	Visible         bool
	IdleWithBacklog bool // True when idle with ready beads in the backlog.
	BacklogCount    int  // Number of ready beads (when IdleWithBacklog is true).
	Pinned          bool // True when operator has pinned this agent.
}

AgentInfo describes an agent for the status overlay.

type Config

type Config struct {
	Agents            []PaneConfig                          // One entry per agent pane.
	ProjectName       string                                // Used for socket path.
	ProjectRoot       string                                // Project root for .initech/ layout persistence.
	ResetLayout       bool                                  // Ignore saved layout and start with defaults.
	Verbose           bool                                  // Enable DEBUG-level logging (default: INFO).
	Version           string                                // Build version for crash reports.
	AutoSuspend       bool                                  // Enable resource-aware auto-suspend/resume.
	PressureThreshold int                                   // RSS percentage threshold (0 uses default 85).
	PaneConfigBuilder func(name string) (PaneConfig, error) // Optional factory for hot-add. Nil disables add command.
}

Config controls what agents the TUI launches.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns a config with standard shell-only agents.

type Divider

type Divider struct {
	X, Y     int
	Len      int
	Vertical bool
}

Divider describes a vertical or horizontal line between panes.

type EventType

type EventType int

EventType classifies semantic events from agent activity detection.

const (
	EventBeadCompleted     EventType = iota // Agent finished a bead (DONE comment, ready_for_qa).
	EventBeadClaimed                        // Agent claimed a bead (in_progress).
	EventBeadFailed                         // QA failed a bead or agent reported failure.
	EventAgentStalled                       // No output for configurable threshold (warning).
	EventAgentStuck                         // Extended inactivity or error loop detected.
	EventAgentIdle                          // Agent returned to idle after work.
	EventAgentIdleWithBead                  // Agent went running->idle while holding a bead.
	EventAgentSuspended                     // Agent auto-suspended by resource pressure policy.
	EventAgentResumed                       // Agent resumed from suspension (triggered by message).
)

func (EventType) String

func (e EventType) String() string

String returns a human-readable label for the event type.

type IPCRequest

type IPCRequest struct {
	Action string `json:"action"` // "send", "peek", "list"
	Target string `json:"target"` // Role name (for send/peek).
	Text   string `json:"text"`   // Text to inject (for send).
	Lines  int    `json:"lines"`  // Number of lines to return (for peek, 0 = all).
	Enter  bool   `json:"enter"`  // Append Enter after text (for send).
}

IPCRequest is the JSON structure sent by CLI commands to the TUI socket.

type IPCResponse

type IPCResponse struct {
	OK    bool   `json:"ok"`
	Error string `json:"error,omitempty"`
	Data  string `json:"data,omitempty"` // Pane content for peek, pane list for list.
}

IPCResponse is the JSON structure returned by the TUI socket.

type JournalEntry

type JournalEntry struct {
	Type      string    // "user", "assistant", "progress", "system", "last-prompt", etc.
	Content   string    // Text content (assistant message, tool output). Capped at 4KB.
	ToolName  string    // For tool_use/tool_result: which tool was called.
	ExitCode  int       // For Bash tool results: exit code if available.
	Timestamp time.Time // When this entry was written.
}

JournalEntry represents a parsed JSONL entry from a Claude Code session.

type LayoutMode

type LayoutMode int

LayoutMode determines how panes are arranged on screen.

const (
	LayoutFocus LayoutMode = iota // Single pane, full screen.
	LayoutGrid                    // Arbitrary NxM grid.
	Layout2Col                    // Main pane left, stacked right.
)

type LayoutState

type LayoutState struct {
	Mode     LayoutMode      `yaml:"mode"`
	GridCols int             `yaml:"grid_cols"`
	GridRows int             `yaml:"grid_rows"`
	Zoomed   bool            `yaml:"zoomed,omitempty"`
	Focused  string          `yaml:"focused"`          // Pane name, not index.
	Hidden   map[string]bool `yaml:"hidden,omitempty"` // Pane names that are hidden.
	Pinned   map[string]bool `yaml:"pinned,omitempty"` // Pane names protected from auto-suspend.
	Order    []string        `yaml:"order,omitempty"`  // Pane names in display order (from show command).
	Overlay  bool            `yaml:"overlay"`

	// Per-column and per-row proportional sizing (future).
	// nil means uniform. Values are relative weights (e.g., [60, 40] = 60%/40%).
	ColWeights []int `yaml:"col_weights,omitempty"`
	RowWeights []int `yaml:"row_weights,omitempty"`
}

LayoutState captures the complete layout intent. It is the single authority on what the screen should look like. Trivially serializable to YAML for persistent layout.

func DefaultLayoutState

func DefaultLayoutState(paneNames []string) LayoutState

DefaultLayoutState creates a LayoutState with auto-calculated grid dimensions for the given pane names.

func LoadLayout

func LoadLayout(projectRoot string, paneNames []string) (LayoutState, bool)

LoadLayout reads .initech/layout.yaml and merges it into a LayoutState, filtering stale pane references. Returns false if the file doesn't exist, is empty, contains invalid YAML, or would result in all panes hidden.

type Pane

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

Pane represents a terminal pane backed by a PTY process. It uses a SafeEmulator from charmbracelet/x/vt for terminal emulation.

func NewPane

func NewPane(cfg PaneConfig, rows, cols int) (*Pane, error)

NewPane creates a terminal pane running the configured command (or $SHELL).

func (*Pane) Activity

func (p *Pane) Activity() ActivityState

Activity returns the current activity state based on JSONL session tailing.

func (*Pane) BacklogCount

func (p *Pane) BacklogCount() int

BacklogCount returns the number of ready beads at the last idle-with-backlog detection.

func (*Pane) BeadID

func (p *Pane) BeadID() string

BeadID returns the currently assigned bead ID, or empty string.

func (*Pane) ClearIdleWithBacklog

func (p *Pane) ClearIdleWithBacklog()

ClearIdleWithBacklog clears the idle-with-backlog indicator.

func (*Pane) Close

func (p *Pane) Close()

Close terminates the PTY, kills the process, and signals goroutines to exit.

func (*Pane) DrainQueue

func (p *Pane) DrainQueue() []QueuedMessage

DrainQueue returns all queued messages in FIFO order and clears the queue. Called on resume to deliver buffered messages.

Caller must be on the main goroutine (via runOnMain).

func (*Pane) EnqueueMessage

func (p *Pane) EnqueueMessage(text string, enter bool) bool

EnqueueMessage appends a message to the pane's queue. If the queue is at capacity (maxMessageQueue), the oldest message is dropped. Returns true if a message was dropped to make room.

Caller must be on the main goroutine (via runOnMain).

func (*Pane) ForwardMouse

func (p *Pane) ForwardMouse(ev uv.MouseEvent)

ForwardMouse sends a mouse event to the emulator with pane-local content coordinates translated to emulator coordinates. The emulator silently drops the event if the child process hasn't enabled mouse reporting.

func (*Pane) IdleWithBacklog

func (p *Pane) IdleWithBacklog() bool

IdleWithBacklog returns true when the pane is idle and ready beads exist.

func (*Pane) InResumeGrace

func (p *Pane) InResumeGrace() bool

InResumeGrace returns true if the pane is within the post-resume grace period. During this window the pane is exempt from auto-suspend and idle-with-bead notifications.

func (*Pane) InScrollback

func (p *Pane) InScrollback() bool

InScrollback returns true when the pane is viewing scrollback history.

func (*Pane) IsAlive

func (p *Pane) IsAlive() bool

IsAlive returns whether the pane's shell process is still running.

func (*Pane) IsPinned

func (p *Pane) IsPinned() bool

IsPinned reports whether the operator has pinned this pane to prevent auto-suspension. Pinned panes are always kept running.

func (*Pane) IsSuspended

func (p *Pane) IsSuspended() bool

IsSuspended returns whether the pane has been stopped by the auto-suspend policy. A suspended pane is distinct from dead (crashed) and will auto-resume when a message arrives.

func (*Pane) LastOutputTime

func (p *Pane) LastOutputTime() time.Time

LastOutputTime returns the last time PTY output was received.

func (*Pane) MemoryRSS

func (p *Pane) MemoryRSS() int64

MemoryRSS returns the pane's last polled RSS in kilobytes. Returns 0 if the memory monitor has not yet polled or the process is dead.

func (*Pane) QueueLen

func (p *Pane) QueueLen() int

QueueLen returns the number of messages waiting in the queue.

func (*Pane) RecentEntries

func (p *Pane) RecentEntries() []JournalEntry

RecentEntries returns a copy of the recent JSONL entries ring buffer.

func (*Pane) Render

func (p *Pane) Render(screen tcell.Screen, focused bool, dimmed bool, index int, sel Selection)

Render draws the pane's bottom ribbon and terminal content onto the tcell screen. When dimmed is true, foreground colors are reduced to ~70% brightness. The index parameter is the 1-based pane number shown in the ribbon badge. All writes are clamped to the pane's region to prevent bleed-through.

func (*Pane) Resize

func (p *Pane) Resize(rows, cols int)

Resize updates the emulator and PTY dimensions.

func (*Pane) ScrapeQuota added in v0.21.3

func (p *Pane) ScrapeQuota() int

ScrapeQuota reads the emulator's status bar rows and extracts the quota percentage ("N% of limit"). Returns 0-100 on success, -1 if not found. Skips panes in alt-screen mode (vim, less) where the status bar is hidden.

func (*Pane) ScrollDown

func (p *Pane) ScrollDown(n int)

ScrollDown moves the viewport down (toward live output) by n rows. When scrollOffset reaches 0, the pane returns to live view.

func (*Pane) ScrollUp

func (p *Pane) ScrollUp(n int)

ScrollUp moves the viewport up (into scrollback history) by n rows.

func (*Pane) SendKey

func (p *Pane) SendKey(ev *tcell.EventKey)

SendKey translates a tcell key event into a charmbracelet KeyPressEvent and sends it through the emulator, which encodes it for the PTY.

func (*Pane) SendPaste

func (p *Pane) SendPaste(start bool)

SendPaste writes a bracketed paste marker to the PTY. On start=true it writes \x1b[200~ (paste start); on start=false it writes \x1b[201~ (paste end). The child process uses these delimiters to distinguish pasted content from typed keystrokes. No-op if the PTY is not open.

func (*Pane) SessionDesc

func (p *Pane) SessionDesc() string

SessionDesc returns the session description extracted from Claude's cursor row.

func (*Pane) SetBead

func (p *Pane) SetBead(id, title string)

SetBead sets the current bead ID and title.

func (*Pane) SetIdleWithBacklog

func (p *Pane) SetIdleWithBacklog(n int)

SetIdleWithBacklog marks the pane as idle with n ready beads in the backlog.

func (*Pane) SetPinned

func (p *Pane) SetPinned(v bool)

SetPinned marks the pane as pinned (true) or unpinned (false).

func (*Pane) SetResumeGrace

func (p *Pane) SetResumeGrace(until time.Time)

SetResumeGrace sets the post-resume grace period expiration.

func (*Pane) SetSuspended

func (p *Pane) SetSuspended(v bool)

SetSuspended marks the pane as suspended or resumed by the auto-suspend policy.

func (*Pane) SetVisible

func (p *Pane) SetVisible(v bool)

SetVisible controls whether the pane appears in the layout. Hiding a pane does not stop its process or resize its emulator.

func (*Pane) Start

func (p *Pane) Start()

Start launches the pane's background goroutines (PTY reader, response loop, JSONL watcher). Must be called after safeGo and eventCh are wired. If safeGo is nil, falls back to bare goroutine launches.

func (*Pane) Visible

func (p *Pane) Visible() bool

Visible returns whether the pane is included in the current layout. Hidden panes keep their emulator running at their last visible size.

type PaneConfig

type PaneConfig struct {
	Name    string   // Display name (role name).
	Command []string // Command + args. Empty means use $SHELL.
	Dir     string   // Working directory. Empty means inherit.
	Env     []string // Extra env vars (KEY=VALUE). TERM is always set.
}

PaneConfig describes how to launch a pane's process.

type PaneRender

type PaneRender struct {
	Pane    *Pane
	Region  Region
	Index   int  // 1-based pane number (position in full pane list).
	Focused bool // Receives keyboard input.
	Dimmed  bool // Render with reduced contrast.
}

PaneRender describes how to render a single pane.

type PersistentLayout

type PersistentLayout struct {
	Grid   string   `yaml:"grid"`             // e.g. "3x2"
	Hidden []string `yaml:"hidden,omitempty"` // Role names of hidden panes.
	Pinned []string `yaml:"pinned,omitempty"` // Role names protected from auto-suspend.
	Order  []string `yaml:"order,omitempty"`  // Pane display order (from show command).
	Mode   string   `yaml:"mode"`             // "grid", "focus", "main"
}

PersistentLayout is the subset of LayoutState that survives sessions. Focused pane is deliberately excluded (momentary choice, not a preference). Overlay and weights are excluded (not layout-changing from the user's perspective).

type QueuedMessage

type QueuedMessage struct {
	Text  string
	Enter bool
	Time  time.Time
}

QueuedMessage is a message waiting to be delivered to a suspended pane. On resume, the queue is drained in FIFO order via injectText.

type Region

type Region struct {
	X, Y, W, H int
}

Region defines a rectangular area on screen (outer bounds including border).

func (Region) InnerSize

func (r Region) InnerSize() (cols, rows int)

InnerSize returns the renderable content area (full width, minus 1 row for bottom ribbon).

type RenderPlan

type RenderPlan struct {
	Panes    []PaneRender // One entry per pane to draw (ordered).
	Dividers []Divider    // Vertical lines between pane columns.
	ScreenW  int
	ScreenH  int

	// ValidatedFocus is the name of the pane that actually receives input.
	// May differ from LayoutState.Focused if that pane is hidden.
	ValidatedFocus string
}

RenderPlan is the complete set of instructions for one frame. Produced by computeLayout, consumed by the render loop.

type Selection

type Selection struct {
	Active         bool
	StartX, StartY int
	EndX, EndY     int
}

Selection describes a text selection range in pane-local content coordinates.

type TUI

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

TUI is the main terminal multiplexer. It owns the tcell screen, a set of terminal panes, and handles input routing, layout, and rendering.

func (*TUI) PressureThreshold

func (t *TUI) PressureThreshold() int

PressureThreshold returns the configured RSS percentage above which agents may be auto-suspended. Returns 85 (the default) when not explicitly set.

func (*TUI) ResourceEnabled

func (t *TUI) ResourceEnabled() bool

ResourceEnabled reports whether resource-aware auto-suspend is active for this TUI instance. All resource management code should check this gate before taking any action.

func (*TUI) SystemMemoryAvailable

func (t *TUI) SystemMemoryAvailable() int64

SystemMemoryAvailable returns the last polled available system RAM in kilobytes. Returns 0 if not yet polled or if the query failed.

func (*TUI) SystemMemoryTotal

func (t *TUI) SystemMemoryTotal() int64

SystemMemoryTotal returns total system RAM in kilobytes.

Jump to

Keyboard shortcuts

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