workspace.go

  1// Package workspace defines the Workspace interface used by all
  2// frontends (TUI, CLI) to interact with a running workspace. Two
  3// implementations exist: one wrapping a local app.App instance and one
  4// wrapping the HTTP client SDK.
  5package workspace
  6
  7import (
  8	"context"
  9	"time"
 10
 11	tea "charm.land/bubbletea/v2"
 12	"charm.land/catwalk/pkg/catwalk"
 13	mcptools "github.com/charmbracelet/crush/internal/agent/tools/mcp"
 14	"github.com/charmbracelet/crush/internal/config"
 15	"github.com/charmbracelet/crush/internal/history"
 16	"github.com/charmbracelet/crush/internal/lsp"
 17	"github.com/charmbracelet/crush/internal/message"
 18	"github.com/charmbracelet/crush/internal/oauth"
 19	"github.com/charmbracelet/crush/internal/permission"
 20	"github.com/charmbracelet/crush/internal/session"
 21)
 22
 23// LSPClientInfo holds information about an LSP client's state. This is
 24// the frontend-facing type; implementations translate from the
 25// underlying app or proto representation.
 26type LSPClientInfo struct {
 27	Name            string
 28	State           lsp.ServerState
 29	Error           error
 30	DiagnosticCount int
 31	ConnectedAt     time.Time
 32}
 33
 34// LSPEventType represents the type of LSP event.
 35type LSPEventType string
 36
 37const (
 38	LSPEventStateChanged       LSPEventType = "state_changed"
 39	LSPEventDiagnosticsChanged LSPEventType = "diagnostics_changed"
 40)
 41
 42// LSPEvent represents an LSP event forwarded to the TUI.
 43type LSPEvent struct {
 44	Type            LSPEventType
 45	Name            string
 46	State           lsp.ServerState
 47	Error           error
 48	DiagnosticCount int
 49}
 50
 51// AgentModel holds the model information exposed to the UI.
 52type AgentModel struct {
 53	CatwalkCfg catwalk.Model
 54	ModelCfg   config.SelectedModel
 55}
 56
 57// Workspace is the main abstraction consumed by the TUI and CLI. It
 58// groups every operation a frontend needs to perform against a running
 59// workspace, regardless of whether the workspace is in-process or
 60// remote.
 61type Workspace interface {
 62	// Sessions
 63	CreateSession(ctx context.Context, title string) (session.Session, error)
 64	GetSession(ctx context.Context, sessionID string) (session.Session, error)
 65	ListSessions(ctx context.Context) ([]session.Session, error)
 66	SaveSession(ctx context.Context, sess session.Session) (session.Session, error)
 67	DeleteSession(ctx context.Context, sessionID string) error
 68	CreateAgentToolSessionID(messageID, toolCallID string) string
 69	ParseAgentToolSessionID(sessionID string) (messageID string, toolCallID string, ok bool)
 70	// SetCurrentSession reports the session this client is currently
 71	// viewing. Empty sessionID clears the entry (e.g. landing screen).
 72	// In single-client local mode this is a no-op. In client/server
 73	// mode it informs the server's per-client presence map so other
 74	// observers can compute attached-client counts per session.
 75	SetCurrentSession(ctx context.Context, sessionID string) error
 76
 77	// Messages
 78	ListMessages(ctx context.Context, sessionID string) ([]message.Message, error)
 79	ListUserMessages(ctx context.Context, sessionID string) ([]message.Message, error)
 80	ListAllUserMessages(ctx context.Context) ([]message.Message, error)
 81
 82	// Agent
 83	AgentRun(ctx context.Context, sessionID, prompt string, attachments ...message.Attachment) error
 84	AgentCancel(sessionID string)
 85	AgentIsBusy() bool
 86	AgentIsSessionBusy(sessionID string) bool
 87	AgentModel() AgentModel
 88	AgentIsReady() bool
 89	AgentQueuedPrompts(sessionID string) int
 90	AgentQueuedPromptsList(sessionID string) []string
 91	AgentClearQueue(sessionID string)
 92	AgentSummarize(ctx context.Context, sessionID string) error
 93	UpdateAgentModel(ctx context.Context) error
 94	InitCoderAgent(ctx context.Context) error
 95	GetDefaultSmallModel(providerID string) config.SelectedModel
 96
 97	// Permissions
 98	//
 99	// PermissionGrant, PermissionGrantPersistent, and PermissionDeny
100	// return true if the call resolved the pending request and false if
101	// it had already been resolved by another subscriber (or is no
102	// longer pending). A false return is not an error; the modal can
103	// still close locally because the resolution will arrive via the
104	// PermissionNotification event stream regardless of which client
105	// won the race.
106	PermissionGrant(perm permission.PermissionRequest) bool
107	PermissionGrantPersistent(perm permission.PermissionRequest) bool
108	PermissionDeny(perm permission.PermissionRequest) bool
109	PermissionSkipRequests() bool
110	PermissionSetSkipRequests(skip bool)
111
112	// FileTracker
113	FileTrackerRecordRead(ctx context.Context, sessionID, path string)
114	FileTrackerLastReadTime(ctx context.Context, sessionID, path string) time.Time
115	FileTrackerListReadFiles(ctx context.Context, sessionID string) ([]string, error)
116
117	// History
118	ListSessionHistory(ctx context.Context, sessionID string) ([]history.File, error)
119
120	// LSP
121	LSPStart(ctx context.Context, path string)
122	LSPStopAll(ctx context.Context)
123	LSPGetStates() map[string]LSPClientInfo
124	LSPGetDiagnosticCounts(name string) lsp.DiagnosticCounts
125
126	// Config (read-only data)
127	Config() *config.Config
128	WorkingDir() string
129	Resolver() config.VariableResolver
130
131	// Config mutations (proxied to server in client mode)
132	UpdatePreferredModel(scope config.Scope, modelType config.SelectedModelType, model config.SelectedModel) error
133	SetCompactMode(scope config.Scope, enabled bool) error
134	SetProviderAPIKey(scope config.Scope, providerID string, apiKey any) error
135	SetConfigField(scope config.Scope, key string, value any) error
136	RemoveConfigField(scope config.Scope, key string) error
137	ImportCopilot() (*oauth.Token, bool)
138	RefreshOAuthToken(ctx context.Context, scope config.Scope, providerID string) error
139
140	// Project lifecycle
141	ProjectNeedsInitialization() (bool, error)
142	MarkProjectInitialized() error
143	InitializePrompt() (string, error)
144
145	// MCP operations (server-side in client mode)
146	MCPGetStates() map[string]mcptools.ClientInfo
147	MCPRefreshPrompts(ctx context.Context, name string)
148	MCPRefreshResources(ctx context.Context, name string)
149	RefreshMCPTools(ctx context.Context, name string)
150	ReadMCPResource(ctx context.Context, name, uri string) ([]MCPResourceContents, error)
151	GetMCPPrompt(clientID, promptID string, args map[string]string) (string, error)
152	EnableDockerMCP(ctx context.Context) error
153	DisableDockerMCP() error
154
155	// Events
156	Subscribe(program *tea.Program)
157	Shutdown()
158}
159
160// MCPResourceContents holds the contents of an MCP resource.
161type MCPResourceContents struct {
162	URI      string `json:"uri"`
163	MIMEType string `json:"mime_type,omitempty"`
164	Text     string `json:"text,omitempty"`
165	Blob     []byte `json:"blob,omitempty"`
166}