Forge
csharpdeeb25a2
1using System.Text.Json;
2using CascadeIDE.Features.Agent.Environment;
3using CascadeIDE.Features.WorkspaceNavigation.Application;
4using CascadeIDE.Services;
5using CascadeIDE.ViewModels;
6
7namespace CascadeIDE.Features.IdeMcp.Execution;
8
9/// <summary>Диспетчер MCP-команд IDE: разбор args и вызов <see cref="IIdeMcpActions"/> / UI-команд главного окна.</summary>
10internal sealed partial class IdeMcpCommandExecutor
11{
12 private readonly IMainWindowMcpHostContext _host;
13 private readonly MainWindowViewModel _vm;
14 private readonly IIdeMcpActions _actions;
15 private readonly Dictionary<string, Handler> _handlers;
16
17 private delegate Task<string> Handler(IReadOnlyDictionary<string, JsonElement>? args, CancellationToken cancellationToken);
18
19 public IdeMcpCommandExecutor(IMainWindowMcpHostContext host, IIdeMcpActions actions)
20 {
21 _host = host;
22 _vm = host.Vm;
23 _actions = actions;
24 _handlers = BuildHandlers();
25 }
26
27 /// <summary>Вход с MCP/агента маршалится на UI в <see cref="MainWindowViewModel"/> до вызова хендлеров; UI-операции выполнять напрямую без вложенного маршалинга.</summary>
28 public async Task<string> ExecuteAsync(string commandId, IReadOnlyDictionary<string, JsonElement>? args, CancellationToken cancellationToken)
29 {
30 var shellBlock = ShellEscapePolicy.TryBlockJson(
31 commandId,
32 _vm.GetCascadeSettingsForExecutor().Agent.Environment.ShellEscapeTier);
33 if (shellBlock is not null)
34 return shellBlock;
35
36 if (_handlers.TryGetValue(commandId, out var handler))
37 return await handler(args, cancellationToken);
38
39 return $"Unknown command: {commandId}";
40
41 }
42
43 private Dictionary<string, Handler> BuildHandlers()
44 {
45 var map = new Dictionary<string, Handler>(StringComparer.Ordinal);
46
47 void Add(string id, Handler h) => map.Add(id, h);
48
49 RegisterCore(Add);
50 RegisterGenerated(Add); // generated .g.cs adds pass-through handlers
51 RegisterEditorAndSolution(Add);
52 RegisterDebuggerBreakpoints(Add);
53 RegisterDapDebug(Add);
54 RegisterPreviewAndConfirmation(Add);
55 RegisterEditorStateAndContent(Add);
56 RegisterEditAndNavigation(Add);
57 RegisterIntercom(Add);
58 RegisterForge(Add);
59 RegisterOutputAndFocus(Add);
60 // NOTE: these are now generated:
61 // - workspace/solution info
62 // - build/tests
63 // - git
64 // - output/build panel (toggle panels); focus_editor is registered above.
65 RegisterUiVisibilityAndModes(Add);
66 RegisterMenuAndToolbarCommands(Add);
67 RegisterFocusPowerAndAgentActions(Add);
68 RegisterDocuments(Add);
69 RegisterCorrespondence(Add);
70 RegisterCasa(Add);
71 // NOTE: UI inspection/control (pure IIdeMcpActions) is generated.
72 RegisterDebugUiSurface(Add);
73 RegisterAgentNotes(Add);
74 RegisterAgentEnvironment(Add);
75
76 return map;
77 }
78
79 // Generation hook: a source generator (or ProtocolDocGen) can emit a partial method
80 // that registers 1:1 handlers for IIdeMcpActions methods.
81 partial void RegisterGenerated(Action<string, Handler> add);
82
83 private void RegisterCorrespondence(Action<string, Handler> add)
84 {
85 add(Services.IdeCommands.OpenWorkspaceAdrCorrespondence, async (_, _) =>
86 {
87 if (_vm.NavigationMap.OpenWorkspaceAdrCorrespondenceCommand.CanExecute(null))
88 _vm.NavigationMap.OpenWorkspaceAdrCorrespondenceCommand.Execute(null);
89 return "OK";
90 });
91
92 add(Services.IdeCommands.OpenWorkspaceFeatureDocs, async (_, _) =>
93 {
94 if (_vm.NavigationMap.OpenWorkspaceFeatureDocsCommand.CanExecute(null))
95 await _vm.NavigationMap.OpenWorkspaceFeatureDocsCommand.ExecuteAsync(null);
96 return "OK";
97 });
98
99 add(Services.IdeCommands.OpenDocsTemplate, async (args, _) =>
100 {
101 var p = McpCommandJsonArgs.String(args, "path");
102 if (_vm.NavigationMap.OpenDocsTemplateCommand.CanExecute(p))
103 await _vm.NavigationMap.OpenDocsTemplateCommand.ExecuteAsync(p);
104 return "OK";
105 });
106
107 add(Services.IdeCommands.GetCorrespondenceContext, async (args, _) =>
108 {
109 var file = McpCommandJsonArgs.String(args, "file_path")?.Trim();
110 if (string.IsNullOrEmpty(file))
111 file = _vm.CurrentFilePath;
112 if (string.IsNullOrWhiteSpace(file))
113 return WorkspaceCorrespondenceContextBuilder.BuildJson(_vm.GetWorkspacePath(), null);
114
115 var ws = _vm.GetWorkspacePath();
116 if (string.IsNullOrWhiteSpace(ws))
117 return WorkspaceCorrespondenceContextBuilder.BuildJson(null, file);
118
119 var abs = Path.IsPathRooted(file)
120 ? file
121 : Path.Combine(ws, file.Replace('/', Path.DirectorySeparatorChar));
122
123 var hasGraph = _vm.NavigationMap.WorkspaceNavigationMapRelatedCount > 0;
124 return WorkspaceCorrespondenceContextBuilder.BuildJson(ws, abs, hasGraph);
125 });
126 }
127}
128
View only · write via MCP/CIDE