session

package
v0.4.1 Latest Latest
Warning

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

Go to latest
Published: Aug 27, 2026 License: AGPL-3.0 Imports: 39 Imported by: 0

Documentation

Overview

Package session ties together the message history, usage totals, and identity for one continuous conversation with the model.

Each Session is created with a UUID v4 identifier that also names its log file, making it straightforward to correlate terminal output with the structured log on disk.

A production session also owns the per-conversation runtime stack — sandbox, client, executor, agent runner, logger, model config — all scoped to the session's working directory. The Manager type in this package builds those wirings; the bare New constructor remains for tests that don't need the full stack.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ApprovalResponseMsg

type ApprovalResponseMsg struct {
	SessionID string
	RequestID string
	Behavior  string
}

ApprovalResponseMsg is sent to the Bubble Tea program when the jungi control websocket connection receives a remote decision on a forwarded tool approval request, so the TUI can resolve the matching pending approval and dismiss its overlay. Modelled on ChannelMessageMsg — async injection through the session's program handle.

TODO: Behavior carries the remote service's legacy "permission" wire vocabulary ("allow"/"deny"); realign with jungi's "approval" terminology once the remote server does.

type CacheCompactionTriggerMsg

type CacheCompactionTriggerMsg struct {
	SessionID string
}

CacheCompactionTriggerMsg is sent to the Bubble Tea program when the idle timer fires, signalling that the session's cached tokens are approaching expiry and in-place compaction should begin.

type ChannelMessageMsg

type ChannelMessageMsg struct {
	SessionID string
	Content   string
}

ChannelMessageMsg is sent to the Bubble Tea program when the jungi control websocket connection receives an inbound message, so the TUI can route it into the target session as a new user turn (or enqueue it if the session is currently busy). Modelled on CacheCompactionTriggerMsg — async injection through the session's program handle.

type ClientFactory

type ClientFactory func(log *logger.Logger, systemPrompt string, m model.ID, tools []tooldef.Definition, opts model.GenerationOptions, transportOpts ...client.Option) client.Client

ClientFactory is the constructor signature used to build a session's API client. Production wires this to defaultClientFactory, which resolves the per-provider credential from the auth store and dispatches to the right SDK client constructor based on the model's provider; tests inject a fake to avoid hitting the network. opts carries the provider-neutral generation settings (reasoning effort); see model.GenerationOptions.

type Deps

type Deps struct {
	LogDir      string
	ConfigDir   string
	Skills      *skillreg.Registry
	UserSkills  *skillreg.Registry
	Prompts     *promptreg.Registry
	UserPrompts *promptreg.Registry
	Agents      *agentreg.Registry
	UserAgents  *agentreg.Registry
	Settings    settings.Settings
	NewClient   ClientFactory

	// JungiControlToken is the bearer token used to authenticate with the
	// jungi control service. Empty means no credential has been
	// configured; sessions skip connecting in that case regardless of
	// the [jungi_control] enabled setting.
	JungiControlToken string

	// NewControl is a factory hook so tests can substitute a fake control
	// client instead of dialing a real service. A nil value defaults to
	// control.New.
	NewControl func(cfg control.Config) control.Client
}

Deps carries the process-scoped dependencies a Manager needs to wire up each new session. LogDir and the skill registries are loaded once at startup; Settings holds the full user-level configuration loaded from ~/.config/jungi/settings.toml (with any CLI flag overrides already applied); NewClient is a factory hook so tests can substitute a fake client. Credentials themselves are never carried on Deps: each session resolves them per-provider from the auth store at build time (see defaultClientFactory), so a submitted credential for any provider takes effect without touching Deps.

ConfigDir is the resolved user-level configuration directory (~/.config/jungi/), used to locate GUIDANCE.md for injection into the system prompt. An empty value simply omits the <user-guidance> block.

Skills holds the built-in (embedded) skills and UserSkills holds the user-level skills loaded from ~/.config/jungi/skills/. They are kept separate so the session builder can interleave project-level skills (<repo>/.jungi/skills/) between them at the correct precedence: built-in > project > user.

Prompts holds the built-in (embedded) prompts and UserPrompts holds the user-level prompts loaded from ~/.config/jungi/prompts/. They are kept separate so the session builder can interleave project-level prompts (<repo>/.jungi/prompts/) between them at the correct precedence: built-in > project > user.

Agents is the registry of agent definitions available for subagent execution (orient, plan/research, etc.). A nil value falls back to an empty registry so callers that don't need agents can omit it safely.

UserAgents holds the user-level agents loaded from ~/.config/jungi/agents/. It is kept separate from Agents so the session builder can interleave project-level agents (<repo>/.jungi/agents/) between them at the correct precedence: built-in > project > user.

