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}