app

package
v0.11.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

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

App is the application-layer orchestrator. It owns the agentic loop, conversation history (via MessageStore), and queue management. It is designed to be created once per session and reused across multiple prompts.

In interactive mode the caller creates a tea.Program and registers it via SetProgram; App then sends events to it as agent work progresses.

In non-interactive mode the caller uses RunOnce, which writes the response directly to an io.Writer.

App satisfies the ui.AppController interface defined in internal/ui/model.go:

Run(prompt string) int
CancelCurrentStep()
QueueLength() int
ClearQueue()
ClearMessages()

func New

func New(opts Options, initialMessages []fantasy.Message) *App

New creates a new App with the provided options and pre-loaded messages. initialMessages may be nil or empty for a fresh session.

func (*App) AddContextMessage added in v0.5.0

func (a *App) AddContextMessage(text string)

AddContextMessage adds a user-role message to the conversation history without triggering an LLM response. Used by the ! shell command prefix to inject command output into context so the LLM can reference it in subsequent turns.

Satisfies ui.AppController.

func (*App) CancelCurrentStep

func (a *App) CancelCurrentStep()

CancelCurrentStep cancels the currently executing agent step. Safe to call even when no step is running (it is a no-op in that case).

Satisfies ui.AppController.

func (*App) ClearMessages

func (a *App) ClearMessages()

ClearMessages empties the conversation history. When a tree session is active the leaf pointer is reset to the root, creating an implicit branch.

Satisfies ui.AppController.

func (*App) ClearQueue

func (a *App) ClearQueue()

ClearQueue discards all queued prompts. The caller is responsible for updating any UI state (e.g. queue badge) — ClearQueue does NOT send events to the program, because it may be called synchronously from within Bubble Tea's Update loop where prog.Send would deadlock.

Satisfies ui.AppController.

func (*App) Close

func (a *App) Close()

Close signals all background goroutines to stop and waits for them to finish. After Close() returns it is safe to call Kit.Close() / agent.Close().

func (*App) CompactConversation

func (a *App) CompactConversation(customInstructions string) error

CompactConversation summarises older messages to free context space. It returns an error synchronously if compaction cannot start (agent busy or app closed). The actual compaction runs in a background goroutine and delivers CompactCompleteEvent or CompactErrorEvent through the registered tea.Program. customInstructions is optional text appended to the summary prompt (e.g. "Focus on the API design decisions").

Satisfies ui.AppController.

func (*App) GetTreeSession

func (a *App) GetTreeSession() *session.TreeManager

GetTreeSession returns the tree session manager, or nil if not configured.

func (*App) NotifyModelChanged added in v0.3.0

func (a *App) NotifyModelChanged(provider, model string)

NotifyModelChanged sends a ModelChangedEvent to the TUI so it updates the model name in the status bar and message attribution.

func (*App) NotifyWidgetUpdate added in v0.2.0

func (a *App) NotifyWidgetUpdate()

NotifyWidgetUpdate sends a WidgetUpdateEvent to the TUI so it re-renders extension widgets. Called from the extension context's SetWidget/RemoveWidget closures. In non-interactive mode this is a no-op (widgets are TUI-only).

func (*App) PrintBlockFromExtension

func (a *App) PrintBlockFromExtension(opts extensions.PrintBlockOpts)

PrintBlockFromExtension outputs a custom styled block from an extension.

func (*App) PrintFromExtension

func (a *App) PrintFromExtension(level, text string)

PrintFromExtension outputs text from an extension to the user. The level controls styling: "" for plain text, "info" for a system message block, "error" for an error block. In interactive mode it sends an ExtensionPrintEvent through the program so the TUI can render it with the appropriate renderer. In non-interactive mode it falls back to stdout.

func (*App) QueueLength

func (a *App) QueueLength() int

QueueLength returns the number of prompts currently waiting in the queue.

Satisfies ui.AppController.

func (*App) QuitFromExtension added in v0.3.0

func (a *App) QuitFromExtension()

QuitFromExtension triggers a graceful shutdown. In interactive mode it sends a tea.QuitMsg to the program so the TUI exits cleanly. In non-interactive mode it cancels the root context, stopping any in-flight step. Safe to call from any goroutine; idempotent.

func (*App) Run

func (a *App) Run(prompt string) int

Run queues a prompt for execution. If the app is idle the prompt is executed immediately in a background goroutine; otherwise it is appended to the queue.