Settings is the user-level configuration. Per-session, the session builder also loads and merges any <repo>/.jungi/settings.toml project file on top, so the effective settings may differ from what is stored here. The project file is loaded fresh for every new/compacted/cleared session so project overrides always reflect the on-disk state at session build time.

type EventStream

type EventStream struct {
	Events    <-chan toolexec.ToolEvent
	Approvals <-chan approval.Request
	Usage     <-chan usage.RequestUsage
	Text      <-chan string
	Outcome   <-chan ExecOutcome
}

EventStream is the receive-side view of an in-flight executor Run. Events emits each tool invocation as it completes; Approvals emits approval requests when the model invokes unsafe_shell; Usage emits each round-trip's RequestUsage live as it arrives, for progressive cost/context display; Text emits each non-empty assistant-text block as that response arrives, before any tools from the same response; Outcome emits the final result (or error) once the loop ends. Events, Approvals, Usage, and Text are all closed before Outcome is written.

Usage sends are best-effort: StartExec uses a buffered channel with a non-blocking send, so a slow or non-draining reader (e.g. compaction, which reuses StartExec but never selects on Usage) simply misses events rather than stalling the executor. Callers that need a definitive accounting should reconcile against the Outcome's Result.Usages.

Text sends are unbuffered and blocking: the executor will not emit that response's tool events until a reader has received the text. Callers (including compaction waiters) MUST drain Text, even if they discard it.

type ExecOutcome

type ExecOutcome struct {
	Result toolexec.Result
	Err    error
}

ExecOutcome bundles the result of a tool-use loop with its terminal error. The TUI translates this into either a model-response message or an error message; modelling both in one struct keeps the result channel a single buffered slot and avoids two parallel channels per Run.

type Manager

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

Manager owns the collection of sessions and knows how to build new ones bound to a working directory. The active session is the one whose messages currently appear in the TUI viewport; the others may still be running tool loops in the background.

Sessions can be added via New and replaced in-place via Clear. CloseAll is invoked at process exit to release log file descriptors.

func NewManager

func NewManager(deps Deps) *Manager

NewManager constructs an empty Manager. No sessions exist until New is called. The caller is responsible for invoking CloseAll on shutdown.

func (*Manager) Active

func (m *Manager) Active() *Session

Active returns the currently-active session, or nil if none exists yet. The TUI must guard against nil before dereferencing — at startup, before the user has run /new, there is no active session.

func (*Manager) All

func (m *Manager) All() []*Session

All returns the sessions in insertion order. The returned slice shares backing memory with the manager's; callers must not mutate it.

func (*Manager) Clear

func (m *Manager) Clear(id string) (*Session, error)

Clear replaces the session identified by id with a brand-new session bound to the same working directory. Any in-flight tool-use loop or orchestration on the old session is cancelled before it is closed so that background goroutines exit promptly. The new session has a fresh UUID, an empty message history, zeroed usage totals, and its own log file — it is otherwise identical to what Manager.New would produce for the same workDir.

The new session occupies the same position in the session list as the old one, so the ordering visible in /sessions is preserved. If the cleared session was active, the new session becomes active.

Returns an error if id is not found or if the new session cannot be wired (e.g. log file cannot be created).

func (*Manager) CloseAll

func (m *Manager) CloseAll() error

CloseAll releases every session's log file descriptor. Errors from individual closes are joined so callers see all failures, not just the first.

func (*Manager) Compact

func (m *Manager) Compact(id string) (*Session, error)

Compact replaces the session identified by id with a fresh session that inherits the old session's git worktree (when present), keeping the same branch checked out and the same on-disk files. It is the /compact lifecycle path: the user wants to keep working on the same feature, just with a fresh conversation context.

The old session is closed, but its worktree is detached first so that closing does not prune it. The new session takes ownership of the worktree handle; from the user's perspective the agent's branch and uncommitted changes carry over verbatim.

Returns an error if id is not found or if the new session cannot be wired.

func (*Manager) CredentialAvailable added in v0.2.0

func (m *Manager) CredentialAvailable() bool

CredentialAvailable reports whether an OpenRouter credential is stored. The TUI uses this to decide which session-creating commands are available and which idle-screen message to show.

func (*Manager) Delete

func (m *Manager) Delete(id string) error

Delete closes the session identified by id, removes it from the session list, and sets the active session to nil unconditionally — the caller is expected to return to the empty-state view rather than fall back to another session. Returns an error if id is not found.

func (*Manager) Find

func (m *Manager) Find(id string) *Session

Find returns the session with the given ID, or nil if not present. Used by the TUI to route async messages tagged with their originating session ID to the correct session, even when that session is not active.

