manager

package
v0.2.2 Latest Latest
Warning

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

Go to latest
Published: Feb 22, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DetectedOption

type DetectedOption struct {
	Number     int    // The option number (1, 2, 3, etc.) or letter index (A=1, B=2, etc.)
	Letter     string // The option letter if letter-based (A, B, C, etc.), empty if numeric
	Text       string // The option text
	GroupIndex int    // Which group this option belongs to (0-indexed)
}

DetectedOption represents a numbered or lettered option found in Claude's response

type DiffStats

type DiffStats struct {
	FilesChanged int
	Additions    int
	Deletions    int
}

DiffStats holds file change statistics for the header display

type MergeType

type MergeType int

MergeType represents the type of merge/PR operation. Using a typed enum instead of strings like "merge" or "pr" provides compile-time safety and clearer code.

const (
	// MergeTypeNone indicates no merge operation is in progress.
	MergeTypeNone MergeType = iota

	// MergeTypeMerge indicates a direct merge to main branch.
	MergeTypeMerge

	// MergeTypePR indicates creating a pull request.
	MergeTypePR

	// MergeTypeParent indicates merging a child session back to its parent.
	MergeTypeParent

	// MergeTypePush indicates pushing updates to an existing PR.
	MergeTypePush
)

func (MergeType) String

func (t MergeType) String() string

String returns a human-readable name for the merge type.

type RunnerFactory

type RunnerFactory func(sessionID, workingDir, repoPath string, sessionStarted bool, initialMessages []claude.Message) claude.RunnerInterface

RunnerFactory creates a runner for a session. This allows tests to inject mock runners.

type SelectResult

type SelectResult struct {
	Runner     claude.RunnerInterface
	Messages   []claude.Message
	HeaderName string     // Branch name if custom, otherwise session name
	BaseBranch string     // Base branch this session was created from
	DiffStats  *DiffStats // Git diff statistics for the worktree

	// State to restore
	WaitStart             time.Time
	IsWaiting             bool
	ContainerInitializing bool      // true during container startup
	ContainerInitStart    time.Time // When container init started
	Permission            *mcp.PermissionRequest
	Question              *mcp.QuestionRequest
	PlanApproval          *mcp.PlanApprovalRequest
	TodoList              *claude.TodoList
	Streaming             string
	SavedInput            string
	SubagentModel         string // Active subagent model (empty if none)
}

SelectResult contains all the state needed by the UI after selecting a session. This allows SessionManager to handle data operations while app.go handles UI updates.

type SessionManager

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

SessionManager handles session lifecycle operations including runner management, state coordination, and message persistence. It encapsulates the relationship between sessions, runners, and per-session state.

func NewSessionManager

func NewSessionManager(cfg SessionManagerConfig, gitSvc *git.GitService) *SessionManager

NewSessionManager creates a new session manager.

func (*SessionManager) AddAllowedTool

func (sm *SessionManager) AddAllowedTool(sessionID string, tool string)

AddAllowedTool adds a tool to the allowed list for a session's repo and updates the runner.

func (*SessionManager) ConfigureRunnerDefaults added in v0.2.0

func (sm *SessionManager) ConfigureRunnerDefaults(runner claude.RunnerInterface, sess *config.Session)

ConfigureRunnerDefaults applies default policy configuration to a runner based on the session's properties. This includes tools, supervisor mode, host tools, container mode, MCP servers, and streaming settings.

This method is intended for the TUI and other consumers that want the "standard" configuration. Headless consumers (like the daemon/agent) should configure runners explicitly using the runner's Set* methods and the tool catalog in claude/tools.go.

func (*SessionManager) DeleteSession

func (sm *SessionManager) DeleteSession(sessionID string) claude.RunnerInterface

DeleteSession cleans up all resources for a deleted session. Returns the runner if it existed (so caller can check if it was active).

func (*SessionManager) GetOrCreateRunner

