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