func (*Manager) New

func (m *Manager) New(workDir string) (*Session, error)

New builds a fully wired session bound to workDir. workDir must exist and be a readable directory; the picker UI is responsible for offering only valid paths, but Manager validates again as a safety net so that a fat-fingered programmatic caller cannot create a broken session.

When workDir is inside a git repository, the session is hosted in a freshly-created git worktree at <repo>/.jungi/state/worktrees/<id>/ on branch jungi/<id>, isolating its work from any other session in the same repo. When workDir is not in a git repository, the session runs against the directory directly (graceful fallback for scratch dirs and projects that aren't version-controlled).

On success the new session becomes the active one, on the assumption that the user just asked for it; the previous active session keeps running its tool loop in the background.

func (*Manager) RefreshCredentials added in v0.2.0

func (m *Manager) RefreshCredentials() error

RefreshCredentials replaces every existing session with a fresh one so they re-resolve their provider credentials from the auth store. Callers invoke this after a credential is saved via /credentials: credentials are never cached on Deps, so this exists solely to rebuild in-flight sessions (whose clients were constructed once, at build time) rather than to record which provider changed.

Lifecycle events (worktree.pruned, worktree.created, session.created) are suppressed during this operation to avoid spurious hook firings.

func (*Manager) SetActive

func (m *Manager) SetActive(id string) bool

SetActive promotes the session with the given ID to active. Returns false if no such session exists; the previous active session is unchanged in that case.

func (*Manager) SetProgram

func (m *Manager) SetProgram(p interface{ Send(msg interface{}) })

SetProgram wires the Bubble Tea program into the manager so that every session — both existing and future ones — can send CacheCompactionTriggerMsg events into the TUI event loop when their idle timers fire. Called once by main after the program is created but before p.Run().

type ModelSelection added in v0.2.0

type ModelSelection struct {
	// Model is the resolved model identifier for the command's session.
	// Empty means no model is configured for this command.
	Model model.ID
	// FallbackModels is the ordered list of fallback model identifiers
	// for the command's session. Empty when no fallbacks are configured.
	FallbackModels []model.ID
	// GenerationOptions is the resolved provider-neutral generation
	// settings (reasoning effort) for the command's session. A zero value
	// means no reasoning override.
	GenerationOptions model.GenerationOptions
}

ModelSelection is a fully-resolved set of model and generation-option values for a slash command (/new, /plan, /execute, or /review). Unlike settings.CommandConfig — which carries the raw, possibly-empty TOML values — every field here is concrete: GenerationOptions has already been reconciled against what the resolved model supports (an unsupported effort level is silently disabled). Model may be empty when the command's section carries no configured model, meaning the command is disabled.

type OrchStream

type OrchStream struct {
	Result <-chan orchestrator.Result
}

OrchStream is the receive-side view of an in-flight orchestration. Result emits the aggregated final result once every agent finishes. Per-agent progress is not exposed here — StartOrchestration forwards it directly to the TUI as SubagentProgressMsg via async injection (see drainProgress), so there is nothing for the caller to drain.

type Session

type Session struct {
	Usage usage.SessionUsage

	// LastMessageAt records the time the most recent API call completed
	// successfully. It is used by the idle-timer logic to determine when
	// cached tokens are approaching expiry and in-place compaction should
	// be triggered.
	LastMessageAt time.Time
	// contains filtered or unexported fields
}

Session groups message history and usage tracking under a unique identifier.

Usage is exported so the TUI can read running totals directly without an accessor. All other state is private; callers mutate it through the methods below so the slice is never replaced out from under concurrent readers.

The runtime fields (sandbox, client, executor, agentRunner, logger, closeLog, modelConfig, workDir) are populated only when the session was built through Manager.New. Sessions created via the bare New constructor have nil runtime fields and are intended for unit tests that operate only on message history and usage accumulation.

func New

func New() *Session

New creates a session with a fresh UUID v4 identifier and no runtime wiring. Use Manager.New for production sessions; this constructor is retained for tests that exercise message history and usage in isolation.

func (*Session) AddUsage

func (s *Session) AddUsage(r usage.RequestUsage)

AddUsage folds a single request's token counts and reported cost into the running session totals.

func (*Session) AgentRunner

func (s *Session) AgentRunner() *agentreg.Runner

AgentRunner returns the session's subagent runner. Nil for bare sessions.

func (*Session) Append

func (s *Session) Append(msg message.Message)

Append adds a message to the end of the conversation history.

func (*Session) ApplyCompaction

func (s *Session) ApplyCompaction(summary string)

ApplyCompaction replaces the session's full message history with a single hidden carry-over user message built from summary, and resets the API client's server-side conversation state so the next request starts fresh with only the compact context. Active skill bodies are re-injected as hidden user messages before the carry-over so they survive the reseed. The session's LastMessageAt is stamped so the next idle timer starts from the compaction time rather than the original last-message time.

func (*Session) ApplySelection added in v0.4.0

func (s *Session) ApplySelection(sel ModelSelection) error

ApplySelection applies a fully-resolved ModelSelection (model, fallbacks, and generation options) to the session, rebuilding its client in-place.

Safe to call on bare sessions (no factory, no executor); in that case it is a no-op.

func (*Session) AutoApprove

func (s *Session) AutoApprove() bool

AutoApprove reports whether the AI auto-approver is enabled for this session. When true, run_unsafe_shell approval requests are delegated to a Haiku-class model rather than the manual overlay.

func (*Session) AutoApproveAPIKey added in v0.2.0

func (s *Session) AutoApproveAPIKey() string

AutoApproveAPIKey returns the credential for the provider that owns the session's auto-approve evaluator model, resolved at session build time. Used by the auto-approver to make its own single-turn evaluation call. Empty when auto-approval is unconfigured.

func (*Session) AutoApproveFallbacks added in v0.4.0

func (s *Session) AutoApproveFallbacks() []model.ID

AutoApproveFallbacks returns the fallback models configured for auto-approval.

func (*Session) AutoApproveModel

func (s *Session) AutoApproveModel() model.ID

AutoApproveModel returns the resolved model used to evaluate run_unsafe_shell auto-approval decisions. Empty when [auto_approve].model is unconfigured, meaning auto-approval is disabled.

func (*Session) AutoCompact added in v0.3.0

func (s *Session) AutoCompact() bool

AutoCompact reports whether idle autocompaction is enabled for this session.

func (*Session) CancelExec

func (s *Session) CancelExec()

CancelExec cancels the context of any in-flight tool-use loop or orchestration on this session. It is safe to call when no work is in flight — the cancel function is nil in that case and the call is a no-op. After cancellation the goroutine will exit and drain its channels; the TUI will receive a ModelErrorMsg (context cancelled) which it silently drops because the session ID will no longer be found in the manager after a /clear.

func (*Session) Client

func (s *Session) Client() client.Client

Client returns the session's API client. Nil for bare sessions.

func (*Session) Close

func (s *Session) Close() error

Close releases resources owned by the session: any in-flight work is cancelled first so the goroutine can exit promptly, then any LSP servers are shut down, then the worktree (if any) is pruned, then the log file descriptor is released. Safe to call on bare sessions; returns nil.

Worktree pruning is force=true so process exit and TUI-initiated close paths both work uniformly: the TUI gates dirty closes through the confirmation overlay before invoking Close, so reaching this point means the caller has already accepted any loss; on process exit there is no UI to consult and orphaned worktrees are recoverable via `git worktree prune`, so forcing through is the better default.

Worktree prune errors are joined with the log close error rather than short-circuiting — failing to remove a worktree must not stop log files from being released.

func (*Session) CompactAPIKey added in v0.3.0

func (s *Session) CompactAPIKey() string

CompactAPIKey returns the credential for the provider that owns the compact model, resolved at session build time. Used by the compact summarizer. Empty when compact.model is unconfigured.

func (*Session) CompactFallbacks added in v0.4.0

func (s *Session) CompactFallbacks() []model.ID

CompactFallbacks returns the fallback models configured for compaction.

func (*Session) CompactIdleTimeout added in v0.3.0

func (s *Session) CompactIdleTimeout() time.Duration

CompactIdleTimeout returns the idle duration after which autocompaction fires. Zero means unset.

func (*Session) CompactModel added in v0.3.0

func (s *Session) CompactModel() model.ID

CompactModel returns the resolved compact model identifier. Empty when compact.model is unconfigured, meaning /compact and autocompact are disabled.

func (*Session) CompactSelection added in v0.3.0

func (s *Session) CompactSelection() ModelSelection

CompactSelection returns the resolved model and generation options for /compact and idle autocompaction. Zero value (empty Model) for bare sessions or when compact.model is unconfigured.

func (*Session) Control

func (s *Session) Control() controlpkg.Client

Control returns the session's jungi control client, or nil when the feature is disabled, misconfigured, or the session was built via the bare New constructor.

func (*Session) DequeueInbound

func (s *Session) DequeueInbound() (string, bool)

DequeueInbound pops the oldest queued inbound channel message, if any. The second return value reports whether a message was present.

func (*Session) Effort

func (s *Session) Effort() string

Effort returns the current output-config effort level for this session. An empty string means no effort override is active.

func (*Session) EnqueueInbound

func (s *Session) EnqueueInbound(content string)

EnqueueInbound appends a channel message's content to the session's inbound queue. Used to hold jungi control messages that arrive while the session is Waiting so they are not dropped; DequeueInbound drains them once the session becomes idle again.

func (*Session) ExecuteSelection added in v0.2.0

func (s *Session) ExecuteSelection() ModelSelection

ExecuteSelection returns the resolved per-command model selection for the /execute slash command. Zero value (empty Model) for bare sessions or when [execute].model is unconfigured.

func (*Session) Executor

func (s *Session) Executor() *toolexec.Executor

Executor returns the session's tool-use executor. Nil for bare sessions.

func (*Session) FallbackModels added in v0.4.0

func (s *Session) FallbackModels() []model.ID

FallbackModels returns the fallback models currently configured for the main session client.

func (*Session) Features added in v0.2.0

func (s *Session) Features() settings.FeatureSet

Features returns the FeatureSet describing which model-backed features the session's effective settings configured. Zero value (all false) for bare sessions.

func (*Session) FoldLiveUsage

func (s *Session) FoldLiveUsage()

FoldLiveUsage records that one more round-trip's usage has been folded into Usage live (via the usage channel), so handleModelResponse's end-of-turn reconciliation knows which prefix of Result.Usages to skip.

func (*Session) HasIdleTimer

func (s *Session) HasIdleTimer() bool

HasIdleTimer reports whether an idle timer is currently pending. Exposed for tests that need to assert whether a timer was started or cleared.

func (*Session) HooksRegistry

func (s *Session) HooksRegistry() *hooksreg.Registry

HooksRegistry returns the session's lifecycle hook registry. Non-nil for sessions built via Manager; nil for bare test sessions. Callers may invoke Emit directly; nil is handled gracefully in hooksreg.Registry.Emit.

func (*Session) ID

func (s *Session) ID() string

ID returns the session's unique identifier, which also serves as the base name for the session log file.

func (*Session) LiveFoldedCount

func (s *Session) LiveFoldedCount() int

LiveFoldedCount reports how many round-trips have already been folded live for the current turn.

func (*Session) LoadedSkills

func (s *Session) LoadedSkills() [][2]string

LoadedSkills returns a snapshot of the currently active skills as an ordered slice of (name, body) pairs. Safe to call from any goroutine.

func (*Session) Logger

func (s *Session) Logger() *logger.Logger

Logger returns the session's structured event logger. Nil for bare sessions.

func (*Session) Messages

func (s *Session) Messages() []message.Message

Messages returns the full conversation history in chronological order.

func (*Session) ModelConfig

func (s *Session) ModelConfig() model.Config

ModelConfig returns the pricing and context-limit metadata for the model the session targets. Zero value for bare sessions.

func (*Session) ModelName

func (s *Session) ModelName() string

ModelName returns the displayed model identifier (e.g. "anthropic/claude-sonnet-5"). After a fallback this is the model that served the last request. Empty for bare sessions.

func (*Session) PlanSelection added in v0.2.0

func (s *Session) PlanSelection() ModelSelection

PlanSelection returns the resolved per-command model selection for the /plan slash command. Zero value (empty Model) for bare sessions or when [plan].model is unconfigured.

func (*Session) PlanStore

func (s *Session) PlanStore() *planengine.Store

PlanStore returns the session's plan store, or nil when the session is not hosted in a git repo. Used by tests and any future code that needs direct access; the routine tool-call path goes through the executor, which is wired during build.

func (*Session) PrimaryModelName added in v0.4.0

func (s *Session) PrimaryModelName() string

PrimaryModelName returns the configured primary model identifier. Unaffected by SetModelName. Falls back to ModelName when unset (bare sessions). Empty for a session that has never had a model applied.

func (*Session) Prompts

func (s *Session) Prompts() *promptreg.Registry

Prompts returns the per-session merged prompt registry (built-in > project > user precedence). When s.prompts is nil the session is a bare or test session — not an error condition — and an empty registry is returned so callers never need nil guards. Do not conflate nil with a load failure.

func (*Session) ProviderPreferences added in v0.4.0

func (s *Session) ProviderPreferences() settings.ProviderSettings

ProviderPreferences returns the effective OpenRouter provider routing preferences for this session.

func (*Session) Reasoning added in v0.2.0

func (s *Session) Reasoning() bool

Reasoning returns whether reasoning is enabled for this session.

func (*Session) RecordSkill

func (s *Session) RecordSkill(name, body string)

RecordSkill stores the rendered body for the named skill so it can be re-injected by ApplyCompaction. If the skill was already recorded its body is updated to the new value; insertion order is preserved on first record. Safe to call from any goroutine.

func (*Session) ResetLiveFoldedCount

func (s *Session) ResetLiveFoldedCount()

ResetLiveFoldedCount is the exported form of resetLiveFoldedCount, for callers outside the package (the TUI, after end-of-turn reconciliation).

func (*Session) RestoreTools added in v0.2.0

func (s *Session) RestoreTools()

RestoreTools re-advertises the session's full tool set and lifts any executor dispatch restriction. Idempotent: safe when tools were never restricted.

func (*Session) ReviewSelection added in v0.2.0

func (s *Session) ReviewSelection() ModelSelection

ReviewSelection returns the resolved per-command model selection for the /review slash command. Zero value (empty Model) for bare sessions or when review.model is unconfigured.

func (*Session) ReviewerSubagentFallbacks added in v0.4.0

func (s *Session) ReviewerSubagentFallbacks() []model.ID

ReviewerSubagentFallbacks returns the fallback models configured for the reviewer subagent.

func (*Session) ReviewerSubagentModel

func (s *Session) ReviewerSubagentModel() model.ID

ReviewerSubagentModel returns the resolved model used for the reviewer subagent spawned by /review and review_ticket. Empty when [review.reviewer_subagent].model is unconfigured, meaning the reviewer subagent is disabled.

func (*Session) Sandbox

func (s *Session) Sandbox() sandbox.Sandbox

Sandbox returns the session's sandbox. Nil for bare sessions.

func (*Session) SessionTitle added in v0.3.0

func (s *Session) SessionTitle() bool

SessionTitle reports whether automatic session title generation is enabled for this session.

func (*Session) SessionTitleAPIKey added in v0.3.0

func (s *Session) SessionTitleAPIKey() string

SessionTitleAPIKey returns the credential for the provider that owns the session's title generation model, resolved at session build time. Empty when session titles are unconfigured.

func (*Session) SessionTitleEffort added in v0.3.0

func (s *Session) SessionTitleEffort() string

SessionTitleEffort returns the reasoning effort level configured for session title generation. Empty when unconfigured or disabled.

func (*Session) SessionTitleFallbacks added in v0.4.0

func (s *Session) SessionTitleFallbacks() []model.ID

SessionTitleFallbacks returns the fallback models configured for session titles.

func (*Session) SessionTitleModel added in v0.3.0

func (s *Session) SessionTitleModel() model.ID

SessionTitleModel returns the resolved model used to generate session titles. Empty when [session_title].model is unconfigured, meaning title generation is disabled.

func (*Session) SetAutoApprove

func (s *Session) SetAutoApprove(v bool)

SetAutoApprove enables or disables the AI auto-approver for this session. Enabling is a no-op when no auto-approve model is configured — there is nothing to evaluate approval requests with, so the toggle stays off regardless of v.

func (*Session) SetAutoCompact added in v0.3.0

func (s *Session) SetAutoCompact(v bool)

SetAutoCompact enables or disables idle autocompaction for this session. Enabling is a no-op when no compact model is configured — there is nothing to summarise with, so the toggle stays off regardless of v.

func (*Session) SetGenerationOptions

func (s *Session) SetGenerationOptions(opts model.GenerationOptions)

SetGenerationOptions updates the provider-neutral generation options (reasoning effort) for the current model and rebuilds the client in-place.

Safe to call on bare sessions (no factory, no executor); in that case it is a no-op. Callers must not call this while a tool-use loop is in flight.

func (*Session) SetModel

func (s *Session) SetModel(m model.ID) error

SetModel swaps the session's API client to target a different model. It builds a fresh client via the factory that was stored at construction time, wires it into the executor, and updates modelName and modelConfig so that context-limit and pricing calculations immediately reflect the new model. Accumulated TotalCost from prior requests is preserved; only future requests price at the new model's rate.

SetModel resets the new client's prompt-cache state (it starts empty by construction) so the next request is sent as a fresh conversation from the server's perspective. This is intentional: the previous server-side betaMessages were keyed to the old model's context and cannot be replayed against a different model endpoint.

Switching models via SetModel drops any previously-configured fallback models and generation options, restoring a single-model configuration. It is a thin wrapper around ApplySelection with only Model set; callers that need to keep fallbacks or effort should call ApplySelection instead.

Safe to call on bare sessions (no factory, no executor); in that case it is a no-op. Callers must not call SetModel while a tool-use loop is in flight on this session.

SetModel returns an error, without changing any session state, if m is not a registered model. Callers (the /model command and the OpenRouter search flow) are expected to register or already know m is registered before calling SetModel, so this should not fail in practice; it exists to avoid silently falling back to another model's pricing/context-limit config.

func (*Session) SetModelName added in v0.4.0

func (s *Session) SetModelName(name string)

SetModelName updates the displayed model identifier for this session without rebuilding the client or changing primaryModelName, modelConfig, or fallbackModels. Used to reflect the model that actually served a request when OpenRouter falls back.

func (*Session) SetProgram

func (s *Session) SetProgram(p interface{ Send(msg interface{}) })

SetProgram wires the Bubble Tea program into the session so the idle timer can inject CacheCompactionTriggerMsg events into the TUI event loop. Called once by the TUI after the program is created.

func (*Session) SetSessionTitle added in v0.3.0

func (s *Session) SetSessionTitle(v bool)

SetSessionTitle enables or disables automatic session title generation for this session. Enabling is a no-op when no session-title model is configured — there is nothing to generate titles with, so the toggle stays off regardless of v.

func (*Session) SetTitle added in v0.3.0

func (s *Session) SetTitle(title string)

SetTitle sets the generated human-readable title slug for this session.

func (*Session) SetWaiting

func (s *Session) SetWaiting(v bool)

SetWaiting sets the in-flight flag. Called by the goroutine spawn site when a request begins, and by the result handlers when it ends.

func (*Session) SourceWorkDir

func (s *Session) SourceWorkDir() string

SourceWorkDir returns the directory the user originally selected when the session was created. Equal to WorkDir when no worktree is in play; differs when WorkDir points at a worktree-relative path. Manager.Clear and Manager.Compact use this to build the successor session from the user-chosen directory rather than the disposable worktree path.

func (*Session) StartCompaction

func (s *Session) StartCompaction(ctx context.Context) EventStream

StartCompaction obtains a conversation summary from the compact model via a throwaway client (see compact.Summarize). It does not append the summary prompt onto session history and does not use the session client. The returned EventStream follows the same contract as StartExec so the TUI waitForCompactionEvent path stays unchanged: unused event/approval/text channels close, then Outcome carries the summary (or error).

On an empty compact model this is a no-op: WAITING is not set and a zero EventStream is returned. Callers must not drain a zero stream.

func (*Session) StartExec

func (s *Session) StartExec(ctx context.Context, messages []message.Message) EventStream

StartExec spawns a goroutine that runs the session's tool-use loop and returns channels carrying the per-tool events, approval requests, and the final outcome. Channels are owned by the goroutine: it writes events and approval requests as they happen, closes those channels on exit, then writes the outcome. Callers read the channels through tea.Cmds without ever closing them, so a session-switch in the TUI cannot panic the writer.

A cancellable child context is derived from ctx and stored on the session so that CancelExec can abort an in-flight loop (e.g. when the user runs /clear). The cancel is also called on goroutine exit so the context is always released promptly.

The session is marked WAITING synchronously here, before the goroutine spawns, so the next View render observes the busy state without any race against the goroutine starting. Clearing waiting is the caller's responsibility (terminal-message handlers in the TUI clear it; cancel paths flip it off as part of CancelExec's contract). This start-side ownership keeps long-running work visible regardless of which TUI handler launched it, without making the goroutine fight the existing drain-message filter that depends on waiting=true at message time.

