| 1 | using System.Text.Json; |
| 2 | using CascadeIDE.Features.IdeMcp.Application; |
| 3 | using CascadeIDE.Models; |
| 4 | |
| 5 | namespace CascadeIDE.Features.IdeMcp.Execution; |
| 6 | |
| 7 | /// <summary>MCP-хендлеры Power / фокус-шагов, автономного агента, чата и установки модели Ollama.</summary> |
| 8 | internal sealed partial class IdeMcpCommandExecutor |
| 9 | { |
| 10 | private void RegisterFocusPowerAndAgentActions(Action<string, Handler> add) |
| 11 | { |
| 12 | add(Services.IdeCommands.FocusCheckpoint, async (_, _) => |
| 13 | { |
| 14 | if (_vm.FocusCheckpointCommand.CanExecute(null)) |
| 15 | _vm.FocusCheckpointCommand.Execute(null); |
| 16 | return "OK"; |
| 17 | }); |
| 18 | add(Services.IdeCommands.FocusRollback, async (_, _) => |
| 19 | { |
| 20 | if (_vm.FocusRollbackCommand.CanExecute(null)) |
| 21 | _vm.FocusRollbackCommand.Execute(null); |
| 22 | return "OK"; |
| 23 | }); |
| 24 | add(Services.IdeCommands.ConfirmFocusStep, async (_, _) => |
| 25 | { |
| 26 | if (_vm.ConfirmFocusStepCommand.CanExecute(null)) |
| 27 | _vm.ConfirmFocusStepCommand.Execute(null); |
| 28 | return "OK"; |
| 29 | }); |
| 30 | add(Services.IdeCommands.CancelFocusStep, async (_, _) => |
| 31 | { |
| 32 | if (_vm.CancelFocusStepCommand.CanExecute(null)) |
| 33 | _vm.CancelFocusStepCommand.Execute(null); |
| 34 | return "OK"; |
| 35 | }); |
| 36 | add(Services.IdeCommands.ExplainCurrentStep, async (_, _) => |
| 37 | { |
| 38 | if (_vm.ExplainCurrentStepCommand.CanExecute(null)) |
| 39 | _vm.ExplainCurrentStepCommand.Execute(null); |
| 40 | return "OK"; |
| 41 | }); |
| 42 | add(Services.IdeCommands.EmergencyStop, async (_, _) => |
| 43 | { |
| 44 | if (_vm.EmergencyStopCommand.CanExecute(null)) |
| 45 | _vm.EmergencyStopCommand.Execute(null); |
| 46 | return "OK"; |
| 47 | }); |
| 48 | add(Services.IdeCommands.RefreshWorkspaceSnapshot, async (_, _) => |
| 49 | { |
| 50 | if (_vm.RefreshWorkspaceSnapshotCommand.CanExecute(null)) |
| 51 | _vm.RefreshWorkspaceSnapshotCommand.Execute(null); |
| 52 | return "OK"; |
| 53 | }); |
| 54 | |
| 55 | add(Services.IdeCommands.ExplainTraceStep, async (args, _) => |
| 56 | { |
| 57 | if (args is null || !args.TryGetValue("step_index", out var exIdx) || exIdx.ValueKind != JsonValueKind.Number || !exIdx.TryGetInt32(out var explainStepIndex) || explainStepIndex < 0) |
| 58 | return "Missing or invalid step_index (non-negative int; 0 = oldest in AgentTraceSteps)"; |
| 59 | var list = _vm.InstrumentationPanel.AgentTraceSteps; |
| 60 | if (explainStepIndex >= list.Count) |
| 61 | return $"Invalid step_index (count={list.Count})"; |
| 62 | _vm.ExplainTraceStepCommand.Execute(list[explainStepIndex]); |
| 63 | return "OK"; |
| 64 | }); |
| 65 | add(Services.IdeCommands.RollbackTraceStep, async (args, _) => |
| 66 | { |
| 67 | if (args is null || !args.TryGetValue("step_index", out var rbIdx) || rbIdx.ValueKind != JsonValueKind.Number || !rbIdx.TryGetInt32(out var rollbackStepIndex) || rollbackStepIndex < 0) |
| 68 | return "Missing or invalid step_index (non-negative int; 0 = oldest in AgentTraceSteps)"; |
| 69 | var listRb = _vm.InstrumentationPanel.AgentTraceSteps; |
| 70 | if (rollbackStepIndex >= listRb.Count) |
| 71 | return $"Invalid step_index (count={listRb.Count})"; |
| 72 | _vm.RollbackTraceStepCommand.Execute(listRb[rollbackStepIndex]); |
| 73 | return "OK"; |
| 74 | }); |
| 75 | |
| 76 | add(Services.IdeCommands.SetSafetyObserve, async (_, _) => |
| 77 | { |
| 78 | if (_vm.SetSafetyObserveCommand.CanExecute(null)) |
| 79 | _vm.SetSafetyObserveCommand.Execute(null); |
| 80 | return "OK"; |
| 81 | }); |
| 82 | add(Services.IdeCommands.SetSafetyConfirm, async (_, _) => |
| 83 | { |
| 84 | if (_vm.SetSafetyConfirmCommand.CanExecute(null)) |
| 85 | _vm.SetSafetyConfirmCommand.Execute(null); |
| 86 | return "OK"; |
| 87 | }); |
| 88 | add(Services.IdeCommands.SetSafetyAutonomous, async (_, _) => |
| 89 | { |
| 90 | if (_vm.SetSafetyAutonomousCommand.CanExecute(null)) |
| 91 | _vm.SetSafetyAutonomousCommand.Execute(null); |
| 92 | return "OK"; |
| 93 | }); |
| 94 | |
| 95 | add(Services.IdeCommands.StartAutonomous, async (_, _) => |
| 96 | { |
| 97 | if (_vm.Autonomous.StartAutonomousCommand.CanExecute(null)) |
| 98 | _vm.Autonomous.StartAutonomousCommand.Execute(null); |
| 99 | return "OK"; |
| 100 | }); |
| 101 | add(Services.IdeCommands.PauseAutonomous, async (_, _) => |
| 102 | { |
| 103 | if (_vm.Autonomous.PauseAutonomousCommand.CanExecute(null)) |
| 104 | _vm.Autonomous.PauseAutonomousCommand.Execute(null); |
| 105 | return "OK"; |
| 106 | }); |
| 107 | add(Services.IdeCommands.ResumeAutonomous, async (_, _) => |
| 108 | { |
| 109 | if (_vm.Autonomous.ResumeAutonomousCommand.CanExecute(null)) |
| 110 | _vm.Autonomous.ResumeAutonomousCommand.Execute(null); |
| 111 | return "OK"; |
| 112 | }); |
| 113 | |
| 114 | add(Services.IdeCommands.FixFailingTests, async (_, _) => |
| 115 | { |
| 116 | if (_vm.Autonomous.FixFailingTestsCommand.CanExecute(null)) |
| 117 | _vm.Autonomous.FixFailingTestsCommand.Execute(null); |
| 118 | return "OK"; |
| 119 | }); |
| 120 | add(Services.IdeCommands.InvestigateNullref, async (_, _) => |
| 121 | { |
| 122 | if (_vm.Autonomous.InvestigateNullrefCommand.CanExecute(null)) |
| 123 | _vm.Autonomous.InvestigateNullrefCommand.Execute(null); |
| 124 | return "OK"; |
| 125 | }); |
| 126 | add(Services.IdeCommands.PrepareCommit, async (_, _) => |
| 127 | { |
| 128 | if (_vm.Autonomous.PrepareCommitCommand.CanExecute(null)) |
| 129 | _vm.Autonomous.PrepareCommitCommand.Execute(null); |
| 130 | return "OK"; |
| 131 | }); |
| 132 | |
| 133 | add(Services.IdeCommands.SendChat, async (args, ct) => |
| 134 | { |
| 135 | var role = McpCommandJsonArgs.String(args, "role")?.Trim(); |
| 136 | var msg = McpCommandJsonArgs.String(args, "message"); |
| 137 | if (string.IsNullOrWhiteSpace(msg)) |
| 138 | return "Missing message"; |
| 139 | |
| 140 | var useFastAttachPath = IntercomMcpSendChatRoute.ShouldAppendPreparedFeedMessage(role, msg); |
| 141 | if (useFastAttachPath) |
| 142 | { |
| 143 | var feedRole = string.Equals(role, "assistant", StringComparison.OrdinalIgnoreCase) |
| 144 | ? "assistant" |
| 145 | : "user"; |
| 146 | return await _vm.ChatPanel.AppendMessageFromMcpAsync(feedRole, msg!, ct).ConfigureAwait(false); |
| 147 | } |
| 148 | |
| 149 | _vm.ChatPanel.ChatInput = msg!; |
| 150 | if (_vm.ChatPanel.SendChatCommand.CanExecute(null)) |
| 151 | await _vm.ChatPanel.SendChatCommand.ExecuteAsync(null); |
| 152 | return "OK"; |
| 153 | }); |
| 154 | |
| 155 | add(Services.IdeCommands.ForkChatThread, async (args, _) => |
| 156 | { |
| 157 | Guid? parent = null; |
| 158 | var raw = McpCommandJsonArgs.String(args, "parent_message_id")?.Trim(); |
| 159 | if (!string.IsNullOrEmpty(raw) && Guid.TryParse(raw, out var pid)) |
| 160 | parent = pid; |
| 161 | var title = McpCommandJsonArgs.String(args, "display_title")?.Trim(); |
| 162 | if (string.IsNullOrEmpty(title)) |
| 163 | title = McpCommandJsonArgs.String(args, "title")?.Trim(); |
| 164 | return string.IsNullOrEmpty(title) |
| 165 | ? _vm.ChatPanel.ForkThread(parent) |
| 166 | : _vm.ChatPanel.ForkThread(parent, title); |
| 167 | }); |
| 168 | add(Services.IdeCommands.OpenChatClarificationBatch, async (args, _) => |
| 169 | { |
| 170 | var batchJson = McpCommandJsonArgs.String(args, "batch_json"); |
| 171 | return _vm.ChatPanel.OpenClarificationBatchFromJson(batchJson ?? ""); |
| 172 | }); |
| 173 | add(Services.IdeCommands.SubmitChatClarificationResponse, async (args, _) => |
| 174 | { |
| 175 | var responseJson = McpCommandJsonArgs.String(args, "response_json"); |
| 176 | return _vm.ChatPanel.SubmitClarificationResponseFromJson(responseJson ?? ""); |
| 177 | }); |
| 178 | add(Services.IdeCommands.ChatSelectPrevMessage, async (_, _) => |
| 179 | { |
| 180 | return _vm.ChatPanel.SelectMessageByOffset(-1); |
| 181 | }); |
| 182 | add(Services.IdeCommands.ChatSelectNextMessage, async (_, _) => |
| 183 | { |
| 184 | return _vm.ChatPanel.SelectMessageByOffset(+1); |
| 185 | }); |
| 186 | add(Services.IdeCommands.ChatToggleSelectedThinking, async (_, _) => |
| 187 | { |
| 188 | return _vm.ChatPanel.ToggleSelectedThinkingDetails(); |
| 189 | }); |
| 190 | add(Services.IdeCommands.ChatToggleShowThinkingInHistory, async (_, _) => |
| 191 | { |
| 192 | _vm.ShowThinkingInHistory = !_vm.ShowThinkingInHistory; |
| 193 | return _vm.ShowThinkingInHistory ? "ShowThinkingInHistory=on" : "ShowThinkingInHistory=off"; |
| 194 | }); |
| 195 | add(Services.IdeCommands.ChatSelectPrevThread, async (_, _) => |
| 196 | { |
| 197 | return _vm.ChatPanel.NavigateThreadSelection(-1); |
| 198 | }); |
| 199 | add(Services.IdeCommands.ChatSelectNextThread, async (_, _) => |
| 200 | { |
| 201 | return _vm.ChatPanel.NavigateThreadSelection(+1); |
| 202 | }); |
| 203 | add(Services.IdeCommands.ChatOpenSelectedThread, async (_, _) => |
| 204 | { |
| 205 | return _vm.ChatPanel.OpenSelectedThreadDetail(); |
| 206 | }); |
| 207 | add(Services.IdeCommands.ChatShowThreadOverview, async (_, _) => |
| 208 | { |
| 209 | return _vm.ChatPanel.ShowThreadOverview(); |
| 210 | }); |
| 211 | add(Services.IdeCommands.CockpitOpenCommandLine, async (args, _) => |
| 212 | { |
| 213 | var initial = McpCommandJsonArgs.String(args, "initial_text"); |
| 214 | await _vm.OpenCockpitCommandLineOnActiveForwardHostAsync(initial ?? "/"); |
| 215 | return "CockpitCommandLine=open"; |
| 216 | }); |
| 217 | add(Services.IdeCommands.TogglePrimaryWorkSurface, async (_, _) => |
| 218 | { |
| 219 | if (_vm.TogglePrimaryWorkSurfaceCommand.CanExecute(null)) |
| 220 | _vm.TogglePrimaryWorkSurfaceCommand.Execute(null); |
| 221 | return _vm.PrimaryWorkSurface.ToTomlValue(); |
| 222 | }); |
| 223 | add(Services.IdeCommands.SetPrimaryWorkSurface, async (args, _) => |
| 224 | { |
| 225 | var surface = McpCommandJsonArgs.String(args, "surface") ?? "intercom"; |
| 226 | _vm.PrimaryWorkSurface = PrimaryWorkSurfaceKindExtensions.ParseTomlValue(surface); |
| 227 | return _vm.PrimaryWorkSurface.ToTomlValue(); |
| 228 | }); |
| 229 | add(Services.IdeCommands.ChatToggleProductSpineInAgentContext, async (_, _) => |
| 230 | { |
| 231 | return _vm.ChatPanel.ToggleProductSpineInAgentContext(); |
| 232 | }); |
| 233 | add(Services.IdeCommands.ChatGetProductSpine, async (_, _) => |
| 234 | { |
| 235 | return _vm.ChatPanel.GetProductSpineJson(); |
| 236 | }); |
| 237 | add(Services.IdeCommands.ChatSetProductSpine, async (args, _) => |
| 238 | { |
| 239 | return _vm.ChatPanel.SetProductSpineFromMcp(args); |
| 240 | }); |
| 241 | add(Services.IdeCommands.ChatGetSedmScope, async (_, _) => |
| 242 | { |
| 243 | return _vm.ChatPanel.GetSedmScopeJson(); |
| 244 | }); |
| 245 | add(Services.IdeCommands.ChatRecordSedmIntent, async (args, ct) => |
| 246 | { |
| 247 | return await _vm.ChatPanel.RecordIntentCardFromMcpArgsAsync(args, ct).ConfigureAwait(false); |
| 248 | }); |
| 249 | add(Services.IdeCommands.ChatRecordSedmDecision, async (args, ct) => |
| 250 | { |
| 251 | return await _vm.ChatPanel.RecordDecisionFromMcpArgsAsync(args, ct).ConfigureAwait(false); |
| 252 | }); |
| 253 | |
| 254 | add(Services.IdeCommands.InstallOllamaModel, async (args, _) => |
| 255 | { |
| 256 | var model = McpCommandJsonArgs.String(args, "model"); |
| 257 | if (string.IsNullOrWhiteSpace(model)) |
| 258 | return "Missing model"; |
| 259 | var m = model.Trim(); |
| 260 | _vm.ModelToInstall = m; |
| 261 | if (_vm.InstallModelCommand.CanExecute(null)) |
| 262 | await _vm.InstallModelCommand.ExecuteAsync(null); |
| 263 | return "OK"; |
| 264 | }); |
| 265 | } |
| 266 | |
| 267 | } |
| 268 | |