Returns the current queue depth after the operation: 0 means the prompt was started immediately (or the app is closed), >0 means it was queued. The caller is responsible for updating any UI state (e.g. queue badge) based on the returned value — Run does NOT send events to the program, because it may be called synchronously from within Bubble Tea's Update loop where prog.Send would deadlock.

Satisfies ui.AppController.

func (*App) RunOnce

func (a *App) RunOnce(ctx context.Context, prompt string) error

RunOnce executes a single agent step synchronously and prints the final response text to stdout. No intermediate events are emitted. Blocks until the step completes or ctx is cancelled.

func (*App) RunOnceResult added in v0.3.0

func (a *App) RunOnceResult(ctx context.Context, prompt string) (*kit.TurnResult, error)

RunOnceResult executes a single agent step synchronously and returns the full TurnResult without printing anything. This is used by --json mode to capture structured output for serialization.

func (*App) RunOnceWithDisplay

func (a *App) RunOnceWithDisplay(ctx context.Context, prompt string, eventFn func(tea.Msg)) error

RunOnceWithDisplay executes a single agent step synchronously, sending intermediate display events (spinner, tool calls, streaming chunks, etc.) to eventFn. This is the non-TUI equivalent of the interactive Run() path — used by non-interactive --prompt mode when output is needed.

The eventFn receives the same event types as the Bubble Tea TUI (SpinnerEvent, ToolCallStartedEvent, StreamChunkEvent, StepCompleteEvent, etc.) and is responsible for rendering them.

Blocks until the step completes or ctx is cancelled.

func (*App) RunWithFiles added in v0.7.0

func (a *App) RunWithFiles(prompt string, files []fantasy.FilePart) int

RunWithFiles queues a multimodal prompt (text + image files) for execution. If the app is idle the prompt executes immediately; otherwise it is queued. Returns the current queue depth (0 = started immediately, >0 = queued).

Satisfies ui.AppController (via RunWithImages which converts ImageAttachment to fantasy.FilePart).

func (*App) SendEvent added in v0.2.0

func (a *App) SendEvent(msg tea.Msg)

SendEvent sends a tea.Msg to the registered program. Safe to call from any goroutine. No-op when no program is registered.

Satisfies ui.AppController.

func (*App) SendOverlayRequest added in v0.2.0

func (a *App) SendOverlayRequest(evt OverlayRequestEvent)

SendOverlayRequest sends an OverlayRequestEvent to the TUI so the user can interact with a modal overlay dialog. In non-interactive mode (no program registered) it immediately responds with a cancelled result via the channel, ensuring the calling extension goroutine never blocks indefinitely.

func (*App) SendPromptRequest added in v0.2.0

func (a *App) SendPromptRequest(evt PromptRequestEvent)

SendPromptRequest sends a PromptRequestEvent to the TUI so the user can respond interactively. In non-interactive mode (no program registered) it immediately responds with a cancelled result via the channel, ensuring the calling extension goroutine never blocks indefinitely.

func (*App) SetEditorTextFromExtension added in v0.3.0

func (a *App) SetEditorTextFromExtension(text string)

SetEditorTextFromExtension sends an EditorTextSetEvent to the TUI to pre-fill the input editor. In non-interactive mode this is a no-op.

func (*App) SetProgram

func (a *App) SetProgram(p *tea.Program)

SetProgram registers the Bubble Tea program used to send events in interactive mode. Must be called before Run() in interactive mode.

func (*App) Steer added in v0.3.0

func (a *App) Steer(prompt string)

Steer cancels the current agent step (if running), clears the queue, and sends a new message that will execute as soon as the current step finishes cancelling. If the agent is idle, the message executes immediately. This is the "steer" delivery mode for SendMessage.

func (*App) SuspendTUI added in v0.3.0

func (a *App) SuspendTUI(callback func()) error

SuspendTUI temporarily releases the terminal from the TUI, runs the callback (which may spawn interactive subprocesses), and then restores the TUI. In non-interactive mode (no program registered) the callback runs directly with no terminal state changes.

Safe to call from any goroutine (extension command handlers run in goroutines). Blocks until the callback returns.

type CompactCompleteEvent

type CompactCompleteEvent struct {
	// Summary is the LLM-generated structured summary of the compacted messages.
	Summary string
	// OriginalTokens is the estimated token count before compaction.
	OriginalTokens int
	// CompactedTokens is the estimated token count after compaction.
	CompactedTokens int
	// MessagesRemoved is the number of messages that were summarised away.
	MessagesRemoved int
}

CompactCompleteEvent is sent when a /compact operation finishes successfully. It carries the summary text and before/after statistics.

