Documentation
¶
Overview ¶
internal/agent/tui/attach_picker.go
internal/agent/tui/cmds.go
internal/agent/tui/keymap.go
internal/agent/tui/login_panel.go
internal/agent/tui/logout_panel.go
internal/agent/tui/model.go
internal/agent/tui/panels.go
internal/agent/tui/runtime_cwd.go
internal/agent/tui/styles.go
internal/agent/tui/view.go
Index ¶
- Variables
- func RenderView(m *Model) string
- type APIError
- type AskUserPanelInput
- type AttachReplyMsg
- type AttachResponse
- type AttachmentPickedMsg
- type AttachmentRemovedMsg
- type AuthConfig
- type AuthController
- func (a *AuthController) CancelLogin()
- func (a *AuthController) EnsureValid(ctx context.Context) (string, error)
- func (a *AuthController) Logout() error
- func (a *AuthController) SetOnChange(fn func(AuthState))
- func (a *AuthController) SetOnLoginFailed(fn func(error))
- func (a *AuthController) StartLogin(ctx context.Context) (LoginInfo, error)
- func (a *AuthController) State() AuthState
- type AuthSource
- type AuthState
- type AuthStateChangedMsg
- type Bus
- func (b *Bus) AccessToken(ctx context.Context) (string, error)
- func (b *Bus) AttachSession(ctx context.Context, sid, mode string) (*AttachResponse, error)
- func (b *Bus) ExecutorID() string
- func (b *Bus) FetchExecutorStatus(ctx context.Context) (*ExecutorStatusResp, error)
- func (b *Bus) ListSessions(ctx context.Context) ([]SessionListItem, error)
- func (b *Bus) ListWorkspaces(ctx context.Context) ([]WorkspaceListItem, error)
- func (b *Bus) NewSession(ctx context.Context, permissionMode string, preferredExecutorID string) (string, error)
- func (b *Bus) PostCancel(ctx context.Context, sid, tid string) error
- func (b *Bus) PostControl(ctx context.Context, sid, command string, args map[string]any) (json.RawMessage, error)
- func (b *Bus) PostDecision(ctx context.Context, sid, pid, decision, scope string) error
- func (b *Bus) PostInbound(ctx context.Context, in InboundRequest) (*InboundResponse, error)
- func (b *Bus) ServerURL() string
- func (b *Bus) SetExecutorID(id string)
- func (b *Bus) SetWorkspaceID(id string)
- func (b *Bus) WorkspaceID() string
- type BusConfig
- type CancelLoginMsg
- type CancelReplyMsg
- type ClearRequestedMsg
- type CommandClass
- type CommandSelectedMsg
- type ConfirmLogoutMsg
- type ControlReplyMsg
- type DecisionAckMsg
- type DeviceCodeReadyMsg
- type EventArrivedMsg
- type ExecutorStatusResp
- type FatalErrorMsg
- type InboundAcceptedMsg
- type InboundAttachment
- type InboundRejectedMsg
- type InboundRequest
- type InboundResponse
- type InitialStateMsg
- type KeyMap
- type ListSessionsReplyMsg
- type LoginInfo
- type LoginPollDoneMsg
- type LogoutDoneMsg
- type Mode
- type Model
- type ModelConfig
- type NewSessionReplyMsg
- type Panel
- type ParsedCommand
- type PermissionPanelInput
- type RequeuePermissionMsg
- type ResumeRequestedMsg
- type SSEConfig
- type SSEConsumer
- type SSEEvent
- type SSEStatusMsg
- type SendAnswerMsg
- type SendDecisionMsg
- type SendPromptMsg
- type SessionListItem
- type StatusTickMsg
- type Timeline
- type TimelineItem
- type WorkspaceListItem
Constants ¶
This section is empty.
Variables ¶
var ( StyleBorder = lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).Padding(0, 1) StylePanelTitle = lipgloss.NewStyle().Bold(true) StyleStatusBar = lipgloss.NewStyle().Background(lipgloss.Color("#222")).Foreground(lipgloss.Color("#ccc")) StyleStatusBarErr = StyleStatusBar.Foreground(lipgloss.Color("#FF7A7A")) StyleHint = lipgloss.NewStyle().Faint(true) StyleAuthErr = lipgloss.NewStyle().Foreground(lipgloss.Color("#FF7A7A")).Bold(true) StyleAuthOk = lipgloss.NewStyle().Foreground(lipgloss.Color("#5FFF87")) )
Functions ¶
func RenderView ¶
RenderView produces the full screen text for the Model. Layout:
[statusBar 2 lines] [viewport (scrolling timeline)] [activePanel (optional)] [input area + LoggedOut hint]
Types ¶
type APIError ¶
APIError is returned for any 4xx/5xx response with the standard {"error":{"code":"...","message":"..."}} body. Code may be empty for non-standard responses.
type AskUserPanelInput ¶
type AttachReplyMsg ¶
type AttachReplyMsg struct {
Resp *AttachResponse
Err error
}
type AttachResponse ¶
type AttachmentPickedMsg ¶
type AttachmentPickedMsg struct{ Attachment InboundAttachment }
type AttachmentRemovedMsg ¶
type AttachmentRemovedMsg struct{ Index int }
type AuthConfig ¶
type AuthConfig struct {
ServerURL string
CredentialsPath string
SkipOpenBrowser bool
OnChange func(AuthState)
// OnLoginFailed is called (from the poll goroutine) when the OAuth Device
// Flow fails for any reason other than user cancellation (context cancel).
// nil if caller doesn't need this signal.
OnLoginFailed func(error)
// Test seams (default to real implementations from internal/agent/login.go).
RequestDeviceCode func(serverURL string) (*agent.DeviceAuthResponse, error)
PollForToken func(serverURL string, dr *agent.DeviceAuthResponse) (*agent.TokenResponse, error)
}
AuthConfig holds configuration for AuthController.
type AuthController ¶
type AuthController struct {
// contains filtered or unexported fields
}
AuthController manages the OAuth authentication state machine.
func NewAuthController ¶
func NewAuthController(cfg AuthConfig) *AuthController
NewAuthController creates an AuthController and initialises state from stored credentials. If valid credentials exist at cfg.CredentialsPath the state starts as AuthLoggedIn.
func (*AuthController) CancelLogin ¶
func (a *AuthController) CancelLogin()
CancelLogin aborts an in-progress login flow.
func (*AuthController) EnsureValid ¶
func (a *AuthController) EnsureValid(ctx context.Context) (string, error)
EnsureValid returns a non-empty access token or an error. If the token is near expiry it triggers a refresh (state → Refreshing). Refresh failure transitions to LoggedOut.
func (*AuthController) Logout ¶
func (a *AuthController) Logout() error
Logout clears in-memory credentials and invalidates the credentials file so that subsequent LoadCredentials calls return an error (file contains no parseable content).
func (*AuthController) SetOnChange ¶
func (a *AuthController) SetOnChange(fn func(AuthState))
SetOnChange installs or replaces the OnChange callback after construction. Used by RunTUI to wire the Bubble Tea program after both AuthController and the program exist.
func (*AuthController) SetOnLoginFailed ¶
func (a *AuthController) SetOnLoginFailed(fn func(error))
SetOnLoginFailed installs or replaces the OnLoginFailed callback after construction. Used by RunTUI to surface login errors to the TUI timeline.
func (*AuthController) StartLogin ¶
func (a *AuthController) StartLogin(ctx context.Context) (LoginInfo, error)
StartLogin kicks off OAuth Device Flow. Returns the user-visible code and URL synchronously; the polling loop runs in a goroutine and eventually transitions state to LoggedIn or LoggedOut.
func (*AuthController) State ¶
func (a *AuthController) State() AuthState
State returns the current authentication state.
type AuthSource ¶
AuthSource is implemented by AuthController. Bus uses it to fetch a fresh access token for every request (it's cheap when the token is already valid).
type AuthState ¶
type AuthState int32
AuthState represents the current authentication state of the controller.
type Bus ¶
type Bus struct {
// contains filtered or unexported fields
}
func (*Bus) AccessToken ¶
AccessToken exposes Auth.EnsureValid for the SSE consumer, which builds long-lived requests outside of `do`'s code path.
func (*Bus) AttachSession ¶
func (*Bus) ExecutorID ¶
func (*Bus) FetchExecutorStatus ¶
func (b *Bus) FetchExecutorStatus(ctx context.Context) (*ExecutorStatusResp, error)
func (*Bus) ListSessions ¶
func (b *Bus) ListSessions(ctx context.Context) ([]SessionListItem, error)
func (*Bus) ListWorkspaces ¶ added in v0.48.1
func (b *Bus) ListWorkspaces(ctx context.Context) ([]WorkspaceListItem, error)
ListWorkspaces returns all workspaces the authenticated user is a member of. Used at startup when --workspace-id is not provided and no saved executor session matches the current server.
func (*Bus) NewSession ¶
func (*Bus) PostControl ¶
func (*Bus) PostDecision ¶
func (*Bus) PostInbound ¶
func (b *Bus) PostInbound(ctx context.Context, in InboundRequest) (*InboundResponse, error)
PostInbound submits a user prompt. The server creates a session implicitly if SessionID is empty, then claims an active turn (returning 409 if one is already in progress) and asynchronously kicks off cc-broker.
func (*Bus) SetExecutorID ¶
SetExecutorID updates the executor ID on the Bus. This should only be called during initialization (before any session activity), not concurrently with active requests. Used by tui_run.go when the executor is registered lazily (post-login) and the ID wasn't known at Bus construction time.
func (*Bus) SetWorkspaceID ¶ added in v0.48.1
SetWorkspaceID updates the workspace ID on the Bus. Same caveats as SetExecutorID: only call during init, before any concurrent requests. Used when --workspace-id wasn't provided and the ID is resolved post-login by listing the user's workspaces.
func (*Bus) WorkspaceID ¶ added in v0.48.1
type CancelLoginMsg ¶
type CancelLoginMsg struct{}
CancelLoginMsg is emitted by the login panel when the user presses Esc. The Model converts this to AuthController.CancelLogin().
type CancelReplyMsg ¶
type CancelReplyMsg struct{ Err error }
type ClearRequestedMsg ¶
type ClearRequestedMsg struct{}
type CommandClass ¶
type CommandClass int
CommandClass distinguishes how a slash command is dispatched:
- LocalClass: handled in-process by the TUI (e.g. /quit).
- SessionClass: changes which session this TUI is attached to.
- RemoteClass: forwarded to agentserver /control. The TUI does not interpret these — they're whatever agentserver supports today plus any future R-class command added server-side without TUI changes.
const ( LocalClass CommandClass = iota SessionClass RemoteClass )
type CommandSelectedMsg ¶
type CommandSelectedMsg struct{ Command, Args string }
type ConfirmLogoutMsg ¶
type ConfirmLogoutMsg struct{}
ConfirmLogoutMsg is emitted when the user confirms logout. Model calls AuthController.Logout in response.
type ControlReplyMsg ¶
type ControlReplyMsg struct {
Command string
Body json.RawMessage
Err error
}
type DecisionAckMsg ¶
type DeviceCodeReadyMsg ¶
type DeviceCodeReadyMsg struct{ Info LoginInfo }
type ExecutorStatusResp ¶
type InboundAcceptedMsg ¶
type InboundAcceptedMsg struct{ SessionID, TurnID string }
HTTP-driven (Bus replies)
type InboundAttachment ¶
type InboundAttachment struct {
Kind string `json:"kind"`
Filename string `json:"filename"`
Size int `json:"size"`
ContentB64 string `json:"content_b64"`
}
func AttachFromPath ¶
func AttachFromPath(path string) (InboundAttachment, error)
AttachFromPath reads a file off disk and returns it as an InboundAttachment ready to drop into the next prompt. Image files (recognised by extension) get kind="image"; everything else is "file". The 8 MiB cap is per-file (the cumulative cap is enforced server-side).
type InboundRejectedMsg ¶
type InboundRejectedMsg struct{ Code, Message string }
type InboundRequest ¶
type InboundResponse ¶
type InitialStateMsg ¶
type KeyMap ¶
type KeyMap struct {
Send key.Binding
Newline key.Binding
Cancel key.Binding
Quit key.Binding
SessionPick key.Binding
Slash key.Binding
Help key.Binding
PageUp key.Binding
PageDown key.Binding
Top key.Binding
Bottom key.Binding
}
KeyMap centralises every keybinding so the help screen and Update can share definitions. Add new bindings here, not scattered in handlers.
type ListSessionsReplyMsg ¶
type ListSessionsReplyMsg struct {
Sessions []SessionListItem
Err error
}
type LoginInfo ¶
LoginInfo contains the user-visible information from the device authorization flow.
type LoginPollDoneMsg ¶
type LoginPollDoneMsg struct{ Err error }
type LogoutDoneMsg ¶
type LogoutDoneMsg struct{ Err error }
type Mode ¶
type Mode int
Mode tracks which input handler owns the keypress stream right now.
const ( ModeNormal Mode = iota // input box has focus ModeAwaitPerm // permission panel is up ModeAwaitAskUser // ask_user panel is up ModeAwaitLogin // OAuth Device Flow panel is up ModeAwaitLogout // logout confirmation panel is up ModeCommand // slash command palette ModeAttachPicker // file picker for /attach ModeQuitting )
type Model ¶
type Model struct {
// contains filtered or unexported fields
}
func NewModel ¶
func NewModel(cfg ModelConfig) *Model
func (*Model) InputEnabled ¶
func (*Model) SetAuthState ¶
func (*Model) Update ¶
Update dispatches a Msg to the right handler. The shape:
- EventArrivedMsg / AuthStateChangedMsg / panel keys / Send*Msg are handled regardless of mode.
- CommandSelectedMsg routes through runCommand (LocalClass / SessionClass / RemoteClass).
- Plain KeyMsg in ModeNormal goes to handleNormalKey, then falls through to the textarea if not handled.
type ModelConfig ¶
type ModelConfig struct {
ServerURL string
WorkspaceID string
ExecutorID string
Bus *Bus
Auth *AuthController
Yolo bool
InitialModel string
Resume string
Continue bool
// OnLoggedIn fires when AuthState transitions to LoggedIn. Used to
// start ExecutorClient + register executor (lazily, post-login).
// nil if caller doesn't need this signal.
OnLoggedIn func()
// OnSessionReady fires whenever the model's sessionID becomes known
// or changes (Resume on Init, NewSessionReplyMsg, InboundAcceptedMsg
// for first-prompt-creates-session, ResumeRequestedMsg). Used to start
// / restart the SSE consumer goroutine. May be called multiple times;
// implementations should cancel any previous consumer before starting
// a new one.
OnSessionReady func(sessionID string)
}
type NewSessionReplyMsg ¶
type Panel ¶
type Panel interface {
View(width int) string
HandleKey(msg tea.KeyMsg) (Panel, tea.Cmd, bool)
ID() string
}
Panel is the interface implemented by every floating overlay (permission, ask_user, login, logout, etc.). The Model treats panels uniformly: route keys via HandleKey, render via View, dismiss via the boolean.
func NewAskUserPanel ¶
func NewAskUserPanel(in AskUserPanelInput) Panel
func NewLoginPanel ¶
func NewLogoutPanel ¶
func NewLogoutPanel() Panel
func NewPermissionPanel ¶
func NewPermissionPanel(in PermissionPanelInput) Panel
type ParsedCommand ¶
type ParsedCommand struct {
Class CommandClass
Name string
Args string
}
func ParseSlashCommand ¶
func ParseSlashCommand(line string) (ParsedCommand, bool)
ParseSlashCommand classifies a slash-prefixed line. Anything that's neither local nor session falls through to RemoteClass — agentserver decides if it's recognised.
type PermissionPanelInput ¶
type RequeuePermissionMsg ¶
type RequeuePermissionMsg struct{ Panel Panel }
RequeuePermissionMsg is emitted by the permission panel when the user presses Esc ("answer later"). The Model appends the panel to the back of permQueue so the request isn't permanently dismissed.
type ResumeRequestedMsg ¶
type ResumeRequestedMsg struct{ SessionID string }
type SSEConfig ¶
type SSEConfig struct {
SessionID string
InitialBackoff time.Duration // default 1s
MaxBackoff time.Duration // default 30s
HTTP *http.Client // optional; defaults to no timeout (SSE is long-lived)
}
SSEConfig configures the consumer's connection + reconnect behavior.
type SSEConsumer ¶
type SSEConsumer struct {
// contains filtered or unexported fields
}
SSEConsumer streams events from the agentserver SSE endpoint with automatic reconnection. The server-side replay mechanism (Last-Event-ID) resumes from the last received event after a transient failure.
func NewSSEConsumer ¶
func NewSSEConsumer(bus *Bus, cfg SSEConfig) *SSEConsumer
type SSEStatusMsg ¶
type SendAnswerMsg ¶
SendAnswerMsg is emitted by the ask_user panel when the user submits. The Model converts this into a Bus.PostAnswer call (endpoint TBD in agent-side; for now the Model can just log it).
type SendDecisionMsg ¶
type SendDecisionMsg struct {
PID, Verdict, Scope string
}
SendDecisionMsg is emitted by the permission panel when the user picks a verdict. The Model converts this into a Bus.PostDecision call.
type SendPromptMsg ¶
type SendPromptMsg struct {
Text string
Attachments []InboundAttachment
Metadata map[string]any
}
Internal user actions
type SessionListItem ¶
type Timeline ¶
type Timeline struct {
// contains filtered or unexported fields
}
Timeline buffers up to `cap` items, indexed by event ID and (for permission_request items) by permission ID so resolutions can find their requests. Render serialises every item into a single multi-line string.
func NewTimeline ¶
type TimelineItem ¶
type TimelineItem struct {
EventID string
EventType string
Payload json.RawMessage
Resolution map[string]any // for permission_request items: filled when resolved
}
TimelineItem is one rendered row (or block) in the message stream.