func (sm *SessionManager) GetOrCreateRunner(sess *config.Session) claude.RunnerInterface

GetOrCreateRunner returns an existing runner or creates a new one for the session. Uses double-checked locking to prevent race conditions where multiple goroutines could create duplicate runners for the same session. This is safe to call concurrently from multiple goroutines.

func (*SessionManager) GetRunner

func (sm *SessionManager) GetRunner(sessionID string) claude.RunnerInterface

GetRunner returns the runner for a session, or nil if none exists.

func (*SessionManager) GetRunners

func (sm *SessionManager) GetRunners() map[string]claude.RunnerInterface

GetRunners returns a copy of all runners (for safe iteration). The returned map is a snapshot - concurrent modifications to the original will not affect it.

func (*SessionManager) GetSession

func (sm *SessionManager) GetSession(sessionID string) *config.Session

GetSession returns the session config for a given session ID.

func (*SessionManager) HasActiveStreaming

func (sm *SessionManager) HasActiveStreaming() bool

HasActiveStreaming returns true if any session is currently streaming.

func (*SessionManager) SaveMessages

func (sm *SessionManager) SaveMessages(sessionID string) error

SaveMessages saves the current messages from a runner to disk.

func (*SessionManager) SaveRunnerMessages

func (sm *SessionManager) SaveRunnerMessages(sessionID string, runner claude.RunnerInterface) error

SaveRunnerMessages saves messages for a specific runner (used when runner reference is already available).

func (*SessionManager) Select

func (sm *SessionManager) Select(sess *config.Session, previousSessionID string, previousInput string, previousStreaming string) *SelectResult

Select prepares a session for activation, creating or reusing a runner, and gathering all state needed for UI restoration. The caller (app.go) is responsible for saving the previous session's state before calling this.

func (*SessionManager) SetGitService

func (sm *SessionManager) SetGitService(svc *git.GitService)

SetGitService sets the git service (for testing/demos).

func (*SessionManager) SetRunner

func (sm *SessionManager) SetRunner(sessionID string, runner claude.RunnerInterface)

SetRunner sets a runner for a session (used when manually creating runners).

func (*SessionManager) SetRunnerFactory

func (sm *SessionManager) SetRunnerFactory(factory RunnerFactory)

SetRunnerFactory sets a custom runner factory (for testing).

func (*SessionManager) SetSkipMessageLoad

func (sm *SessionManager) SetSkipMessageLoad(skip bool)

SetSkipMessageLoad configures whether to skip loading messages from disk. This is useful for demos and tests where clean state is needed.

func (*SessionManager) Shutdown

func (sm *SessionManager) Shutdown()

Shutdown stops all runners gracefully. This should be called when the application is exiting to ensure all Claude CLI processes are terminated and resources are cleaned up.

func (*SessionManager) StateManager

func (sm *SessionManager) StateManager() *SessionStateManager

StateManager returns the underlying session state manager for direct state access. This is needed for operations that don't warrant a full SessionManager method.

type SessionManagerConfig

type SessionManagerConfig interface {
	GetSession(id string) *config.Session
	GetSessions() []config.Session
	GetAllowedToolsForRepo(repoPath string) []string
	GetMCPServersForRepo(repoPath string) []config.MCPServer
	GetContainerImage() string
	AddRepoAllowedTool(repoPath, tool string) bool
	Save() error
}

SessionManagerConfig defines the configuration interface required by SessionManager. This decouples SessionManager from the concrete config.Config struct.

*config.Config satisfies this interface implicitly.

type SessionState

