session

package
v0.0.315 Latest Latest
Warning

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

Go to latest
Published: May 16, 2026 License: MIT Imports: 40 Imported by: 0

Documentation

Index

Constants

View Source
const (
	StatusRunning           = "running"
	StatusWaitingInput      = "waiting_input"
	StatusWaitingPermission = "waiting_permission"
	StatusExited            = "exited"
)

Status constants

View Source
const (
	StatusWorking            = "working"
	StatusMainAgentIdle      = "main_agent_idle"
	StatusIdle               = "idle"
	StatusAwaitingPermission = "awaiting_permission"
	StatusAwaitingInput      = "awaiting_input"
	// StatusError marks a turn that ended in an API/auth/billing error
	// (Claude Code's StopFailure hook). It is transient: the next
	// normal hook event overwrites it, so a retried agent leaves the
	// error state on its own. See the StopFailure case in hook_callback.go.
	StatusError = "error"
)

Valid status values for callbacks

View Source
const DefaultCleanupAge = 7 * 24 * time.Hour // 1 week

DefaultCleanupAge is the default max age for exited sessions in prune command

Variables

View Source
var ClaudeAncestorCheck = func() bool {
	return FindClaudePID() != 0
}

ClaudeAncestorCheck reports whether the current process is running underneath a Claude Code instance — i.e. whether a `claude`/`node` process appears anywhere in this process's own ancestry, walked up from os.Getppid().

It is a package var, not a plain func, so tests (and the agentd flow tests) can substitute a deterministic implementation — "pretend we have a claude ancestor" — without literally running the test binary under Claude Code. Production points it at FindClaudePID, the same process-tree walk agentd's identity middleware uses (via convIDForPID) to tell a human caller apart from an agent.

View Source
var ErrNestedClaudeSpawn = errors.New("refusing to launch a nested Claude Code session")

ErrNestedClaudeSpawn is the sentinel wrapped by the error GuardAgainstNestedSpawn returns, so callers/tests can match it with errors.Is regardless of the human-readable explanation appended.

View Source
var HookCommand string

HookCommand is the unified callback command for all hooks (detected at startup)

View Source
var JoinGroupHandler func(*NewParams) error

JoinGroupHandler implements `--join-group`. Set by the agent package's init() to avoid a session→agent import cycle (agent already depends on session for AttachToSession). When nil, --join-group falls back to a clear error.

View Source
var RequiredHooks map[string][]HookMatcher

RequiredHooks defines the hooks tclaude needs for status tracking All hooks use the same unified callback - it reads stdin and figures out what to do

Functions

func AttachCmd

func AttachCmd() *cobra.Command

func AttachToSession

func AttachToSession(sessionID, tmuxSession string, forceAttach bool) error

AttachToSession attaches to a tmux session. Sets terminal title for window focus, then replaces process with tmux attach. If forceAttach is true, detaches other clients before attaching (-d flag).

func AttachToTmuxSession

func AttachToTmuxSession(tmuxSession string) int

AttachToTmuxSession attaches to a tmux session, replacing the current process Returns exit code (0 = success) for use by other packages

func CheckHooksInstalled

func CheckHooksInstalled() (installed bool, missing []string, needsRepair bool)

CheckHooksInstalled checks if tclaude hooks are installed in Claude settings. Returns: installed (all required hooks present with current binary), missing event names, needsRepair (stale or duplicate hooks detected).

func CheckTmuxInstalled

func CheckTmuxInstalled() error

CheckTmuxInstalled verifies tmux is available

func ClaudeSettingsPath

func ClaudeSettingsPath() string

ClaudeSettingsPath returns the path to ~/.claude/settings.json

func CleanupOldExitedSessions

func CleanupOldExitedSessions(maxAge time.Duration) error

CleanupOldExitedSessions removes exited session states older than maxAge.

func Cmd

func Cmd() *cobra.Command

func ConfigureTmuxKeybindings added in v0.0.45

func ConfigureTmuxKeybindings()

ConfigureTmuxKeybindings sets up keybindings on the tclaude tmux server for session navigation. Prefix-less so they work without Ctrl+b.

Only enabled on macOS with iTerm2 or Terminal.app, since the goto command uses AppleScript to focus terminal windows/tabs.

Current bindings:

  • Shift+Right → goto next session
  • Shift+Left → goto prev session

