| 1 | using CascadeIDE.Cockpit.DataBus; |
| 2 | using CascadeIDE.ViewModels; |
| 3 | using CascadeIDE.Features.IdeMcp.Application; |
| 4 | using CascadeIDE.Models; |
| 5 | using CascadeIDE.Services; |
| 6 | |
| 7 | namespace CascadeIDE.Features.IdeMcp.Application; |
| 8 | |
| 9 | internal sealed partial class MainWindowIdeMcpHost |
| 10 | { |
| 11 | |
| 12 | /// <summary>Код выхода и успех для финального <see cref="BuildStateChanged"/> после MCP-операции на панели сборки.</summary> |
| 13 | private sealed class IdeMcpMutableBuildPhaseOutcome |
| 14 | { |
| 15 | public int? ExitCode; |
| 16 | public bool? Succeeded; |
| 17 | } |
| 18 | |
| 19 | /// <summary> |
| 20 | /// Пара событий «сборка идёт» / «сборка завершилась» на IDE DataBus (ADR 0099); тело задаёт <see cref="IdeMcpMutableBuildPhaseOutcome"/> до return. |
| 21 | /// </summary> |
| 22 | private async Task<T> WithIdeMcpPublishedBuildStateAsync<T>(Func<IdeMcpMutableBuildPhaseOutcome, Task<T>> body) |
| 23 | { |
| 24 | await _host.PublishIdeBuildStateOnUiAsync(new BuildStateChanged(true)).ConfigureAwait(false); |
| 25 | var outcome = new IdeMcpMutableBuildPhaseOutcome(); |
| 26 | try |
| 27 | { |
| 28 | return await body(outcome).ConfigureAwait(false); |
| 29 | } |
| 30 | finally |
| 31 | { |
| 32 | await _host.PublishIdeBuildStateOnUiAsync(new BuildStateChanged(false, outcome.ExitCode, outcome.Succeeded)) |
| 33 | .ConfigureAwait(false); |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | /// <summary>Диагностики открытого .cs файла (ошибки и предупреждения Roslyn). JSON: массив { id, message, severity, line, column }. Для не-C# или при отсутствии файла — [].</summary> |
| 38 | public async Task<string> GetCurrentFileDiagnosticsAsync() |
| 39 | { |
| 40 | var (path, text) = await UiScheduler.Default.InvokeAsync(() => (_host.CurrentFilePath ?? "", _host.EditorText ?? "")); |
| 41 | return await Task.Run(() => _host.McpContextMinimizer.GetDiagnosticsJson(path, text)).ConfigureAwait(false); |
| 42 | } |
| 43 | |
| 44 | /// <summary>Список файлов и дерево решения. file_entries — плоский список с path, title, relative_path. solution_tree — иерархия (solution → projects → folders → files). Выполняется в UI-потоке.</summary> |
| 45 | public Task<string> GetSolutionFilesAsync() => |
| 46 | UiScheduler.Default.InvokeAsync(() => |
| 47 | IdeMcpBuildTestOrchestrator.BuildSolutionFilesJson(_host.Workspace.SolutionPath, _host.Workspace.SolutionRoots)); |
| 48 | public async Task<string> BuildAsync() |
| 49 | { |
| 50 | var path = await UiScheduler.Default.InvokeAsync(() => _host.Workspace.SolutionPath ?? ""); |
| 51 | if (!IdeMcpSolutionPathAvailability.IsRunnableSolutionFile(path)) |
| 52 | { |
| 53 | var surf = IdeMcpBuildTestOrchestrator.BuildMissingSolutionPanelSurface(); |
| 54 | UiScheduler.Default.Post(() => |
| 55 | { |
| 56 | _host.BuildOutputPanel.Set(surf.BuildOutputPanelFullText); |
| 57 | _host.IsBuildOutputVisible = true; |
| 58 | }); |
| 59 | return surf.McpReplyText; |
| 60 | } |
| 61 | var pathCopy = path; |
| 62 | await UiScheduler.Default.InvokeAsync(() => |
| 63 | { |
| 64 | _host.BuildOutputPanel.Set(IdeMcpBuildTestOrchestrator.BuildOperationHeader("РЎР±РѕСЂРєР°", pathCopy)); |
| 65 | _host.IsBuildOutputVisible = true; |
| 66 | }).ConfigureAwait(false); |
| 67 | |
| 68 | return await WithIdeMcpPublishedBuildStateAsync(async outcome => |
| 69 | { |
| 70 | try |
| 71 | { |
| 72 | void AppendBuildChunk(string chunk) => _host.BuildOutputPanel.Append(chunk); |
| 73 | var (outStr, success, exitCode, binlogPath) = await _host.McpBuildTest |
| 74 | .BuildWithBinlogAsync(path, AppendBuildChunk, cancellationToken: default) |
| 75 | .ConfigureAwait(false); |
| 76 | outcome.ExitCode = exitCode; |
| 77 | outcome.Succeeded = success; |
| 78 | |
| 79 | await UiScheduler.Default.InvokeAsync(() => |
| 80 | { |
| 81 | _host.BuildOutputPanel.FlushPending(); |
| 82 | _host.McpLastBuildBinlogPath = binlogPath; |
| 83 | }).ConfigureAwait(false); |
| 84 | return outStr; |
| 85 | } |
| 86 | catch (Exception ex) |
| 87 | { |
| 88 | outcome.Succeeded = false; |
| 89 | var surf = IdeMcpBuildTestOrchestrator.FailedBuildPanelSurface(ex.Message); |
| 90 | UiScheduler.Default.Post(() => |
| 91 | { |
| 92 | _host.BuildOutputPanel.Set(surf.BuildOutputPanelFullText); |
| 93 | _host.IsBuildOutputVisible = true; |
| 94 | }); |
| 95 | return surf.McpReplyText; |
| 96 | } |
| 97 | }).ConfigureAwait(false); |
| 98 | } |
| 99 | public async Task<string> BuildStructuredAsync() |
| 100 | { |
| 101 | var raw = await this.BuildAsync().ConfigureAwait(false); |
| 102 | return Services.McpDotnetBuildTestService.SerializeStructuredBuild(raw, _host.McpLastBuildBinlogPath); |
| 103 | } |
| 104 | public async Task<string> RunCodeCleanupAsync(string? includePath) |
| 105 | { |
| 106 | var path = await UiScheduler.Default.InvokeAsync(() => _host.Workspace.SolutionPath ?? ""); |
| 107 | if (!IdeMcpSolutionPathAvailability.IsRunnableSolutionFile(path)) |
| 108 | return IdeMcpBuildTestOrchestrator.SerializeCodeCleanupFailure(IdeMcpBuildTestOrchestrator.MissingSolutionMessage()); |
| 109 | |
| 110 | var pathCopy = path; |
| 111 | await UiScheduler.Default.InvokeAsync(() => |
| 112 | { |
| 113 | _host.BuildOutputPanel.Set(IdeMcpBuildTestOrchestrator.BuildOperationHeader("Code cleanup", pathCopy)); |
| 114 | _host.IsBuildOutputVisible = true; |
| 115 | }).ConfigureAwait(false); |
| 116 | |
| 117 | return await WithIdeMcpPublishedBuildStateAsync(async outcome => |
| 118 | { |
| 119 | try |
| 120 | { |
| 121 | void AppendBuildChunk(string chunk) => _host.BuildOutputPanel.Append(chunk); |
| 122 | var (success, exitCode, outStr) = await _host.McpBuildTest |
| 123 | .RunCodeCleanupAsync(path, includePath, AppendBuildChunk, cancellationToken: default) |
| 124 | .ConfigureAwait(false); |
| 125 | outcome.ExitCode = exitCode; |
| 126 | outcome.Succeeded = success; |
| 127 | var rawTruncated = IdeMcpBuildTestOrchestrator.BuildTruncatedRawOutput(outStr, 4000); |
| 128 | |
| 129 | await UiScheduler.Default.InvokeAsync(() => _host.BuildOutputPanel.FlushPending()).ConfigureAwait(false); |
| 130 | |
| 131 | return IdeMcpBuildTestOrchestrator.SerializeCodeCleanupResult(success, exitCode, rawTruncated); |
| 132 | } |
| 133 | catch (Exception ex) |
| 134 | { |
| 135 | outcome.Succeeded = false; |
| 136 | return IdeMcpBuildTestOrchestrator.SerializeCodeCleanupFailure(ex.Message); |
| 137 | } |
| 138 | }).ConfigureAwait(false); |
| 139 | } |
| 140 | |
| 141 | } |
| 142 | |