type CompactErrorEvent

type CompactErrorEvent struct {
	// Err is the error that caused compaction to fail.
	Err error
}

CompactErrorEvent is sent when a /compact operation fails.

type EditorTextSetEvent added in v0.3.0

type EditorTextSetEvent struct {
	Text string
}

EditorTextSetEvent is sent when an extension calls ctx.SetEditorText to pre-fill the input editor with text. The TUI handles this by setting the textarea content and moving the cursor to the end.

type ExtensionPrintEvent

type ExtensionPrintEvent struct {
	// Text is the content the extension wants to display to the user.
	Text string
	// Level controls the rendering style:
	//   ""      — plain text (no styling)
	//   "info"  — system message block (bordered, themed)
	//   "error" — error block (red border, bold text)
	//   "block" — custom block with BorderColor and Subtitle
	Level string
	// BorderColor is a hex color (e.g. "#a6e3a1") for Level="block".
	BorderColor string
	// Subtitle is optional muted text below the content for Level="block".
	Subtitle string
}

ExtensionPrintEvent is sent when an extension calls ctx.Print, ctx.PrintInfo, ctx.PrintError, or ctx.PrintBlock. The TUI renders it via the appropriate renderer and tea.Println (scrollback); the CLI handler uses DisplayInfo/DisplayError or plain fmt.Println. This exists because BubbleTea captures stdout, so plain fmt.Println inside extensions would be swallowed.

type MessageCreatedEvent

type MessageCreatedEvent struct {
	// Message is the fantasy message that was added to the store.
	Message fantasy.Message
}

MessageCreatedEvent is sent when a new message is added to the message store. This allows the TUI to stay in sync with the conversation history.

type MessageStore

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

MessageStore is a thread-safe in-memory store for the conversation history. On-disk persistence is handled by the TreeManager at the app/SDK layer.

func NewMessageStore

func NewMessageStore() *MessageStore

NewMessageStore creates an empty MessageStore.

func NewMessageStoreWithMessages

func NewMessageStoreWithMessages(msgs []fantasy.Message) *MessageStore

NewMessageStoreWithMessages creates a MessageStore pre-populated with the given messages. This is used when loading an existing session at startup.

func (*MessageStore) Add

func (s *MessageStore) Add(msg fantasy.Message)

Add appends a single message to the store.

func (*MessageStore) Clear

func (s *MessageStore) Clear()

Clear removes all messages from the store.

func (*MessageStore) GetAll

func (s *MessageStore) GetAll() []fantasy.Message

GetAll returns a snapshot copy of the current message slice. The returned slice is safe to modify without affecting the store.

func (*MessageStore) Len

func (s *MessageStore) Len() int

Len returns the number of messages currently in the store.

func (*MessageStore) Replace

func (s *MessageStore) Replace(msgs []fantasy.Message)

Replace replaces the entire message history with the given slice. This is used after an agent step returns the full updated conversation (including tool calls and results).

type ModelChangedEvent added in v0.3.0

type ModelChangedEvent struct {
	// ProviderName is the new provider (e.g. "anthropic").
	ProviderName string
	// ModelName is the new model ID (e.g. "claude-3-5-haiku-20241022").
	ModelName string
}

ModelChangedEvent is sent when an extension changes the active model via ctx.SetModel. The TUI updates the model name shown in the status bar and message attribution.

type Options

type Options struct {
	// Kit is the SDK instance. executeStep() delegates to kit.PromptResult()
	// and events flow through SDK subscriptions. Required in production;
	// tests may use PromptFunc instead.
	Kit *kit.Kit

	// PromptFunc overrides Kit.PromptResult for testing. When set,
	// executeStep calls this directly, bypassing SDK event subscription
	// and usage tracking. Must not be set in production.
	PromptFunc func(ctx context.Context, prompt string) (*kit.TurnResult, error)

	// TreeSession is the tree-structured JSONL session manager. When non-nil,
	// conversation history is persisted as an append-only JSONL tree and tree
	// navigation (/tree, /fork) is enabled.
	TreeSession *session.TreeManager

	// MCPConfig is the full MCP configuration used for session continuation and
	// slash command resolution.
	MCPConfig *config.Config

	// ModelName is the display name of the model (e.g. "claude-sonnet-4-5").
	ModelName string

	// ServerNames holds the names of loaded MCP servers, used for slash command
	// autocomplete.
	ServerNames []string

	// ToolNames holds the names of available tools, used for slash command
	// autocomplete.
	ToolNames []string

	// StreamingEnabled controls whether the agent uses streaming responses.
	StreamingEnabled bool

	// Quiet suppresses all output except the final response (non-interactive mode).
	Quiet bool

	// Debug enables verbose debug logging.
	Debug bool

	// CompactMode selects the compact renderer instead of the block renderer for
	// message formatting.
	CompactMode bool

	// UsageTracker is an optional callback for recording token usage after each
	// agent step. When non-nil, the app layer calls UpdateUsage (or
	// EstimateAndUpdateUsage as a fallback) using the usage data returned by the
	// agent. Satisfied by *ui.UsageTracker; wired in cmd/root.go.
	UsageTracker UsageUpdater
}