func DeleteSessionState

func DeleteSessionState(id string) error

DeleteSessionState removes a session from the database.

func DetachSessionClients

func DetachSessionClients(sessionName string) (int, error)

DetachSessionClients detaches all clients from a tmux session and returns how many were detached. The tmux session — and the process running inside it — keeps running untouched; only the attached clients (the terminal windows) go away. A count of 0 means the session had no window open: a clean no-op.

func EnsureHooksInstalled

func EnsureHooksInstalled(autoInstall bool, stdout, stderr *os.File) bool

EnsureHooksInstalled checks and optionally installs hooks, returning true if ready. Stale hooks (wrong/duplicate binary) are always auto-repaired since the user already opted into hook management. The autoInstall flag only controls first-time installation.

func FindClaudePID

func FindClaudePID() int

FindClaudePID walks up the process tree from the current process to find a parent process named "claude" or "node" (Claude Code runs as node) Returns the PID of the Claude process, or 0 if not found

func FocusCmd

func FocusCmd() *cobra.Command

func FocusOwnWindow

func FocusOwnWindow() bool

FocusOwnWindow attempts to focus the current process's terminal window.

func FormatDuration

func FormatDuration(d time.Duration) string

FormatDuration formats a duration in a human-readable way

func GenerateSessionID

func GenerateSessionID() string

GenerateSessionID creates a short unique session ID

func GetCurrentTmuxSession

func GetCurrentTmuxSession() string

GetCurrentTmuxSession returns the current tmux session name if running inside tmux Returns empty string if not in tmux

func GetOwnWindowTitle

func GetOwnWindowTitle() string

GetOwnWindowTitle returns the title of the current terminal window.

func GetParentPID

func GetParentPID(pid int) int

GetParentPID returns the parent PID of a process Returns 0 if unable to determine

func GetProcessName

func GetProcessName(pid int) string

GetProcessName returns the name of a process Returns empty string if unable to determine

func GetSessionCompletions

func GetSessionCompletions(includeExited bool) []string

GetSessionCompletions returns completions for session IDs If includeExited is true, includes exited sessions (for kill command)

func GetTmuxSessionAttachedCount

func GetTmuxSessionAttachedCount(sessionName string) int

GetTmuxSessionAttachedCount returns the number of clients attached to a tmux session Returns 0 if session doesn't exist or on error

func GitLocationOf added in v0.0.249

func GitLocationOf(dir string) (worktreeRoot, branch string)