Panics if the session has no executor wired (i.e. it was built via the bare New() constructor); production callers always go through Manager.

func (*Session) StartIdleTimer

func (s *Session) StartIdleTimer(d time.Duration)

StartIdleTimer starts (or resets) the idle timer for duration d. When the timer fires it sends a CacheCompactionTriggerMsg to the Bubble Tea program so the TUI can initiate in-place history compaction before the cached tokens expire. Safe to call when program is nil — the timer is still created but its callback is a no-op.

func (*Session) StartOrchestration

func (s *Session) StartOrchestration(ctx context.Context, orch *orchestrator.Orchestrator, tasks []orchestrator.Task) OrchStream

StartOrchestration spawns a goroutine that runs the given orchestrator against tasks and returns a channel carrying the final aggregated result. Per-agent progress is forwarded to the TUI as it happens via drainProgress + async injection (SubagentProgressMsg), the same mechanism planAgentRunner.ReviewDiff uses for the plan-driven review_ticket path — so ad-hoc /review and review_ticket display subagent progress identically.

A cancellable child context is derived and stored on the session, consistent with StartExec, so that CancelExec can abort an in-flight orchestration.

As with StartExec, the session is marked WAITING synchronously here so the next View render reflects the busy state without racing the goroutine. End-of-work clears are the TUI's responsibility.