Options configures an App instance.

type OverlayRequestEvent added in v0.2.0

type OverlayRequestEvent struct {
	// Title is displayed at the top of the dialog. Empty means no title.
	Title string
	// Content is the text to render inside the dialog body.
	Content string
	// Markdown, when true, renders Content as styled markdown.
	Markdown bool
	// BorderColor is a hex color for the dialog border. Empty uses default.
	BorderColor string
	// Background is a hex color for the dialog background. Empty = none.
	Background string
	// Width is the dialog width in columns. 0 = auto (60% of terminal).
	Width int
	// MaxHeight limits dialog height. 0 = auto (80% of terminal).
	MaxHeight int
	// Anchor is the vertical positioning: "center", "top-center", "bottom-center".
	Anchor string
	// Actions lists the action button labels. Empty = simple dismiss dialog.
	Actions []string
	// ResponseCh receives the user's response. Must have buffer size >= 1.
	ResponseCh chan<- OverlayResponse
}

OverlayRequestEvent is sent when an extension requests a modal overlay dialog. The TUI enters an overlay state, renders the dialog, and sends a single OverlayResponse through ResponseCh when the user dismisses or selects an action.

The extension goroutine blocks on the read side of ResponseCh until the TUI sends a response. The channel must have buffer size >= 1.

type OverlayResponse added in v0.2.0

type OverlayResponse struct {
	// Action is the text of the selected action button, or "" if no actions
	// were configured or the dialog was dismissed without selection.
	Action string
	// Index is the zero-based index of the selected action, or -1 if no
	// action was selected.
	Index int
	// Cancelled is true if the user dismissed the overlay (ESC) or the
	// overlay could not be shown (e.g. non-interactive mode).
	Cancelled bool
}

OverlayResponse carries the user's answer to a modal overlay dialog. The TUI sends exactly one OverlayResponse through the channel embedded in OverlayRequestEvent when the user completes or cancels the overlay.

type PromptRequestEvent added in v0.2.0

type PromptRequestEvent struct {
	// PromptType is "select", "confirm", or "input".
	PromptType string
	// Message is the question displayed to the user.
	Message string
	// Options lists the choices for select prompts.
	Options []string
	// Default is the pre-filled value: "true"/"false" for confirm prompts,
	// or the initial text for input prompts.
	Default string
	// Placeholder is the ghost text for input prompts.
	Placeholder string
	// ResponseCh receives the user's answer. The TUI must send exactly one
	// value. The channel must be buffered (cap >= 1) so sending never
	// blocks inside Update().
	ResponseCh chan<- PromptResponse
}

PromptRequestEvent is sent when an extension requests an interactive prompt from the user (select, confirm, or text input). The TUI enters a modal prompt state, renders the prompt, and sends a single PromptResponse through ResponseCh when the user completes or cancels.

The extension goroutine blocks on the read side of ResponseCh until the TUI sends a response. The channel must have buffer size >= 1.

type PromptResponse added in v0.2.0

type PromptResponse struct {
	// Value is the response text — the selected option (select), or the
	// entered text (input). Unused for confirm prompts.
	Value string
	// Index is the zero-based index of the selected option (select only).
	Index int
	// Confirmed is the boolean answer for confirm prompts.
	Confirmed bool
	// Cancelled is true if the user dismissed the prompt (ESC) or the
	// prompt could not be shown (e.g. app shutting down).
	Cancelled bool
}

PromptResponse carries the user's answer to an interactive prompt. The TUI sends exactly one PromptResponse through the channel embedded in PromptRequestEvent when the user completes or cancels the prompt.

type QueueUpdatedEvent

type QueueUpdatedEvent struct {
	// Length is the current number of messages waiting in the queue.
	Length int
}

QueueUpdatedEvent is sent whenever the message queue length changes. The TUI uses this to update the queue badge display.

type ReasoningChunkEvent added in v0.7.0