GitLocationOf resolves dir to its git worktree root and the branch checked out there. It shells out to git twice; both calls are best-effort — a dir outside any git repo (or a missing git binary) yields ("", ""), which is recorded faithfully as "not in a repo" rather than treated as an error. A detached HEAD reports an empty branch (there's no branch name to show).

The PostToolUse hook calls this once per file edit so an agent's current branch is stamped into agent_workdir at edit time — read surfaces then never have to run git themselves.

func GotoCmd added in v0.0.45

func GotoCmd() *cobra.Command

func GuardAgainstNestedSpawn added in v0.0.268

func GuardAgainstNestedSpawn() error

GuardAgainstNestedSpawn refuses to start a new Claude Code session when the calling `tclaude` process is itself running underneath a Claude Code instance. This stops a runaway chain of CC instances launching each other directly via `tclaude session new` (or bare `tclaude`).

Daemon-initiated spawns are deliberately unaffected: `tclaude agent spawn` / `groups resume` make the agentd daemon fork `tclaude session new`. agentd is started by the human and is not a CC instance, so a daemon-forked `session new` has agentd (and the human's shell) in its ancestry — no `claude`/`node` — and ClaudeAncestorCheck returns false for it. Agents that genuinely need another session go through `tclaude agent spawn`, which the daemon gates on the `groups.spawn` permission.

Known limitation: if the human starts `tclaude agentd serve` from inside a Claude Code session, daemon-forked spawns would inherit that claude ancestor and be refused too. agentd is expected to be launched from a plain shell / login / tray, so this is accepted rather than papered over with a bypass flag a CC instance could trivially set.

func HookCallbackCmd

func HookCallbackCmd() *cobra.Command

func InstallHooks

func InstallHooks() error

InstallHooks adds tclaude hooks to Claude settings, replacing any existing tclaude hooks

func IsProcessAlive

func IsProcessAlive(pid int) bool

IsProcessAlive checks if a process with the given PID is still running

func IsTmuxSessionAlive

func IsTmuxSessionAlive(sessionName string) bool

IsTmuxSessionAlive checks if a tmux session exists

func IsTmuxSessionAttached

func IsTmuxSessionAttached(sessionName string) bool

IsTmuxSessionAttached checks if a tmux session has any clients attached

func IsXdotoolInstalled

func IsXdotoolInstalled() bool

IsXdotoolInstalled checks if xdotool is available.

func KillCmd

func KillCmd() *cobra.Command

func ListCmd

func ListCmd() *cobra.Command

func MaxUpdatedAt added in v0.0.38

func MaxUpdatedAt() (time.Time, error)

MaxUpdatedAt returns the most recent updated_at across all sessions.

func NewCmd

func NewCmd() *cobra.Command

func NotifyListenCmd

func NotifyListenCmd() *cobra.Command

NotifyListenCmd returns a hidden command that sends a D-Bus notification and listens for action signals on the same connection. The notification and signal must use the same D-Bus connection because notification daemons send ActionInvoked signals only to the connection that created the notification. This runs as a detached background process spawned by sendLinuxClickable.

func ParsePIDFromTmux

func ParsePIDFromTmux(sessionName string) int

ParsePIDFromTmux gets the PID of the main process in a tmux session

func PruneCmd

func PruneCmd() *cobra.Command

func RefreshSessionStatus

func RefreshSessionStatus(state *SessionState)

RefreshSessionStatus updates the session status based on actual state

func RegisterJoinGroupCompletion added in v0.0.187

func RegisterJoinGroupCompletion(cmd *cobra.Command)

RegisterJoinGroupCompletion wires `--join-group` to suggest existing agent group names. Reads SQLite directly — completions fire on every <tab> keystroke, so they bypass the daemon (same convention as `tclaude agent groups …` completions). Exported so the top-level `tclaude` cobra cmd in pkg/claude/claude.go can register it too.

func ReinitHookCommand added in v0.0.85

func ReinitHookCommand()

ReinitHookCommand re-evaluates the hook command path using current DetectCmd settings. Call this after changing common.SetAbsolutePaths().

func ReplayCmd added in v0.0.59

func ReplayCmd() *cobra.Command

func RunInteractive

func RunInteractive(includeAll bool, state WatchState) (AttachResult, WatchState, error)

RunInteractive starts the interactive session viewer Returns the attach result (if any) and the final watch state

func RunNew

func RunNew(params *NewParams) error

RunNew is the exported entry point for running the new session command

func RunWatchMode

func RunWatchMode(includeAll bool, initialSort table.SortState, initialFilter, initialHide []string) error

RunWatchMode runs the interactive watch mode with attach support

func SaveSessionState

func SaveSessionState(state *SessionState) error

SaveSessionState saves session state to the database.

func SessionExists added in v0.0.38

func SessionExists(id string) (bool, error)

SessionExists checks whether a session with the given ID exists.

func ShortID

func ShortID(id string) string

ShortID returns the first 8 characters of an ID for display

func ShortenPath

func ShortenPath(path string, maxLen int) string

ShortenPath shortens a path for display

func SortSessionsByKey

func SortSessionsByKey(sessions []*SessionState, key string, dir table.SortDirection)

SortSessionsByKey sorts sessions by the given sort key and direction.

func StatusCallbackCmd

func StatusCallbackCmd() *cobra.Command

func TaskSignalPath added in v0.0.64

func TaskSignalPath(cwd string) string

TaskSignalPath returns a per-project path to the task signal file, allowing concurrent task runners in different projects.

func TryFocusAttachedSession

func TryFocusAttachedSession(tmuxSession string)

TryFocusAttachedSession attempts to focus the terminal window that has the session attached.

func TryFocusAttachedSessionWithID added in v0.0.181

func TryFocusAttachedSessionWithID(tmuxSession, sessionID string)

TryFocusAttachedSessionWithID is like TryFocusAttachedSession but takes the tclaude session ID (label) explicitly. The session ID is needed on WSL where the focus path searches Windows windows by the "tclaude:<id>" title pattern that setTerminalTitle stamps on each pane. Existing TryFocusAttachedSession reads the ID from $TCLAUDE_SESSION_ID, which is correct when called from `tclaude session focus` (CLI sets the env first) but not from the daemon (env points at the daemon's own session, if any).

func WorkDirFromToolUse added in v0.0.234

func WorkDirFromToolUse(toolName string, toolInput json.RawMessage, cwd string) (string, bool)

WorkDirFromToolUse inspects a Claude Code PostToolUse payload and returns the directory the tool acted in, when it can tell.

It looks only at the file-mutating tools (see fileMutatingTools) and returns the parent directory of the file they touched. Relative paths are resolved against cwd — Claude Code's launch directory, as reported in the hook payload. Returns ("", false) when the tool isn't a recognised file-mutator or carries no usable path.

Pure function: no I/O, no globals beyond the static tool set, so the derivation is straightforward to unit-test.

Types

type AttachParams

type AttachParams struct {
	ID    string `pos:"true" help:"Session ID to attach to"`
	Force bool   `short:"f" long:"force" help:"Attach even if session already has clients attached"`
}

type AttachResult

type AttachResult struct {
	TmuxSession string
	SessionID   string // session ID for inbox watcher
	ForceAttach bool   // true if we should detach other clients
	CreateNew   bool   // true if user wants to create a new session
	FocusOnly   bool   // true if we should just focus (session already attached)
}

AttachResult holds the result of selecting a session to attach

type FocusParams

type FocusParams struct {
	ID string `pos:"true" help:"Session ID to focus"`
}

type GotoParams added in v0.0.45

type GotoParams struct {
	Direction string `pos:"true" help:"Direction: next or prev"`
}

type HookCallbackInput

type HookCallbackInput struct {
	ConvID               string          `json:"session_id"` // claude's session id, what we call conv_id
	TranscriptPath       string          `json:"transcript_path"`
	Cwd                  string          `json:"cwd"`
	PermissionMode       string          `json:"permission_mode,omitempty"`
	HookEventName        string          `json:"hook_event_name"`
	NotificationType     string          `json:"notification_type,omitempty"`
	Reason               string          `json:"reason,omitempty"` // SessionEnd: clear | logout | prompt_input_exit | other
	Message              string          `json:"message,omitempty"`
	Prompt               string          `json:"prompt,omitempty"`
	StopHookActive       bool            `json:"stop_hook_active,omitempty"`
	ToolName             string          `json:"tool_name,omitempty"`
	ToolInput            json.RawMessage `json:"tool_input,omitempty"`
	AgentType            string          `json:"agent_type,omitempty"`
	AgentID              string          `json:"agent_id,omitempty"`
	LastAssistantMessage string          `json:"last_assistant_message,omitempty"`
	// StopFailure: error_type is one of rate_limit, authentication_failed,
	// oauth_org_not_allowed, billing_error, invalid_request, server_error,
	// max_output_tokens, unknown; error_message is the human-readable string.
	ErrorType    string `json:"error_type,omitempty"`
	ErrorMessage string `json:"error_message,omitempty"`
}

HookCallbackInput represents the JSON input from any Claude Code hook

type HookConfig

type HookConfig struct {
	Type    string `json:"type"`
	Command string `json:"command"`
}

HookConfig represents a single hook configuration

type HookInput

type HookInput struct {
	SessionID        string `json:"session_id"`
	Cwd              string `json:"cwd"`
	HookEventName    string `json:"hook_event_name"`
	NotificationType string `json:"notification_type,omitempty"`
}

HookInput represents the JSON input from Claude Code hooks

type HookMatcher

type HookMatcher struct {
	Matcher string       `json:"matcher,omitempty"`
	Hooks   []HookConfig `json:"hooks"`
}

HookMatcher represents a hook matcher configuration

type KillParams

type KillParams struct {
	ID    string `pos:"true" optional:"true" help:"Session ID to kill"`
	All   bool   `short:"a" long:"all" help:"Kill all sessions"`
	Idle  bool   `long:"idle" help:"Kill only idle sessions"`
	Force bool   `short:"f" long:"force" help:"Force kill without confirmation"`
}

type ListParams

type ListParams struct {
	JSON  bool     `long:"json" help:"Output as JSON"`
	All   bool     `short:"a" long:"all" help:"Include exited sessions"`
	Watch bool     `short:"w" long:"watch" help:"Interactive watch mode with auto-refresh"`
	Sort  string   `short:"s" long:"sort" optional:"true" help:"Sort by column" alts:"id,directory,status,age,updated"`
	Asc   bool     `long:"asc" help:"Sort ascending (default for id/directory/status)"`
	Desc  bool     `long:"desc" help:"Sort descending (default for updated)"`
	Show  []string `` /* 131-byte string literal not displayed */
	Hide  []string `long:"hide" optional:"true" help:"Hide these statuses" alts:"idle,working,awaiting_permission,awaiting_input,error,exited"`
}

type NewParams

type NewParams struct {
	Dir              string `short:"C" long:"dir" optional:"true" help:"Directory to start session in (defaults to current directory)"`
	Resume           string `long:"resume" short:"r" optional:"true" help:"Resume an existing conversation by ID"`
	Global           bool   `short:"g" help:"Search for conversation across all projects (with --resume)"`
	Label            string `long:"label" optional:"true" help:"Custom label for the session"`
	Detached         bool   `long:"detached" short:"d" help:"Start detached (don't attach to session)"`
	Compact          int    `long:"compact" optional:"true" help:"Auto-compact at this context usage percentage (overrides config)"`
	WaitForRateLimit bool   `long:"wait-for-rate-limit" short:"w" help:"Wait for rate limit (5-hour and 7-day) to reset before starting session"`

	// --join-group makes the new session auto-join an existing agent group
	// the moment its conv-id materialises. Routed through the daemon's
	// `groups.spawn` orchestration; not compatible with --resume / --label.
	JoinGroup string `` /* 144-byte string literal not displayed */
	Name      string `long:"name" optional:"true" help:"Name for the new agent in --join-group (e.g. 'reviewer'); becomes its conversation title"`
	Role      string `long:"role" optional:"true" help:"Role tag for the new member in --join-group (e.g. 'tech-lead')"`
	Descr     string `long:"descr" optional:"true" help:"Description of the new member's purpose in --join-group"`
}

type PruneParams

type PruneParams struct {
	MaxAge string `long:"max-age" help:"Max age for exited sessions (e.g., 24h, 7d, 1w)" default:"0s"`
	DryRun bool   `long:"dry-run" help:"Show what would be deleted without deleting"`
	All    bool   `short:"a" long:"all" help:"Delete all exited sessions regardless of age"`
}

type SessionState

type SessionState struct {
	ID            string    `json:"id"`
	TmuxSession   string    `json:"tmuxSession"`
	PID           int       `json:"pid"`
	Cwd           string    `json:"cwd"`
	ConvID        string    `json:"convId,omitempty"`
	Status        string    `json:"status"`
	StatusDetail  string    `json:"statusDetail,omitempty"`
	SubagentCount int       `json:"subagentCount"`
	Created       time.Time `json:"created"`
	Updated       time.Time `json:"updated"`
	LastHook      time.Time `json:"lastHook"`
	Attached      int       `json:"-"` // Number of attached clients (runtime only, not persisted)
}

SessionState represents the state of a Claude session

func FindSessionByConvID added in v0.0.38

func FindSessionByConvID(convID string) (*SessionState, error)

FindSessionByConvID finds a session by Claude conversation ID using an indexed lookup.

func ListSessionStates

func ListSessionStates() ([]*SessionState, error)

ListSessionStates returns all session states.

func LoadSessionState

func LoadSessionState(id string) (*SessionState, error)

LoadSessionState loads session state from the database.

type StatusCallbackParams

type StatusCallbackParams struct {
	Status string `pos:"true" help:"New status (working, idle, awaiting_permission, awaiting_input, error)"`
}

type TaskSignal added in v0.0.36

type TaskSignal struct {
	Report    string `json:"report"`
	SessionID string `json:"sessionId,omitempty"`
	Event     string `json:"event,omitempty"`    // hook event name (e.g. "Stop", "PermissionRequest")
	ToolName  string `json:"toolName,omitempty"` // tool name from the hook (e.g. "ExitPlanMode")
}

TaskSignal is the JSON structure written to the task signal file.

type WatchState

type WatchState struct {
	Sort         table.SortState
	StatusFilter []string
	HideFilter   []string
	SearchInput  string
	Cursor       int
}

WatchState holds state that persists between attach cycles

Jump to

Keyboard shortcuts

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