func (*Session) StopIdleTimer

func (s *Session) StopIdleTimer()

StopIdleTimer cancels the pending idle timer if one is running. Safe to call when no timer is active.

func (*Session) Title added in v0.3.0

func (s *Session) Title() string

Title returns the generated human-readable title slug for this session, or an empty string if no title has been generated yet.

func (*Session) Waiting

func (s *Session) Waiting() bool

Waiting reports whether a tool-use loop is currently in flight on this session. The TUI uses this to gate new submissions and to render the WAITING tag in the divider when this session is the active one.

func (*Session) WorkDir

func (s *Session) WorkDir() string

WorkDir returns the working directory the session is bound to. Empty for sessions built via the bare New constructor. When the session is hosted in a git worktree, this is the worktree-relative path; the sandbox, LSP manager, and system prompt all see the worktree as the canonical project root.

func (*Session) Worktree

func (s *Session) Worktree() *worktree.Handle

Worktree returns the worktree handle this session owns, or nil when the session is not hosted in a worktree. The TUI uses this to query dirty-state before /close and /clear so the user can be warned about uncommitted or unpushed work that would be discarded.

type SessionCreatedEvent

type SessionCreatedEvent struct {
	SessionID  string
	WorkDirEnv string
	Dir        string
}

SessionCreatedEvent implements hooksreg.Payload for session.created. Its fields are exactly the keys hooksreg's catalog declares for that event: the session ID and the working directory.