type SessionState struct {

	// Permission, question, and plan approval handling
	PendingPermission   *mcp.PermissionRequest
	PendingQuestion     *mcp.QuestionRequest
	PendingPlanApproval *mcp.PlanApprovalRequest

	// Merge/PR operation state
	MergeChan   <-chan git.Result
	MergeCancel context.CancelFunc
	MergeType   MergeType // What operation is in progress

	// Claude streaming state
	StreamCancel context.CancelFunc
	WaitStart    time.Time // When the session started waiting for Claude
	IsWaiting    bool      // Whether we're waiting for Claude response

	// UI state preserved when switching sessions
	InputText          string    // Saved input text
	StreamingContent   string    // In-progress streaming content
	StreamingStartTime time.Time // When streaming started (for elapsed time display)
	ToolUsePos         int       // Position of tool use marker for replacement

	// Tool use rollup for non-active sessions
	ToolUseRollup *ToolUseRollupState // Current rollup group (nil when no tool uses yet)

	// Parallel options state
	DetectedOptions []DetectedOption // Options detected in last assistant message

	// Queued message to send when streaming completes
	PendingMessage string

	// Initial message to send when session is first selected (for issue imports)
	InitialMessage string

	// Current todo list from TodoWrite tool
	CurrentTodoList *claude.TodoList

	// Subagent indicator - model name when subagent is active (empty when none)
	SubagentModel string

	// Container initialization state (for containerized sessions)
	ContainerInitializing bool      // true during container startup
	ContainerInitStart    time.Time // When container init started

	// Pending merge child request ID (for supervisor MCP tool correlation).
	// Uses interface{} because JSON-RPC request IDs can be numbers or strings.
	PendingMergeChildRequestID any
	// contains filtered or unexported fields
}

SessionState holds all per-session state in one place. This consolidates what was previously 11 separate maps in the Model, making it easier to manage session lifecycle and avoid race conditions.

Thread Safety: SessionState has an internal mutex to protect concurrent field access. For simple reads/writes, use the thread-safe methods (e.g., AppendStreamingContent, GetStreamingContent, SetStreamingContent). For operations that need to access multiple fields atomically, use WithLock() to hold the mutex during the operation.

The SessionStateManager's mutex protects the map of sessions, while each SessionState's internal mutex protects its own fields.

func (*SessionState) AddToolUse

func (s *SessionState) AddToolUse(toolName, toolInput, toolUseID string)

AddToolUse adds a tool use to the rollup. Thread-safe.

func (*SessionState) AppendStreamingContent

func (s *SessionState) AppendStreamingContent(content string)

AppendStreamingContent appends to the streaming content. Thread-safe.

func (*SessionState) FlushToolUseRollup

func (s *SessionState) FlushToolUseRollup(getToolIcon func(string) string, inProgressMarker, completeMarker string)

FlushToolUseRollup flushes the tool use rollup to streaming content and clears it. Thread-safe.

func (*SessionState) GetContainerInitStart

func (s *SessionState) GetContainerInitStart() time.Time

GetContainerInitStart returns when container initialization started. Thread-safe.

func (*SessionState) GetContainerInitializing

func (s *SessionState) GetContainerInitializing() bool

GetContainerInitializing returns whether the container is initializing. Thread-safe.

func (*SessionState) GetCurrentTodoList

func (s *SessionState) GetCurrentTodoList() *claude.TodoList

GetCurrentTodoList returns a copy of the current todo list. Returns nil if no todo list exists. Thread-safe.

func (*SessionState) GetDetectedOptions

func (s *SessionState) GetDetectedOptions() []DetectedOption

GetDetectedOptions returns a copy of the detected options slice. Thread-safe.

func (*SessionState) GetInputText

func (s *SessionState) GetInputText() string

GetInputText returns the saved input text. Thread-safe.

func (*SessionState) GetIsWaiting

func (s *SessionState) GetIsWaiting() bool

GetIsWaiting returns whether the session is waiting. Thread-safe.

func (*SessionState) GetMergeChan

func (s *SessionState) GetMergeChan() <-chan git.Result

GetMergeChan returns the merge channel if one exists, nil otherwise. Thread-safe.

func (*SessionState) GetMergeType

func (s *SessionState) GetMergeType() MergeType

