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}