type ReasoningChunkEvent struct {
	// Delta is the incremental reasoning text from the streaming response.
	Delta string
}

ReasoningChunkEvent is sent when a streaming reasoning/thinking delta arrives from the LLM. Thinking content is rendered separately from regular text.

type ResponseCompleteEvent

type ResponseCompleteEvent struct {
	// Content is the complete final response text.
	Content string
}

ResponseCompleteEvent is sent when the LLM produces a final (non-streaming) response. In streaming mode, this may be empty if all content was delivered via StreamChunkEvents.

type SpinnerEvent

type SpinnerEvent struct {
	// Show is true to display the spinner and false to hide it.
	Show bool
}

SpinnerEvent is sent to show or hide the spinner animation in the stream component. The spinner is shown before the first streaming chunk arrives and hidden once content begins flowing or the step completes.

type StepCancelledEvent

type StepCancelledEvent struct{}

StepCancelledEvent is sent when an agent step is cancelled by the user (e.g. via double-ESC). The TUI should flush any partially streamed content, cut off the agent message where it was, and return to input state without displaying an error.

type StepCompleteEvent

type StepCompleteEvent struct {
	// ResponseText is the final assistant response text.
	ResponseText string
}

StepCompleteEvent is sent when an agent step finishes successfully.

type StepErrorEvent

type StepErrorEvent struct {
	// Err is the error that caused the step to fail.
	Err error
}

StepErrorEvent is sent when an agent step fails with an error. The TUI should display the error inline and transition back to input state.

type StreamChunkEvent

type StreamChunkEvent struct {
	// Content is the incremental text delta from the streaming response.
	Content string
}

StreamChunkEvent is sent by the app layer when a streaming text delta arrives from the LLM. Each chunk contains an incremental portion of the response.

type ToolCallContentEvent

type ToolCallContentEvent struct {
	// Content is the assistant text that accompanies one or more tool calls.
	Content string
}

ToolCallContentEvent is sent when a step includes text content alongside tool calls. This allows the TUI to display assistant commentary that accompanies tool usage.

type ToolCallStartedEvent

type ToolCallStartedEvent struct {
	// ToolName is the name of the tool being called.
	ToolName string
	// ToolArgs is the JSON-encoded arguments for the tool call.
	ToolArgs string
}

ToolCallStartedEvent is sent when a tool call has been parsed and is about to execute. It carries the tool name and its arguments for display purposes.

type ToolExecutionEvent

type ToolExecutionEvent struct {
	// ToolName is the name of the tool being executed.
	ToolName string
	// ToolArgs is the JSON-encoded arguments for the tool call (only set when IsStarting is true).
	ToolArgs string
	// IsStarting is true when execution is beginning, false when it is complete.
	IsStarting bool
}

ToolExecutionEvent is sent when a tool starts or finishes executing. The IsStarting flag distinguishes between the start and end of execution.

type ToolResultEvent

type ToolResultEvent struct {
	// ToolName is the name of the tool that was executed.
	ToolName string
	// ToolArgs is the JSON-encoded arguments that were passed to the tool.
	ToolArgs string
	// Result is the text output from the tool execution.
	Result string
	// IsError indicates whether the tool returned an error result.
	IsError bool
}

ToolResultEvent is sent after a tool execution completes with its result.

type UsageUpdater

type UsageUpdater interface {
	// UpdateUsage records actual token counts returned by the provider.
	// The counts come from fantasy's TotalUsage (aggregate across all steps
	// in a multi-step tool-calling run) and are used for session cost tracking.
	UpdateUsage(inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens int)
	// EstimateAndUpdateUsage falls back to text-based token estimation when
	// the provider does not return exact counts.
	EstimateAndUpdateUsage(inputText, outputText string)
	// SetContextTokens records the approximate current context window fill
	// level. This should be the final API call's input+output tokens (from
	// FinalResponse.Usage), NOT the aggregate TotalUsage.
	SetContextTokens(tokens int)
}

UsageUpdater is the interface the app layer uses to record token usage after each agent step. It is satisfied by *ui.UsageTracker (which lives in internal/ui) without creating an import cycle — the concrete type is wired in cmd/root.go, which can import both packages.

type WidgetUpdateEvent added in v0.2.0

type WidgetUpdateEvent struct{}

WidgetUpdateEvent is sent when an extension adds, updates, or removes a widget via ctx.SetWidget or ctx.RemoveWidget. The TUI re-reads widget state from its WidgetProvider on the next render cycle.

Jump to

Keyboard shortcuts

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