GetMergeType returns the current merge type. Thread-safe.

func (*SessionState) GetPendingMergeChildRequestID

func (s *SessionState) GetPendingMergeChildRequestID() any

GetPendingMergeChildRequestID returns the stored merge child request ID.

func (*SessionState) GetPendingMsg

func (s *SessionState) GetPendingMsg() string

GetPendingMsg returns the pending message (non-consuming). Thread-safe.

func (*SessionState) GetPendingPermission

func (s *SessionState) GetPendingPermission() *mcp.PermissionRequest

GetPendingPermission returns a copy of the pending permission request. Returns nil if no permission request exists. Thread-safe.

func (*SessionState) GetPendingPlanApproval

func (s *SessionState) GetPendingPlanApproval() *mcp.PlanApprovalRequest

GetPendingPlanApproval returns a copy of the pending plan approval request. Returns nil if no plan approval request exists. Thread-safe.

func (*SessionState) GetPendingQuestion

func (s *SessionState) GetPendingQuestion() *mcp.QuestionRequest

GetPendingQuestion returns a copy of the pending question request. Returns nil if no question request exists. Thread-safe.

func (*SessionState) GetStreamCancel

func (s *SessionState) GetStreamCancel() context.CancelFunc

GetStreamCancel returns the stream cancel function. Thread-safe.

func (*SessionState) GetStreamingContent

func (s *SessionState) GetStreamingContent() string

GetStreamingContent returns the current streaming content. Thread-safe.

func (*SessionState) GetSubagentModel

func (s *SessionState) GetSubagentModel() string

GetSubagentModel returns the current subagent model (empty if none active). Thread-safe.

func (*SessionState) GetToolUsePos

func (s *SessionState) GetToolUsePos() int

GetToolUsePos returns the current tool use position. Thread-safe.

func (*SessionState) GetToolUseRollup

func (s *SessionState) GetToolUseRollup() *ToolUseRollupState

GetToolUseRollup returns a copy of the current tool use rollup. Returns nil if no rollup exists. Thread-safe.

func (*SessionState) GetWaitStartTime

func (s *SessionState) GetWaitStartTime() time.Time

GetWaitStartTime returns the wait start time. Thread-safe.

func (*SessionState) HasDetectedOptions

func (s *SessionState) HasDetectedOptions() bool

HasDetectedOptions returns true if there are at least 2 detected options. Thread-safe.

func (*SessionState) HasTodoList

func (s *SessionState) HasTodoList() bool

HasTodoList returns true if there is a non-empty todo list. Thread-safe.

func (*SessionState) IsMerging

func (s *SessionState) IsMerging() bool

IsMerging returns true if a merge operation is in progress. Thread-safe.

func (*SessionState) MarkToolUseComplete

func (s *SessionState) MarkToolUseComplete(toolUseID string, resultInfo *claude.ToolResultInfo)

MarkToolUseComplete marks the tool use with the given ID as complete. If the ID is empty or not found, falls back to marking the first incomplete tool use. The optional resultInfo provides rich details about the tool execution result. Thread-safe.

func (*SessionState) SetCurrentTodoList

func (s *SessionState) SetCurrentTodoList(list *claude.TodoList)

SetCurrentTodoList sets the current todo list. Thread-safe.

func (*SessionState) SetDetectedOptions

func (s *SessionState) SetDetectedOptions(options []DetectedOption)

SetDetectedOptions sets the detected options. Thread-safe.

func (*SessionState) SetInputText

func (s *SessionState) SetInputText(text string)

SetInputText sets the saved input text. Thread-safe.

func (*SessionState) SetPendingMergeChildRequestID

func (s *SessionState) SetPendingMergeChildRequestID(id any)

SetPendingMergeChildRequestID stores the merge child request ID for later correlation.

func (*SessionState) SetPendingMsg

func (s *SessionState) SetPendingMsg(msg string)