func (SessionCreatedEvent) Env

func (e SessionCreatedEvent) Env() map[string]string

Env implements hooksreg.Payload.

func (SessionCreatedEvent) Event

Event implements hooksreg.Payload.

func (SessionCreatedEvent) WorkDir

func (e SessionCreatedEvent) WorkDir() string

WorkDir implements hooksreg.Payload.

type SessionDeletedEvent

type SessionDeletedEvent struct {
	SessionID  string
	WorkDirEnv string
	Dir        string
}

SessionDeletedEvent implements hooksreg.Payload for session.deleted. Its fields are exactly the keys hooksreg's catalog declares for that event: the session ID and the working directory.

WorkDirEnv is the value reported via the WORK_DIR env var; Dir is the actual working directory the hook process runs in. These differ for session.deleted: the hook process runs in the session's source directory after the worktree has already been pruned, but WORK_DIR keeps reporting the original (possibly worktree-relative) workDir value so consumers see the path the session operated against throughout its life.

func (SessionDeletedEvent) Env

func (e SessionDeletedEvent) Env() map[string]string

Env implements hooksreg.Payload.

func (SessionDeletedEvent) Event

Event implements hooksreg.Payload.

func (SessionDeletedEvent) WorkDir

func (e SessionDeletedEvent) WorkDir() string

