Documentation
¶
Overview ¶
Package uiadapter: Phase 2 Conversation and TurnHandle over chat.Session.
Conversation wraps an existing *chat.Session into the ports.Conversation contract. The session is owned by the caller; the adapter holds no reference that outlives the caller closing the session and Send is the only mutating call. Concurrent Send calls are serialized by an internal mutex; the second caller blocks until the first finishes.
TurnHandle exposes the buffered uievent.Event stream for one turn. Events() is closed exactly once when the turn ends. Cancel cancels the turn's per-turn context and closes the channel; it is safe to call after the turn has already ended.
The synthetic-event ordering and Cancel/goroutine/tap coordination contract is documented on Send; the rationale moved to docs/ would belong there if it ever grows beyond a paragraph.
Package uiadapter translates domain events (internal/agent.Event) into the canonical UI event stream (internal/uikit/uievent.Event). It is the single seam between the agent runtime and every renderer; no renderer imports internal/agent, and no agent code imports uievent.
Phase 1 ships ONLY the pure translation layer. Session lifecycle (Conversation, TurnHandle, Cancel), approval gating, settings ports, and the build/constructor live in later phases of docs/design/ui-replacement- phases.md.
Empty-TurnID window: the first event on every turn is KindTurnStart with TurnID="" (chat.Session only surfaces the real ID after SendUserWithEvent returns). The terminal KindTurnEnd always carries the real ID, so renderers that read TurnID off the end event are correct; renderers that need it for every intermediate event must accept the leading-empty window. See conversation.go's emitSyntheticTurnStart comment for the tap-stamp mechanism.
The full per-kind mapping table lives next to TranslateEvent.
Index ¶
- Variables
- func DefaultCommands() []composer.Command
- func ErrFromDetailForTest(detail string, ok bool) string
- func ParseArgsForTest(input string) map[string]any
- func PopulateFromToolCalls(threads *SubagentThreads, msgs []ports.Message)
- func SkillCommands(reg *skills.Registry) []composer.Command
- func TranslateEvent(ev agent.Event) []uievent.Event
- func TranslateEventWithOptions(ev agent.Event, opts TranslateOptions) []uievent.Event
- type Adapter
- type Approver
- type CommandRunner
- func (r *CommandRunner) Commands() []composer.Command
- func (r *CommandRunner) Pool() *SessionPool
- func (r *CommandRunner) Run(ctx context.Context, name, args string) ports.CommandOutcome
- func (r *CommandRunner) SelectAgent(_ context.Context, name string) ports.CommandOutcome
- func (r *CommandRunner) SelectEffort(_ context.Context, levelStr string) ports.CommandOutcome
- func (r *CommandRunner) SelectModel(_ context.Context, name string) ports.CommandOutcome
- func (r *CommandRunner) SelectSession(ctx context.Context, id string) ports.CommandOutcome
- func (r *CommandRunner) SessionActive(id string) bool
- func (r *CommandRunner) SetActiveSession(sess *chat.Session)
- func (r *CommandRunner) SetSettingsStore(s *SettingsStore)
- type Conversation
- func (c *Conversation) ActiveTurn() (ports.TurnHandle, bool)
- func (c *Conversation) ContextUsage() ports.Usage
- func (c *Conversation) History() []ports.Message
- func (c *Conversation) ID() string
- func (c *Conversation) IsActive() bool
- func (c *Conversation) Model() ports.ModelInfo
- func (c *Conversation) NoticeOptions() TranslateOptions
- func (c *Conversation) ScrollLines() int
- func (c *Conversation) Send(ctx context.Context, in intent.Send) (ports.TurnHandle, error)
- func (c *Conversation) Session() *chat.Session
- func (c *Conversation) SetNoticeOptions(opts TranslateOptions)
- func (c *Conversation) SetScrollLines(n int)
- func (c *Conversation) SetShowReasoning(show bool)
- func (c *Conversation) SetSubagents(subagents *SubagentThreads)
- func (c *Conversation) ShowReasoning() bool
- func (c *Conversation) Title() string
- type Input
- type SessionPool
- type SettingsStore
- type SubagentThreads
- type SubagentTranscriptConversation
- func (c *SubagentTranscriptConversation) ActiveTurn() (ports.TurnHandle, bool)
- func (c *SubagentTranscriptConversation) ContextUsage() ports.Usage
- func (c *SubagentTranscriptConversation) History() []ports.Message
- func (c *SubagentTranscriptConversation) ID() string
- func (c *SubagentTranscriptConversation) Model() ports.ModelInfo
- func (c *SubagentTranscriptConversation) RecordEvent(e uievent.Event)
- func (c *SubagentTranscriptConversation) Send(_ context.Context, in intent.Send) (ports.TurnHandle, error)
- func (c *SubagentTranscriptConversation) Title() string
- type TranslateOptions
Constants ¶
This section is empty.
Variables ¶
var SubagentProgressRegistrar func(fn func(agent.Event)) (cleanup func())
Send starts one user turn on the wrapped session. The second caller blocks until the first finishes. The per-turn context is derived from ctx; cancelling it cancels the turn and closes the channel.
Synthetic-event ordering and Cancel/goroutine/tap coordination contract: the very first event on the channel is KindTurnStart (TurnID="" because chat.Session only surfaces the turnID after SendUserWithEvent returns; the terminal turn.end carries the real TurnID). Tap-installed events stamp the real TurnID once known via a shared atomic.Pointer. An atomic.Bool closed is the single source of truth for "events is closed"; Cancel and the goroutine CAS-claim it; the tap drops on closed. Exactly one close occurs. SubagentProgressRegistrar allows the UI layer to receive live subagent progress events (nested tool calls, steps, heartbeats) from the subagent progress callback.
Functions ¶
func DefaultCommands ¶
DefaultCommands returns the list of available slash commands for composer auto-completion.
func ErrFromDetailForTest ¶
ErrFromDetailForTest is the test-export wrapper for errFromDetail. Same pattern as ParseArgsForTest: production callers go through the renderer path; tests exercise the bare helper through this shim so diff-coverage keeps the "non-bare detail" branch under cover.
func ParseArgsForTest ¶
ParseArgsForTest is the test-export wrapper for parseArgs. The body helper is unexported so production code never reaches for it directly; tests use this shim instead. Returning the same map[string]any as the internal helper keeps the diff-coverage gate satisfied without making the helper public API.
func PopulateFromToolCalls ¶
func PopulateFromToolCalls(threads *SubagentThreads, msgs []ports.Message)
PopulateFromToolCalls scans conversation messages for subagent tool invocations (such as dispatch_tasks, delegate, spawn_agent, invoke_subagent, and agent_* tools) and seeds SubagentTranscriptConversation instances into threads so resumed sessions show full subagent history when opening their threads in the TUI.
func SkillCommands ¶
SkillCommands returns the slash command candidates for the given skill registry.
func TranslateEvent ¶
TranslateEvent converts one agent.Event into zero or more uievent.Events using default translation options (iteration and cache notices disabled).
func TranslateEventWithOptions ¶
func TranslateEventWithOptions(ev agent.Event, opts TranslateOptions) []uievent.Event
TranslateEventWithOptions converts one agent.Event into zero or more uievent.Events with custom notice visibility options.
Types ¶
type Adapter ¶
type Adapter struct {
*Conversation
// contains filtered or unexported fields
}
Adapter is the production handle cmd/mivia-ui (or a future CLI surface) drives a real chat.Session through the ports.Conversation seam. The embedded *Conversation exposes every Conversation method (Send, History, Model, ContextUsage, Title). The store and mcp fields are kept so the cleanup closure the constructor returns can close both, and so a future phase that surfaces checkpoint or MCP state in the UI does not need to plumb new fields through New's signature.
func New ¶
New wires every Input field into a real chat.Session and returns it wrapped in an Adapter. The returned cleanup closes the checkpoint store and the MCP manager (the latter only when MCP was enabled). On any error the function returns (nil, nil, err): no partial resources leak, because BuildSession closes its own store on error and AttachMCPServers closes its manager on error.
The principal returned by BuildSession is captured but not stored on the Adapter; future phases that surface checkpoint state will add a field rather than re-running the build to surface it.
func (*Adapter) SubagentThreads ¶
func (a *Adapter) SubagentThreads() ports.SubagentThreads
SubagentThreads returns the active ports.SubagentThreads registry.
type Approver ¶
type Approver struct {
// contains filtered or unexported fields
}
Approver implements ports.Approver and bridges tool approval requests between the chat.Session's ApprovalGate and the UI's Pending / Resolve surface.
func NewApprover ¶
NewApprover creates an Approver and hooks it into sess.ApprovalGate. If sess.ApprovalStanding is nil, it initializes a new standing cache so session-level always decisions persist.
func (*Approver) Pending ¶
func (a *Approver) Pending() <-chan ports.ApprovalRequest
Pending returns the read-only channel delivering approval requests to the UI.
type CommandRunner ¶
type CommandRunner struct {
// contains filtered or unexported fields
}
CommandRunner bridges the UI slash-command loop with the backend session, config, and agent state.
func NewCommandRunner ¶
func NewCommandRunner(sess *chat.Session, res *config.Resolved, state *cliagents.AgentSessionState) *CommandRunner
NewCommandRunner constructs a CommandRunner for the given session and configuration.
func NewCommandRunnerWithPool ¶
func NewCommandRunnerWithPool(sess *chat.Session, pool *SessionPool, res *config.Resolved, state *cliagents.AgentSessionState) *CommandRunner
NewCommandRunnerWithPool constructs a CommandRunner with an explicit SessionPool.
func (*CommandRunner) Commands ¶
func (r *CommandRunner) Commands() []composer.Command
Commands returns all available slash commands, merging builtins with active skills.
func (*CommandRunner) Pool ¶
func (r *CommandRunner) Pool() *SessionPool
Pool returns the SessionPool backing this runner's session switches (/resume, /new). Callers building the UI use it to source the initial Conversation and its SubagentThreads registry from the SAME pool that SelectSession and handleNew hand out on later switches, so the activity panel's thread dialog is wired to whichever session is actually active rather than a separately-constructed twin.
func (*CommandRunner) Run ¶
func (r *CommandRunner) Run(ctx context.Context, name, args string) ports.CommandOutcome
Run executes one slash command by name with arguments.
func (*CommandRunner) SelectAgent ¶
func (r *CommandRunner) SelectAgent(_ context.Context, name string) ports.CommandOutcome
SelectAgent switches the session's active agent.
func (*CommandRunner) SelectEffort ¶
func (r *CommandRunner) SelectEffort(_ context.Context, levelStr string) ports.CommandOutcome
SelectEffort applies a reasoning effort level override.
func (*CommandRunner) SelectModel ¶
func (r *CommandRunner) SelectModel(_ context.Context, name string) ports.CommandOutcome
SelectModel switches the session's active model.
func (*CommandRunner) SelectSession ¶
func (r *CommandRunner) SelectSession(ctx context.Context, id string) ports.CommandOutcome
SelectSession loads and resumes a saved session.
func (*CommandRunner) SessionActive ¶
func (r *CommandRunner) SessionActive(id string) bool
SessionActive reports whether a pooled session currently has a turn in flight: a map lookup and an atomic load, no I/O. It is safe to call on every /resume picker refresh tick, unlike listSessionSummaries which re-queries the session store.
func (*CommandRunner) SetActiveSession ¶
func (r *CommandRunner) SetActiveSession(sess *chat.Session)
SetActiveSession updates the active session for subsequent commands.
func (*CommandRunner) SetSettingsStore ¶
func (r *CommandRunner) SetSettingsStore(s *SettingsStore)
SetSettingsStore links a SettingsStore to synchronize active sessions during session switches.
type Conversation ¶
type Conversation struct {
// contains filtered or unexported fields
}
Conversation wraps a *chat.Session and satisfies ports.Conversation. Fields are unexported; NewConversation is the only construction path.
func NewConversation ¶
func NewConversation(sess *chat.Session) *Conversation
NewConversation wraps an existing chat.Session. The caller owns the session and is responsible for its lifecycle; NewConversation stores the pointer verbatim and does not retain any other reference.
func (*Conversation) ActiveTurn ¶
func (c *Conversation) ActiveTurn() (ports.TurnHandle, bool)
ActiveTurn returns the current active turn handle, if any.
func (*Conversation) ContextUsage ¶
func (c *Conversation) ContextUsage() ports.Usage
ContextUsage reports the session's live prompt-cost estimate. Field mapping: InputTokens <- chat.ContextUsage.UsedTokens, OutputTokens = 0 (chat.ContextUsage.OutputReserveTokens is max output capacity, not consumed tokens), CachedTokens = 0 (chat has no cache field; honest zero), CostUSD = 0 (chat has no cost field; honest zero). Percent from chat.ContextUsage is discarded.
func (*Conversation) History ¶
func (c *Conversation) History() []ports.Message
History returns a snapshot of the session's user/assistant turns at the moment of the call. Empty input returns nil (NOT an empty slice) so callers can distinguish "no history" from "history is empty".
func (*Conversation) IsActive ¶
func (c *Conversation) IsActive() bool
IsActive reports whether a turn is currently in flight on this conversation. It is the liveness signal session pickers use to show which background sessions are actually doing something.
func (*Conversation) Model ¶
func (c *Conversation) Model() ports.ModelInfo
Model reports the bound provider/model and its usable prompt budget.
ContextWindow carries the PROMPT BUDGET, not the model's raw context window: its only consumer is the top bar's context percentage, and the agent compacts against the budget (the window minus the output reserve). Reporting the raw window made that gauge read about two thirds at the exact moment compaction fired, and left it unable to reach 100% at all - the surface users watch to decide whether to intervene showed slack that did not exist. Falls back to the window when no budget is derived, so a binding without a profile still shows something rather than nothing.
func (*Conversation) NoticeOptions ¶
func (c *Conversation) NoticeOptions() TranslateOptions
NoticeOptions returns the current notice visibility options.
func (*Conversation) ScrollLines ¶
func (c *Conversation) ScrollLines() int
ScrollLines returns the configured viewport scroll step.
func (*Conversation) Send ¶
func (c *Conversation) Send(ctx context.Context, in intent.Send) (ports.TurnHandle, error)
func (*Conversation) Session ¶
func (c *Conversation) Session() *chat.Session
Session returns the wrapped *chat.Session.
func (*Conversation) SetNoticeOptions ¶
func (c *Conversation) SetNoticeOptions(opts TranslateOptions)
SetNoticeOptions configures notice visibility options for this conversation.
func (*Conversation) SetScrollLines ¶
func (c *Conversation) SetScrollLines(n int)
SetScrollLines updates the viewport scroll step for this conversation.
func (*Conversation) SetShowReasoning ¶
func (c *Conversation) SetShowReasoning(show bool)
SetShowReasoning updates the default reasoning visibility for this conversation.
func (*Conversation) SetSubagents ¶
func (c *Conversation) SetSubagents(subagents *SubagentThreads)
SetSubagents connects the SubagentThreads registry for isolating subagent events from the main chat transcript.
func (*Conversation) ShowReasoning ¶
func (c *Conversation) ShowReasoning() bool
ShowReasoning returns the default reasoning visibility.
func (*Conversation) Title ¶
func (c *Conversation) Title() string
Title returns the session's display title, derived from the first user message. Memoised on first call for the current session ID.
type Input ¶
type Input struct {
// Resolved is the workspace-resolved configuration. Required; a
// nil Resolved returns an error naming "resolved" so a CLI caller
// can surface a precise diagnostic.
Resolved *config.Resolved
// WorkspaceRoot is the directory hooks and config tools anchor at.
// Required iff HooksConfigured is true: with no hooks, the empty
// string is allowed. Returning an error here keeps a silent
// mis-wiring impossible.
WorkspaceRoot string
// Workspace is the workspace.Root the tool registry uses for
// filesystem scoping. Optional; nil yields a registry with no
// filesystem tools.
Workspace *workspace.Root
// MCPConfig selects the MCP servers attached after the registry is
// built. Disabled (Enabled=false) is a no-op, not an error.
MCPConfig config.MCPConfig
// SessionID is the checkpoint principal's subject scope. Empty
// defaults to chat.Session.SessionID inside BuildSession.
SessionID string
// StorePath is the SQLite checkpoint store BuildSession opens.
// Required; an empty path returns an error naming "store path".
StorePath string
// Dispatcher tunables. BuildDispatcher passes them through; zero
// means "use the runtime defaults".
MaxDepth int
MaxRetries int
MaxInputBytes int
MaxOutputBytes int
MaxBudget int
// Completer is the provider completer the chat session's initial
// binding runs on. May be nil (chat.NewSession accepts a nil
// completer for construction); a nil completer cannot run a turn.
Completer provider.Completer
// RedactionPolicy is the privacy redaction policy forwarded to MCP
// server output handling. Nil means the workspace configured none
// and MCP redaction is skipped (matching internal/cli's default).
RedactionPolicy *redact.Policy
// HooksConfigured reports whether the dispatcher should install
// lifecycle hook closures. False (or an empty WorkspaceRoot) means
// nil compare per invocation, no hook overhead at all - the same
// contract the historical cli path held.
HooksConfigured bool
// HooksGroups returns the runnable hook groups for the current
// session. Required iff HooksConfigured is true; nil with
// HooksConfigured true is treated as "no groups".
HooksGroups func() []hooks.Group
// NoteHookWarnings receives runtime diagnostics from hooks that
// actually executed, for the caller to surface (e.g. a /hooks
// listing). Nil is safe: warnings are simply dropped.
NoteHookWarnings func([]string)
}
Input carries every value New needs to wire a real chat.Session behind the ports.Conversation seam. Most fields are direct copies of what the CLI chat command already supplies; the seam exists so cmd/mivia-ui can do the same wiring without importing internal/cli or any of its split sub-packages (cliworktree, clichat, etc.).
type SessionPool ¶
type SessionPool struct {
// contains filtered or unexported fields
}
SessionPool manages active and resumed sessions in memory. It allows background sessions to keep running while the user switches freely between them.
func NewSessionPool ¶
func NewSessionPool(initialSess *chat.Session, res *config.Resolved, agentState *cliagents.AgentSessionState, toolsOn bool) *SessionPool
NewSessionPool constructs a SessionPool seeded with the initial session.
func (*SessionPool) CreateFresh ¶
func (p *SessionPool) CreateFresh() (ports.Conversation, error)
CreateFresh creates a brand-new session, inheriting runtime state (tools, store, context manager, event bus, session directory) from the first existing pool member. It does NOT call Load — the session starts empty. The new conversation is registered in the pool and returned.
func (*SessionPool) GetOrCreate ¶
func (p *SessionPool) GetOrCreate(sessionID string) (ports.Conversation, error)
GetOrCreate retrieves an active conversation or instantiates a new session loaded from the persisted session store.
func (*SessionPool) IsActive ¶
func (p *SessionPool) IsActive(id string) bool
IsActive reports whether the session with the given ID has a turn currently in flight. A session this process has never loaded into the pool cannot be active from here, so it reports false.
func (*SessionPool) Session ¶
func (p *SessionPool) Session(id string) *chat.Session
Session returns the underlying chat.Session for a session ID, or nil if not present.
func (*SessionPool) Threads ¶
func (p *SessionPool) Threads() *SubagentThreads
Threads returns the SubagentThreads registry every pooled Conversation is wired to. Callers building the UI (internal/newtui) pass this same instance to Screen.SetSubagentThreads so the dialog resolves whichever session is currently active, including one reached by /resume or /new.
type SettingsStore ¶
type SettingsStore struct {
// contains filtered or unexported fields
}
SettingsStore holds the active configuration and state for settings management.
func NewSettingsStore ¶
func NewSettingsStore(sess *chat.Session, res *config.Resolved, state *cliagents.AgentSessionState) *SettingsStore
NewSettingsStore builds a SettingsStore populated from the resolved configuration and agent state.
func (*SettingsStore) SetActiveSession ¶
func (s *SettingsStore) SetActiveSession(sess *chat.Session)
SetActiveSession updates the active session pointer for SettingsStore.
func (*SettingsStore) SetConversation ¶
func (s *SettingsStore) SetConversation(conv *Conversation)
SetConversation attaches the active Conversation to receive live notice option updates.
func (*SettingsStore) Settings ¶
func (s *SettingsStore) Settings() ports.Settings
Settings returns the ports.Settings bundle with all section adapters.
type SubagentThreads ¶
type SubagentThreads struct {
// contains filtered or unexported fields
}
SubagentThreads implements ports.SubagentThreads by dynamically resolving threads registered during runtime subagent executions.
func NewSubagentThreads ¶
func NewSubagentThreads() *SubagentThreads
NewSubagentThreads creates a new SubagentThreads registry.
func (*SubagentThreads) HandleEvent ¶
func (s *SubagentThreads) HandleEvent(ev agent.Event, opts TranslateOptions)
HandleEvent receives subagent-originated agent.Events, translates them, records their history, and routes them to the matching SubagentTranscriptConversation.
func (*SubagentThreads) RegisterThread ¶
func (s *SubagentThreads) RegisterThread(callID string, conv ports.Conversation)
RegisterThread adds or replaces an active conversation thread for a tool call ID.
func (*SubagentThreads) Thread ¶
func (s *SubagentThreads) Thread(callID string) (ports.Conversation, bool)
Thread retrieves the conversation thread for a given tool call ID.
type SubagentTranscriptConversation ¶
type SubagentTranscriptConversation struct {
// contains filtered or unexported fields
}
SubagentTranscriptConversation represents a subagent transcript thread.
func NewSubagentTranscriptConversation ¶
func NewSubagentTranscriptConversation(title string, model ports.ModelInfo, history []ports.Message) *SubagentTranscriptConversation
NewSubagentTranscriptConversation creates a new thread conversation.
func (*SubagentTranscriptConversation) ActiveTurn ¶
func (c *SubagentTranscriptConversation) ActiveTurn() (ports.TurnHandle, bool)
ActiveTurn returns a live event subscription for the active subagent.
func (*SubagentTranscriptConversation) ContextUsage ¶
func (c *SubagentTranscriptConversation) ContextUsage() ports.Usage
ContextUsage returns token usage for the subagent thread.
func (*SubagentTranscriptConversation) History ¶
func (c *SubagentTranscriptConversation) History() []ports.Message
History returns a copy of the thread history.
func (*SubagentTranscriptConversation) ID ¶
func (c *SubagentTranscriptConversation) ID() string
ID returns the subagent thread ID.
func (*SubagentTranscriptConversation) Model ¶
func (c *SubagentTranscriptConversation) Model() ports.ModelInfo
Model returns the subagent model information.
func (*SubagentTranscriptConversation) RecordEvent ¶
func (c *SubagentTranscriptConversation) RecordEvent(e uievent.Event)
RecordEvent records one translated uievent into message history and notifies listeners.
func (*SubagentTranscriptConversation) Send ¶
func (c *SubagentTranscriptConversation) Send(_ context.Context, in intent.Send) (ports.TurnHandle, error)
Send records user messages in the thread and emits transcript stream events.
func (*SubagentTranscriptConversation) Title ¶
func (c *SubagentTranscriptConversation) Title() string
Title returns the title of the subagent thread.
type TranslateOptions ¶
TranslateOptions configures notice visibility and filtering during event translation.