SetPendingMsg sets the pending message. Thread-safe.

func (*SessionState) SetPendingPermission

func (s *SessionState) SetPendingPermission(req *mcp.PermissionRequest)

SetPendingPermission sets the pending permission request. Thread-safe.

func (*SessionState) SetPendingPlanApproval

func (s *SessionState) SetPendingPlanApproval(req *mcp.PlanApprovalRequest)

SetPendingPlanApproval sets the pending plan approval request. Thread-safe.

func (*SessionState) SetPendingQuestion

func (s *SessionState) SetPendingQuestion(req *mcp.QuestionRequest)

SetPendingQuestion sets the pending question request. Thread-safe.

func (*SessionState) SetStreamingContent

func (s *SessionState) SetStreamingContent(content string)

SetStreamingContent sets the streaming content. Thread-safe.

func (*SessionState) SetSubagentModel

func (s *SessionState) SetSubagentModel(model string)

SetSubagentModel sets the current subagent model (empty string to clear). Thread-safe.

func (*SessionState) SetToolUsePos

func (s *SessionState) SetToolUsePos(pos int)

SetToolUsePos sets the tool use position. Thread-safe.

func (*SessionState) SetToolUseRollup

func (s *SessionState) SetToolUseRollup(rollup *ToolUseRollupState)

SetToolUseRollup sets the tool use rollup. Thread-safe.

func (*SessionState) SetWaitStartTime

func (s *SessionState) SetWaitStartTime(t time.Time)

SetWaitStartTime sets the wait start time. Thread-safe.

func (*SessionState) WithLock

func (s *SessionState) WithLock(fn func(*SessionState))

WithLock executes fn while holding the session state lock. Use this for operations that need to access multiple fields atomically. The function receives the SessionState pointer for direct field access.

Example:

state.WithLock(func(s *SessionState) {
    s.ToolUsePos = len(s.StreamingContent)
    s.StreamingContent += line
})

type SessionStateManager

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

SessionStateManager provides thread-safe access to per-session state.

Basic usage pattern with thread-safe accessors:

// For simple field access, use the thread-safe methods on SessionState:
state := manager.GetOrCreate(sessionID)
state.SetPendingPermission(req)          // Thread-safe write
req := state.GetPendingPermission()      // Thread-safe read
state.AppendStreamingContent("chunk")    // Thread-safe append

// For operations involving multiple fields atomically, use WithLock:
state.WithLock(func(s *SessionState) {
    s.ToolUsePos = len(s.StreamingContent)
    s.StreamingContent += line
})

// For reads when session may not exist:
if state := manager.GetIfExists(sessionID); state != nil {
    req := state.GetPendingPermission()
}

For operations that span multiple fields with special semantics (StartWaiting, StartMerge, consuming gets like GetPendingMessage), use the dedicated methods on SessionStateManager which handle locking internally.

func NewSessionStateManager

func NewSessionStateManager() *SessionStateManager

NewSessionStateManager creates a new session state manager.

func (*SessionStateManager) Delete

func (m *SessionStateManager) Delete(sessionID string)

Delete removes all state for a session and releases all associated resources.

func (*SessionStateManager) GetContainerInitStart

func (m *SessionStateManager) GetContainerInitStart(sessionID string) (time.Time, bool)

GetContainerInitStart returns when the container started initializing, and whether it's initializing.

func (*SessionStateManager) GetIfExists

func (m *SessionStateManager) GetIfExists(sessionID string) *SessionState

GetIfExists returns the state for a session if it exists, nil otherwise.

func (*SessionStateManager) GetInitialMessage

func (m *SessionStateManager) GetInitialMessage(sessionID string) string

GetInitialMessage returns and clears the initial message for a session. This is a consuming get - the message is cleared after retrieval. Use state.InitialMessage directly (with WithLock) if you need to read without clearing.

func (*SessionStateManager) GetOrCreate