WorkDir implements hooksreg.Payload.

type SubagentProgressMsg

type SubagentProgressMsg struct {
	SessionID string
	Event     agentreg.ProgressEvent
}

SubagentProgressMsg is sent to the Bubble Tea program when a reviewer or research subagent starts, finishes, or errors, so the TUI can append a harness message to the session history. It is the single progress path for both producers: StartOrchestration (ad-hoc /review) and planAgentRunner.ReviewDiff (plan-driven review_ticket) both drain their progress channel through drainProgress and forward each event here via async injection through the session's program handle — the same mechanism the idle timer uses for CacheCompactionTriggerMsg — rather than a channel the TUI itself drains via a chained tea.Cmd.

type WorktreeCreatedEvent

type WorktreeCreatedEvent struct {
	WorktreePath string
	RepoRoot     string
	SessionID    string
}

WorktreeCreatedEvent implements hooksreg.Payload for worktree.created. Its fields are exactly the keys hooksreg's catalog declares for that event: the worktree path, the repo root, and the session ID.

func (WorktreeCreatedEvent) Env

func (e WorktreeCreatedEvent) Env() map[string]string

Env implements hooksreg.Payload.

func (WorktreeCreatedEvent) Event

Event implements hooksreg.Payload.

func (WorktreeCreatedEvent) WorkDir

func (e WorktreeCreatedEvent) WorkDir() string

WorkDir implements hooksreg.Payload.

type WorktreePrunedEvent

type WorktreePrunedEvent struct {
	WorktreePath string
	RepoRoot     string
	SessionID    string
}

WorktreePrunedEvent implements hooksreg.Payload for worktree.pruned. Its fields are exactly the keys hooksreg's catalog declares for that event: the worktree path, the repo root, and the session ID.

func (WorktreePrunedEvent) Env

func (e WorktreePrunedEvent) Env() map[string]string

Env implements hooksreg.Payload.

func (WorktreePrunedEvent) Event

Event implements hooksreg.Payload.

func (WorktreePrunedEvent) WorkDir

func (e WorktreePrunedEvent) WorkDir() string

WorkDir implements hooksreg.Payload.

Source Files

  • hookevents.go
  • manager.go
  • plan_agent_runner.go
  • session.go

Jump to

Keyboard shortcuts

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