@@ -28,7 +28,6 @@ import (
28 "github.com/charmbracelet/crush/internal/agent/notify"
28 "github.com/charmbracelet/crush/internal/agent/notify"
29 agenttools "github.com/charmbracelet/crush/internal/agent/tools"
29 agenttools "github.com/charmbracelet/crush/internal/agent/tools"
30 "github.com/charmbracelet/crush/internal/agent/tools/mcp"
30 "github.com/charmbracelet/crush/internal/agent/tools/mcp"
31− "github.com/charmbracelet/crush/internal/app"
32 "github.com/charmbracelet/crush/internal/commands"
31 "github.com/charmbracelet/crush/internal/commands"
33 "github.com/charmbracelet/crush/internal/config"
32 "github.com/charmbracelet/crush/internal/config"
34 "github.com/charmbracelet/crush/internal/fsext"
33 "github.com/charmbracelet/crush/internal/fsext"
@@ -50,6 +49,7 @@ import (
50 "github.com/charmbracelet/crush/internal/ui/styles"
49 "github.com/charmbracelet/crush/internal/ui/styles"
51 "github.com/charmbracelet/crush/internal/ui/util"
50 "github.com/charmbracelet/crush/internal/ui/util"
52 "github.com/charmbracelet/crush/internal/version"
51 "github.com/charmbracelet/crush/internal/version"
52+ "github.com/charmbracelet/crush/internal/workspace"
53 uv "github.com/charmbracelet/ultraviolet"
53 uv "github.com/charmbracelet/ultraviolet"
54 "github.com/charmbracelet/ultraviolet/layout"
54 "github.com/charmbracelet/ultraviolet/layout"
55 "github.com/charmbracelet/ultraviolet/screen"
55 "github.com/charmbracelet/ultraviolet/screen"
@@ -195,7 +195,7 @@ type UI struct {
198− lspStates map[string]app.LSPClientInfo
198+ lspStates map[string]workspace.LSPClientInfo
201 mcpStates map[string]mcp.ClientInfo
201 mcpStates map[string]mcp.ClientInfo
@@ -294,7 +294,7 @@ func New(com *common.Common) *UI {
294 completions: comp,
294 completions: comp,
295 attachments: attachments,
295 attachments: attachments,
296 todoSpinner: todoSpinner,
296 todoSpinner: todoSpinner,
297− lspStates: make(map[string]app.LSPClientInfo),
297+ lspStates: make(map[string]workspace.LSPClientInfo),
298 mcpStates: make(map[string]mcp.ClientInfo),
298 mcpStates: make(map[string]mcp.ClientInfo),
299 notifyBackend: notification.NoopBackend{},
299 notifyBackend: notification.NoopBackend{},
300 notifyWindowFocused: true,
300 notifyWindowFocused: true,
@@ -317,7 +317,7 @@ func New(com *common.Common) *UI {
317 desiredFocus := uiFocusEditor
317 desiredFocus := uiFocusEditor
318 if !com.Config().IsConfigured() {
318 if !com.Config().IsConfigured() {
319 desiredState = uiOnboarding
319 desiredState = uiOnboarding
320− } else if n, _ := config.ProjectNeedsInitialization(com.Store()); n {
320+ } else if n, _ := com.Workspace.ProjectNeedsInitialization(); n {
321 desiredState = uiInitialize
321 desiredState = uiInitialize
@@ -415,7 +415,7 @@ func (m *UI) loadMCPrompts() tea.Msg {
415func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
415func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
416 var cmds []tea.Cmd
416 var cmds []tea.Cmd
417 if m.hasSession() && m.isAgentBusy() {
417 if m.hasSession() && m.isAgentBusy() {
418− queueSize := m.com.App.AgentCoordinator.QueuedPrompts(m.session.ID)
418+ queueSize := m.com.Workspace.AgentQueuedPrompts(m.session.ID)
419 if queueSize != m.promptQueue {
419 if queueSize != m.promptQueue {
420 m.promptQueue = queueSize
420 m.promptQueue = queueSize
421 m.updateLayoutAndSize()
421 m.updateLayoutAndSize()
@@ -450,7 +450,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
450 m.session = msg.session
450 m.session = msg.session
451 m.sessionFiles = msg.files
451 m.sessionFiles = msg.files
452 cmds = append(cmds, m.startLSPs(msg.lspFilePaths()))
452 cmds = append(cmds, m.startLSPs(msg.lspFilePaths()))
453− msgs, err := m.com.App.Messages.List(context.Background(), m.session.ID)
453+ msgs, err := m.com.Workspace.ListMessages(context.Background(), m.session.ID)
454 if err != nil {
454 if err != nil {
455 cmds = append(cmds, util.ReportError(err))
455 cmds = append(cmds, util.ReportError(err))
@@ -567,8 +567,8 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
567 m.renderPills()
567 m.renderPills()
568 case pubsub.Event[history.File]:
568 case pubsub.Event[history.File]:
569 cmds = append(cmds, m.handleFileEvent(msg.Payload))
569 cmds = append(cmds, m.handleFileEvent(msg.Payload))
570− case pubsub.Event[app.LSPEvent]:
570+ case pubsub.Event[workspace.LSPEvent]:
571− m.lspStates = app.GetLSPStates()
571+ m.lspStates = m.com.Workspace.LSPGetStates()
572 case pubsub.Event[mcp.Event]:
572 case pubsub.Event[mcp.Event]:
573 switch msg.Payload.Type {
573 switch msg.Payload.Type {
574 case mcp.EventStateChanged:
574 case mcp.EventStateChanged:
@@ -577,11 +577,11 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
577 m.loadMCPrompts,
577 m.loadMCPrompts,
579 case mcp.EventPromptsListChanged:
579 case mcp.EventPromptsListChanged:
580− return m, handleMCPPromptsEvent(msg.Payload.Name)
580+ return m, handleMCPPromptsEvent(m.com.Workspace, msg.Payload.Name)
581 case mcp.EventToolsListChanged:
581 case mcp.EventToolsListChanged:
582− return m, handleMCPToolsEvent(m.com.Store(), msg.Payload.Name)
582+ return m, handleMCPToolsEvent(m.com.Workspace, msg.Payload.Name)
583 case mcp.EventResourcesListChanged:
583 case mcp.EventResourcesListChanged:
584− return m, handleMCPResourcesEvent(msg.Payload.Name)
584+ return m, handleMCPResourcesEvent(m.com.Workspace, msg.Payload.Name)
586 case pubsub.Event[permission.PermissionRequest]:
586 case pubsub.Event[permission.PermissionRequest]:
587 if cmd := m.openPermissionsDialog(msg.Payload); cmd != nil {
587 if cmd := m.openPermissionsDialog(msg.Payload); cmd != nil {
@@ -830,7 +830,7 @@ func (m *UI) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
831 m.textarea.Placeholder = m.readyPlaceholder
831 m.textarea.Placeholder = m.readyPlaceholder
833− if m.com.App.Permissions.SkipRequests() {
833+ if m.com.Workspace.PermissionSkipRequests() {
834 m.textarea.Placeholder = "Yolo mode!"
834 m.textarea.Placeholder = "Yolo mode!"
@@ -909,10 +909,10 @@ func (m *UI) loadNestedToolCalls(items []chat.MessageItem) {
909 messageID := toolItem.MessageID()
909 messageID := toolItem.MessageID()
911 // Get the agent tool session ID.
911 // Get the agent tool session ID.
912− agentSessionID := m.com.App.Sessions.CreateAgentToolSessionID(messageID, tc.ID)
912+ agentSessionID := m.com.Workspace.CreateAgentToolSessionID(messageID, tc.ID)
914 // Fetch nested messages.
914 // Fetch nested messages.
915− nestedMsgs, err := m.com.App.Messages.List(context.Background(), agentSessionID)
915+ nestedMsgs, err := m.com.Workspace.ListMessages(context.Background(), agentSessionID)
916 if err != nil || len(nestedMsgs) == 0 {
916 if err != nil || len(nestedMsgs) == 0 {
@@ -1114,7 +1114,7 @@ func (m *UI) handleChildSessionMessage(event pubsub.Event[message.Message]) tea.
1115 // Check if this is an agent tool session and parse it.
1115 // Check if this is an agent tool session and parse it.
1116 childSessionID := event.Payload.SessionID
1116 childSessionID := event.Payload.SessionID
1117− _, toolCallID, ok := m.com.App.Sessions.ParseAgentToolSessionID(childSessionID)
1117+ _, toolCallID, ok := m.com.Workspace.ParseAgentToolSessionID(childSessionID)
1118 if !ok {
1118 if !ok {
1119 return nil
1119 return nil
@@ -1246,8 +1246,8 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1247 // Command dialog messages
1247 // Command dialog messages
1248 case dialog.ActionToggleYoloMode:
1248 case dialog.ActionToggleYoloMode:
1249− yolo := !m.com.App.Permissions.SkipRequests()
1249+ yolo := !m.com.Workspace.PermissionSkipRequests()
1250− m.com.App.Permissions.SetSkipRequests(yolo)
1250+ m.com.Workspace.PermissionSetSkipRequests(yolo)
1251 m.setEditorPrompt(yolo)
1251 m.setEditorPrompt(yolo)
1252 m.dialog.CloseDialog(dialog.CommandsID)
1252 m.dialog.CloseDialog(dialog.CommandsID)
1253 case dialog.ActionNewSession:
1253 case dialog.ActionNewSession:
@@ -1265,7 +1265,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1267 cmds = append(cmds, func() tea.Msg {
1267 cmds = append(cmds, func() tea.Msg {
1268− err := m.com.App.AgentCoordinator.Summarize(context.Background(), msg.SessionID)
1268+ err := m.com.Workspace.AgentSummarize(context.Background(), msg.SessionID)
1269 if err != nil {
1269 if err != nil {
1270 return util.ReportError(err)()
1270 return util.ReportError(err)()
@@ -1304,10 +1304,10 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1305 currentModel := cfg.Models[agentCfg.Model]
1305 currentModel := cfg.Models[agentCfg.Model]
1306 currentModel.Think = !currentModel.Think
1306 currentModel.Think = !currentModel.Think
1307− if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, agentCfg.Model, currentModel); err != nil {
1307+ if err := m.com.Workspace.UpdatePreferredModel(config.ScopeGlobal, agentCfg.Model, currentModel); err != nil {
1308 return util.ReportError(err)()
1308 return util.ReportError(err)()
1310− m.com.App.UpdateAgentModel(context.TODO())
1310+ m.com.Workspace.UpdateAgentModel(context.TODO())
1311 status := "disabled"
1311 status := "disabled"
1312 if currentModel.Think {
1312 if currentModel.Think {
1313 status = "enabled"
1313 status = "enabled"
@@ -1345,7 +1345,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1346 // Attempt to import GitHub Copilot tokens from VSCode if available.
1346 // Attempt to import GitHub Copilot tokens from VSCode if available.
1347 if isCopilot && !isConfigured() && !msg.ReAuthenticate {
1347 if isCopilot && !isConfigured() && !msg.ReAuthenticate {
1348− m.com.Store().ImportCopilot()
1348+ m.com.Workspace.ImportCopilot()
1351 if !isConfigured() || msg.ReAuthenticate {
1351 if !isConfigured() || msg.ReAuthenticate {
@@ -1356,18 +1356,18 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1359− if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, msg.ModelType, msg.Model); err != nil {
1359+ if err := m.com.Workspace.UpdatePreferredModel(config.ScopeGlobal, msg.ModelType, msg.Model); err != nil {
1360 cmds = append(cmds, util.ReportError(err))
1360 cmds = append(cmds, util.ReportError(err))
1361 } else if _, ok := cfg.Models[config.SelectedModelTypeSmall]; !ok {
1361 } else if _, ok := cfg.Models[config.SelectedModelTypeSmall]; !ok {
1362 // Ensure small model is set is unset.
1362 // Ensure small model is set is unset.
1363− smallModel := m.com.App.GetDefaultSmallModel(providerID)
1363+ smallModel := m.com.Workspace.GetDefaultSmallModel(providerID)
1364− if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, config.SelectedModelTypeSmall, smallModel); err != nil {
1364+ if err := m.com.Workspace.UpdatePreferredModel(config.ScopeGlobal, config.SelectedModelTypeSmall, smallModel); err != nil {
1365 cmds = append(cmds, util.ReportError(err))
1365 cmds = append(cmds, util.ReportError(err))
1369 cmds = append(cmds, func() tea.Msg {
1369 cmds = append(cmds, func() tea.Msg {
1370− if err := m.com.App.UpdateAgentModel(context.TODO()); err != nil {
1370+ if err := m.com.Workspace.UpdateAgentModel(context.TODO()); err != nil {
1371 return util.ReportError(err)
1371 return util.ReportError(err)
@@ -1383,7 +1383,7 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1383 if isOnboarding {
1383 if isOnboarding {
1384 m.setState(uiLanding, uiFocusEditor)
1384 m.setState(uiLanding, uiFocusEditor)
1385 m.com.Config().SetupAgents()
1385 m.com.Config().SetupAgents()
1386− if err := m.com.App.InitCoderAgent(context.TODO()); err != nil {
1386+ if err := m.com.Workspace.InitCoderAgent(context.TODO()); err != nil {
1387 cmds = append(cmds, util.ReportError(err))
1387 cmds = append(cmds, util.ReportError(err))
@@ -1407,13 +1407,13 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1408 currentModel := cfg.Models[agentCfg.Model]
1408 currentModel := cfg.Models[agentCfg.Model]
1409 currentModel.ReasoningEffort = msg.Effort
1409 currentModel.ReasoningEffort = msg.Effort
1410− if err := m.com.Store().UpdatePreferredModel(config.ScopeGlobal, agentCfg.Model, currentModel); err != nil {
1410+ if err := m.com.Workspace.UpdatePreferredModel(config.ScopeGlobal, agentCfg.Model, currentModel); err != nil {
1411 cmds = append(cmds, util.ReportError(err))
1411 cmds = append(cmds, util.ReportError(err))
1415 cmds = append(cmds, func() tea.Msg {
1415 cmds = append(cmds, func() tea.Msg {
1416− m.com.App.UpdateAgentModel(context.TODO())
1416+ m.com.Workspace.UpdateAgentModel(context.TODO())
1417 return util.NewInfoMsg("Reasoning effort set to " + msg.Effort)
1417 return util.NewInfoMsg("Reasoning effort set to " + msg.Effort)
1419 m.dialog.CloseDialog(dialog.ReasoningID)
1419 m.dialog.CloseDialog(dialog.ReasoningID)
@@ -1421,11 +1421,11 @@ func (m *UI) handleDialogMsg(msg tea.Msg) tea.Cmd {
1421 m.dialog.CloseDialog(dialog.PermissionsID)
1421 m.dialog.CloseDialog(dialog.PermissionsID)
1422 switch msg.Action {
1422 switch msg.Action {
1423 case dialog.PermissionAllow:
1423 case dialog.PermissionAllow:
1424− m.com.App.Permissions.Grant(msg.Permission)
1424+ m.com.Workspace.PermissionGrant(msg.Permission)
1425 case dialog.PermissionAllowForSession:
1425 case dialog.PermissionAllowForSession:
1426− m.com.App.Permissions.GrantPersistent(msg.Permission)
1426+ m.com.Workspace.PermissionGrantPersistent(msg.Permission)
1427 case dialog.PermissionDeny:
1427 case dialog.PermissionDeny:
1428− m.com.App.Permissions.Deny(msg.Permission)
1428+ m.com.Workspace.PermissionDeny(msg.Permission)
1431 case dialog.ActionFilePickerSelected:
1431 case dialog.ActionFilePickerSelected:
@@ -2019,7 +2019,7 @@ func (m *UI) View() tea.View {
2020 v.MouseMode = tea.MouseModeCellMotion
2020 v.MouseMode = tea.MouseModeCellMotion
2021 v.ReportFocus = m.caps.ReportFocusEvents
2021 v.ReportFocus = m.caps.ReportFocusEvents
2022− v.WindowTitle = "crush " + home.Short(m.com.Store().WorkingDir())
2022+ v.WindowTitle = "crush " + home.Short(m.com.Workspace.WorkingDir())
2024 canvas := uv.NewScreenBuffer(m.width, m.height)
2024 canvas := uv.NewScreenBuffer(m.width, m.height)
2025 v.Cursor = m.Draw(canvas, canvas.Bounds())
2025 v.Cursor = m.Draw(canvas, canvas.Bounds())
@@ -2062,7 +2062,7 @@ func (m *UI) ShortHelp() []key.Binding {
2062 cancelBinding := k.Chat.Cancel
2062 cancelBinding := k.Chat.Cancel
2063 if m.isCanceling {
2063 if m.isCanceling {
2064 cancelBinding.SetHelp("esc", "press again to cancel")
2064 cancelBinding.SetHelp("esc", "press again to cancel")
2065− } else if m.com.App.AgentCoordinator.QueuedPrompts(m.session.ID) > 0 {
2065+ } else if m.com.Workspace.AgentQueuedPrompts(m.session.ID) > 0 {
2066 cancelBinding.SetHelp("esc", "clear queue")
2066 cancelBinding.SetHelp("esc", "clear queue")
2068 binds = append(binds, cancelBinding)
2068 binds = append(binds, cancelBinding)
@@ -2141,7 +2141,7 @@ func (m *UI) FullHelp() [][]key.Binding {
2141 cancelBinding := k.Chat.Cancel
2141 cancelBinding := k.Chat.Cancel
2142 if m.isCanceling {
2142 if m.isCanceling {
2143 cancelBinding.SetHelp("esc", "press again to cancel")
2143 cancelBinding.SetHelp("esc", "press again to cancel")
2144− } else if m.com.App.AgentCoordinator.QueuedPrompts(m.session.ID) > 0 {
2144+ } else if m.com.Workspace.AgentQueuedPrompts(m.session.ID) > 0 {
2145 cancelBinding.SetHelp("esc", "clear queue")
2145 cancelBinding.SetHelp("esc", "clear queue")
2147 binds = append(binds, []key.Binding{cancelBinding})
2147 binds = append(binds, []key.Binding{cancelBinding})
@@ -2258,7 +2258,7 @@ func (m *UI) FullHelp() [][]key.Binding {
2258func (m *UI) toggleCompactMode() tea.Cmd {
2258func (m *UI) toggleCompactMode() tea.Cmd {
2259 m.forceCompactMode = !m.forceCompactMode
2259 m.forceCompactMode = !m.forceCompactMode
2261− err := m.com.Store().SetCompactMode(config.ScopeGlobal, m.forceCompactMode)
2261+ err := m.com.Workspace.SetCompactMode(config.ScopeGlobal, m.forceCompactMode)
2262 if err != nil {
2262 if err != nil {
2263 return util.ReportError(err)
2263 return util.ReportError(err)
@@ -2600,7 +2600,7 @@ func (m *UI) insertFileCompletion(path string) tea.Cmd {
2601 if m.hasSession() {
2601 if m.hasSession() {
2602 // Skip attachment if file was already read and hasn't been modified.
2602 // Skip attachment if file was already read and hasn't been modified.
2603− lastRead := m.com.App.FileTracker.LastReadTime(context.Background(), m.session.ID, absPath)
2603+ lastRead := m.com.Workspace.FileTrackerLastReadTime(context.Background(), m.session.ID, absPath)
2604 if !lastRead.IsZero() {
2604 if !lastRead.IsZero() {
2605 if info, err := os.Stat(path); err == nil && !info.ModTime().After(lastRead) {
2605 if info, err := os.Stat(path); err == nil && !info.ModTime().After(lastRead) {
2606 return nil
2606 return nil
@@ -2638,9 +2638,8 @@ func (m *UI) insertMCPResourceCompletion(item completions.ResourceCompletionValu
2640 return func() tea.Msg {
2640 return func() tea.Msg {
2641− contents, err := mcp.ReadResource(
2641+ contents, err := m.com.Workspace.ReadMCPResource(
2642 context.Background(),
2642 context.Background(),
2644 item.MCPName,
2643 item.MCPName,
2645 item.URI,
2644 item.URI,
@@ -2708,9 +2707,8 @@ func isWhitespace(b byte) bool {
2708// isAgentBusy returns true if the agent coordinator exists and is currently
2707// isAgentBusy returns true if the agent coordinator exists and is currently
2709// busy processing a request.
2708// busy processing a request.
2710func (m *UI) isAgentBusy() bool {
2709func (m *UI) isAgentBusy() bool {
2711− return m.com.App != nil &&
2710+ return m.com.Workspace.AgentIsReady() &&
2712− m.com.App.AgentCoordinator != nil &&
2711+ m.com.Workspace.AgentIsBusy()
2713− m.com.App.AgentCoordinator.IsBusy()
2716// hasSession returns true if there is an active session with a valid ID.
2714// hasSession returns true if there is an active session with a valid ID.
@@ -2767,13 +2765,13 @@ func (m *UI) cacheSidebarLogo(width int) {
2768// sendMessage sends a message with the given content and attachments.
2766// sendMessage sends a message with the given content and attachments.
2769func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.Cmd {
2767func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.Cmd {
2770− if m.com.App.AgentCoordinator == nil {
2768+ if !m.com.Workspace.AgentIsReady() {
2771 return util.ReportError(fmt.Errorf("coder agent is not initialized"))
2769 return util.ReportError(fmt.Errorf("coder agent is not initialized"))
2774 var cmds []tea.Cmd
2772 var cmds []tea.Cmd
2775 if !m.hasSession() {
2773 if !m.hasSession() {
2776− newSession, err := m.com.App.Sessions.Create(context.Background(), "New Session")
2774+ newSession, err := m.com.Workspace.CreateSession(context.Background(), "New Session")
2777 if err != nil {
2775 if err != nil {
2778 return util.ReportError(err)
2776 return util.ReportError(err)
@@ -2790,8 +2788,8 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
2790 ctx := context.Background()
2788 ctx := context.Background()
2791 cmds = append(cmds, func() tea.Msg {
2789 cmds = append(cmds, func() tea.Msg {
2792 for _, path := range m.sessionFileReads {
2790 for _, path := range m.sessionFileReads {
2793− m.com.App.FileTracker.RecordRead(ctx, m.session.ID, path)
2791+ m.com.Workspace.FileTrackerRecordRead(ctx, m.session.ID, path)
2794− m.com.App.LSPManager.Start(ctx, path)
2792+ m.com.Workspace.LSPStart(ctx, path)
2796 return nil
2794 return nil
@@ -2799,7 +2797,7 @@ func (m *UI) sendMessage(content string, attachments ...message.Attachment) tea.
2799 // Capture session ID to avoid race with main goroutine updating m.session.
2797 // Capture session ID to avoid race with main goroutine updating m.session.
2800 sessionID := m.session.ID
2798 sessionID := m.session.ID
2801 cmds = append(cmds, func() tea.Msg {
2799 cmds = append(cmds, func() tea.Msg {
2802− _, err := m.com.App.AgentCoordinator.Run(context.Background(), sessionID, content, attachments...)
2800+ err := m.com.Workspace.AgentRun(context.Background(), sessionID, content, attachments...)
2803 if err != nil {
2801 if err != nil {
2804 isCancelErr := errors.Is(err, context.Canceled)
2802 isCancelErr := errors.Is(err, context.Canceled)
2805 isPermissionErr := errors.Is(err, permission.ErrorPermissionDenied)
2803 isPermissionErr := errors.Is(err, permission.ErrorPermissionDenied)
@@ -2833,15 +2831,14 @@ func (m *UI) cancelAgent() tea.Cmd {
2833 return nil
2831 return nil
2836− coordinator := m.com.App.AgentCoordinator
2834+ if !m.com.Workspace.AgentIsReady() {
2837− if coordinator == nil {
2838 return nil
2835 return nil
2841 if m.isCanceling {
2838 if m.isCanceling {
2842 // Second escape press - actually cancel the agent.
2839 // Second escape press - actually cancel the agent.
2843 m.isCanceling = false
2840 m.isCanceling = false
2844− coordinator.Cancel(m.session.ID)
2841+ m.com.Workspace.AgentCancel(m.session.ID)
2845 // Stop the spinning todo indicator.
2842 // Stop the spinning todo indicator.
2846 m.todoIsSpinning = false
2843 m.todoIsSpinning = false
2847 m.renderPills()
2844 m.renderPills()
@@ -2849,8 +2846,8 @@ func (m *UI) cancelAgent() tea.Cmd {
2851 // Check if there are queued prompts - if so, clear the queue.
2848 // Check if there are queued prompts - if so, clear the queue.
2852− if coordinator.QueuedPrompts(m.session.ID) > 0 {
2849+ if m.com.Workspace.AgentQueuedPrompts(m.session.ID) > 0 {
2853− coordinator.ClearQueue(m.session.ID)
2850+ m.com.Workspace.AgentClearQueue(m.session.ID)
2854 return nil
2851 return nil
@@ -3071,7 +3068,7 @@ func (m *UI) newSession() tea.Cmd {
3071 agenttools.ResetCache()
3068 agenttools.ResetCache()
3072 return tea.Batch(
3069 return tea.Batch(
3073 func() tea.Msg {
3070 func() tea.Msg {
3074− m.com.App.LSPManager.StopAll(context.Background())
3071+ m.com.Workspace.LSPStopAll(context.Background())
3075 return nil
3072 return nil
3077 m.loadPromptHistory(),
3074 m.loadPromptHistory(),
@@ -3302,7 +3299,7 @@ func (m *UI) drawSessionDetails(scr uv.Screen, area uv.Rectangle) {
3303 lspSection := m.lspInfo(sectionWidth, maxItemsPerSection, false)
3300 lspSection := m.lspInfo(sectionWidth, maxItemsPerSection, false)
3304 mcpSection := m.mcpInfo(sectionWidth, maxItemsPerSection, false)
3301 mcpSection := m.mcpInfo(sectionWidth, maxItemsPerSection, false)
3305− filesSection := m.filesInfo(m.com.Store().WorkingDir(), sectionWidth, maxItemsPerSection, false)
3302+ filesSection := m.filesInfo(m.com.Workspace.WorkingDir(), sectionWidth, maxItemsPerSection, false)
3306 sections := lipgloss.JoinHorizontal(lipgloss.Top, filesSection, " ", lspSection, " ", mcpSection)
3303 sections := lipgloss.JoinHorizontal(lipgloss.Top, filesSection, " ", lspSection, " ", mcpSection)
3307 uv.NewStyledString(
3304 uv.NewStyledString(
3308 s.CompactDetails.View.
3305 s.CompactDetails.View.
@@ -3320,7 +3317,7 @@ func (m *UI) drawSessionDetails(scr uv.Screen, area uv.Rectangle) {
3321func (m *UI) runMCPPrompt(clientID, promptID string, arguments map[string]string) tea.Cmd {
3318func (m *UI) runMCPPrompt(clientID, promptID string, arguments map[string]string) tea.Cmd {
3322 load := func() tea.Msg {
3319 load := func() tea.Msg {
3323− prompt, err := commands.GetMCPPrompt(m.com.Store(), clientID, promptID, arguments)
3320+ prompt, err := m.com.Workspace.GetMCPPrompt(clientID, promptID, arguments)
3324 if err != nil {
3321 if err != nil {
3325 // TODO: make this better
3322 // TODO: make this better
3326 return util.ReportError(err)()
3323 return util.ReportError(err)()
@@ -3347,34 +3344,30 @@ func (m *UI) runMCPPrompt(clientID, promptID string, arguments map[string]string
3348func (m *UI) handleStateChanged() tea.Cmd {
3345func (m *UI) handleStateChanged() tea.Cmd {
3349 return func() tea.Msg {
3346 return func() tea.Msg {
3350− m.com.App.UpdateAgentModel(context.Background())
3347+ m.com.Workspace.UpdateAgentModel(context.Background())
3351 return mcpStateChangedMsg{
3348 return mcpStateChangedMsg{
3352− states: mcp.GetStates(),
3349+ states: m.com.Workspace.MCPGetStates(),
3357−func handleMCPPromptsEvent(name string) tea.Cmd {
3354+func handleMCPPromptsEvent(ws workspace.Workspace, name string) tea.Cmd {
3358 return func() tea.Msg {
3355 return func() tea.Msg {
3359− mcp.RefreshPrompts(context.Background(), name)
3356+ ws.MCPRefreshPrompts(context.Background(), name)
3360 return nil
3357 return nil
3364−func handleMCPToolsEvent(cfg *config.ConfigStore, name string) tea.Cmd {
3361+func handleMCPToolsEvent(ws workspace.Workspace, name string) tea.Cmd {
3365 return func() tea.Msg {
3362 return func() tea.Msg {
3367− context.Background(),
3363+ ws.RefreshMCPTools(context.Background(), name)
3371 return nil
3364 return nil
3375−func handleMCPResourcesEvent(name string) tea.Cmd {
3368+func handleMCPResourcesEvent(ws workspace.Workspace, name string) tea.Cmd {
3376 return func() tea.Msg {
3369 return func() tea.Msg {
3377− mcp.RefreshResources(context.Background(), name)
3370+ ws.MCPRefreshResources(context.Background(), name)
3378 return nil
3371 return nil