func (m *SessionStateManager) GetOrCreate(sessionID string) *SessionState

GetOrCreate returns the state for a session, creating it if it doesn't exist. Use GetIfExists when you don't want to create state for sessions that haven't been accessed.

func (*SessionStateManager) GetPendingMessage

func (m *SessionStateManager) GetPendingMessage(sessionID string) string

GetPendingMessage returns and clears the pending message for a session. This is a consuming get - the message is cleared after retrieval. Use state.GetPendingMsg() if you need to read without clearing.

func (*SessionStateManager) GetStreamingStartTimeOrNow

func (m *SessionStateManager) GetStreamingStartTimeOrNow(sessionID string) time.Time

GetStreamingStartTimeOrNow returns the streaming start time for a session, or time.Now() if the session doesn't exist or hasn't started streaming.

func (*SessionStateManager) GetWaitStart

func (m *SessionStateManager) GetWaitStart(sessionID string) (time.Time, bool)

GetWaitStart returns when the session started streaming, and whether it's waiting. Returns StreamingStartTime (not WaitStart) because WaitStart gets cleared when the first chunk arrives, but we still need the start time for elapsed display.

func (*SessionStateManager) ReplaceToolUseMarker

func (m *SessionStateManager) ReplaceToolUseMarker(sessionID, oldMarker, newMarker string, pos int)

ReplaceToolUseMarker replaces the tool use marker in streaming content. The function validates that the old marker actually exists at the given position to prevent corruption if the streaming content has changed since the position was recorded.

func (*SessionStateManager) StartContainerInit

func (m *SessionStateManager) StartContainerInit(sessionID string)

StartContainerInit marks a session as initializing its container. This sets ContainerInitializing and ContainerInitStart atomically. Uses the same locking pattern as StartWaiting and StartMerge: acquire write lock, create state if needed, release manager lock, then acquire state lock.

func (*SessionStateManager) StartMerge

func (m *SessionStateManager) StartMerge(sessionID string, ch <-chan git.Result, cancel context.CancelFunc, mergeType MergeType)

StartMerge starts a merge operation for a session. This sets MergeChan, MergeCancel, and MergeType atomically.

func (*SessionStateManager) StartWaiting

func (m *SessionStateManager) StartWaiting(sessionID string, cancel context.CancelFunc)

StartWaiting marks a session as waiting for Claude response. This sets WaitStart, IsWaiting, StreamCancel, and StreamingStartTime atomically.

func (*SessionStateManager) StopContainerInit

func (m *SessionStateManager) StopContainerInit(sessionID string)

StopContainerInit marks a session's container as initialized. This clears ContainerInitializing and ContainerInitStart atomically.

func (*SessionStateManager) StopMerge

func (m *SessionStateManager) StopMerge(sessionID string)

StopMerge clears the merge state for a session. This clears MergeChan, MergeCancel, and MergeType atomically.

func (*SessionStateManager) StopWaiting

func (m *SessionStateManager) StopWaiting(sessionID string)

StopWaiting marks a session as no longer waiting. This clears IsWaiting, WaitStart, StreamCancel, and StreamingStartTime atomically.

type ToolUseItemState

type ToolUseItemState struct {
	ToolName   string
	ToolInput  string
	ToolUseID  string
	Complete   bool
	ResultInfo *claude.ToolResultInfo // Rich details about the result (populated on completion)
}

ToolUseItemState represents a single tool use

func (ToolUseItemState) Copy

Copy creates a deep copy of the ToolUseItemState.

type ToolUseRollupState

type ToolUseRollupState struct {
	Items    []ToolUseItemState // All tool uses in this group
	Expanded bool               // Whether the rollup is expanded
}

ToolUseRollupState tracks consecutive tool uses for non-active sessions

func (*ToolUseRollupState) Copy

Copy creates a deep copy of the ToolUseRollupState.

Jump to

Keyboard shortcuts

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