| 1 | using System.ComponentModel; |
| 2 | using System.Text.Json; |
| 3 | using Avalonia.Threading; |
| 4 | using CascadeIDE.Features.AutonomousAgent; |
| 5 | using CascadeIDE.Features.Build; |
| 6 | using CascadeIDE.Features.Chat; |
| 7 | using CascadeIDE.Features.Debug; |
| 8 | using CascadeIDE.Features.Documents; |
| 9 | using CascadeIDE.Features.Git; |
| 10 | using CascadeIDE.Features.Instrumentation; |
| 11 | using CascadeIDE.Features.Terminal; |
| 12 | using CascadeIDE.Cockpit.Channels.Eicas; |
| 13 | using CascadeIDE.Cockpit.Channels.EnvironmentReadiness; |
| 14 | using CascadeIDE.Cockpit.Channels.WorkspaceHealth; |
| 15 | using CascadeIDE.Cockpit.ComputingUnits.IdeHealth; |
| 16 | using CascadeIDE.Cockpit.DataBus; |
| 17 | using CascadeIDE.Contracts; |
| 18 | using CascadeIDE.Cockpit.Composition.EnvironmentReadiness; |
| 19 | using CascadeIDE.Cockpit.Composition.WorkspaceHealth; |
| 20 | using CascadeIDE.Cockpit.Composition.HostSurface; |
| 21 | using CascadeIDE.Features.UiChrome; |
| 22 | using CascadeIDE.Features.HybridIndex.Application; |
| 23 | using CascadeIDE.Features.Shell.Application; |
| 24 | using CascadeIDE.Models; |
| 25 | using CascadeIDE.Features.Os.DataAcquisition; |
| 26 | using CascadeIDE.Features.IdeMcp.Execution; |
| 27 | using CascadeIDE.Features.WorkspaceNavigation.Application; |
| 28 | using CascadeIDE.Features.Workspace; |
| 29 | using CascadeIDE.Features.Workspace.Application; |
| 30 | |
| 31 | namespace CascadeIDE.ViewModels; |
| 32 | |
| 33 | /// <summary> |
| 34 | /// Главный композитор окна (partial-класс, несколько <c>MainWindowViewModel*.cs</c>). |
| 35 | /// Карта файлов и ответственности — <c>docs/architecture-migration.md</c>, раздел «Срез MainWindowViewModel». |
| 36 | /// </summary> |
| 37 | [DataBusPublisher("ide-health & related domain signals")] |
| 38 | public partial class MainWindowViewModel : ViewModelBase, IAutonomousAgentSessionHost, |
| 39 | IMainWindowHostSurfaceInput, SolutionLoadSessionApplyProjection.IHost, |
| 40 | IMainWindowMcpHostContext, IWorkspaceNavigationMapHost |
| 41 | { |
| 42 | public const string InstallNewSentinel = "— Установить модель… —"; |
| 43 | |
| 44 | private readonly Services.IOllamaService _ollama = new Services.OllamaService(); |
| 45 | private readonly Services.AiProviderManager _aiProviderManager; |
| 46 | private readonly CascadeIdeSettings _settings = Services.SettingsService.Load(); |
| 47 | internal CascadeIdeSettings GetCascadeSettingsForExecutor() => _settings; |
| 48 | private AiKeys _aiKeys = Services.AiKeysStorage.Load(); |
| 49 | private CascadeIdeSettings? _lastSavedSettings; |
| 50 | private AiKeys? _lastSavedAiKeys; |
| 51 | |
| 52 | /// <summary>ADR 0083: значения <c>ai.mode</c> в TOML.</summary> |
| 53 | public static readonly IReadOnlyList<string> AiModeOptions = ["local", "acp", "mcp_only", "cloud"]; |
| 54 | |
| 55 | public IReadOnlyList<string> AiModeOptionsList => AiModeOptions; |
| 56 | |
| 57 | /// <summary>Для <c>mode = cloud</c>: <c>ai.cloud.active_provider</c>.</summary> |
| 58 | public static readonly IReadOnlyList<string> AiCloudProviderKeys = ["anthropic", "openai", "deepseek"]; |
| 59 | |
| 60 | public IReadOnlyList<string> AiCloudProviderKeysList => AiCloudProviderKeys; |
| 61 | |
| 62 | private readonly Services.CSharpLanguageService _csharpLanguageService; |
| 63 | private readonly Services.ContextMinimizer _contextMinimizer; |
| 64 | private readonly Services.WorkspaceDiagnosticsCoordinator _workspaceDiagnostics; |
| 65 | private readonly Services.Capabilities.SimpleCapabilityRegistry _capabilities = new(); |
| 66 | private CSharpLspDiagnosticsHost? _csharpLspHost; |
| 67 | private MarkdownLspDiagnosticsHost? _markdownLspHost; |
| 68 | private readonly Features.IdeMcp.Application.MainWindowIdeMcpHost _ideMcpHost; |
| 69 | private readonly Services.IdeDapDebugSession _dapDebug; |
| 70 | private readonly IDataBus _ideDataBus; |
| 71 | private readonly DotNetBuildTest.Core.BuildTestJobService _buildTestJobService; |
| 72 | private readonly Features.Agent.Environment.IAgentEnvironmentService _agentEnvironment; |
| 73 | private readonly HybridIndexOrchestrator _hybridIndex; |
| 74 | private readonly IIdeHealthChannel _workspaceHealth; |
| 75 | private readonly IIdeHealthSurfaceCompositor _workspaceHealthSurfaceCompositor; |
| 76 | private readonly IEicasFeed _eicasFeed; |
| 77 | private readonly IEnvironmentReadinessChannel _environmentReadinessChannel; |
| 78 | private readonly IEnvironmentReadinessSurfaceCompositor _environmentReadinessSurfaceCompositor; |
| 79 | private readonly Services.Presentation.PresentationParseResult _presentationParse; |
| 80 | private readonly bool _presentationDedicatedMfdSecondScreen; |
| 81 | private readonly bool _presentationTripleOneAnchorPerZone; |
| 82 | private readonly bool _presentationMfdHostTopology; |
| 83 | private readonly bool _presentationPmForwardTwoScreen; |
| 84 | private readonly bool _presentationPmHostTopology; |
| 85 | private readonly IInstrumentMountPolicyResolver _instrumentMountPolicyResolver; |
| 86 | private bool _suppressMfdColumnForMfdHostWindow; |
| 87 | private bool _suppressPfdColumnForPfdHostWindow; |
| 88 | |
| 89 | private Services.McpClientService _mcpClientService; |
| 90 | private AutonomousAgentService _autonomousAgentService; |
| 91 | |
| 92 | private readonly IOsShellLauncher _osShell; |
| 93 | |
| 94 | /// <summary>DAP-сессия (netcoredbg): launch/attach и обновление панели отладки.</summary> |
| 95 | public Services.IdeDapDebugSession DapDebug => _dapDebug; |
| 96 | |
| 97 | /// <summary>Solution/workspace state and background loading.</summary> |
| 98 | public SolutionWorkspaceViewModel Workspace { get; } |
| 99 | |
| 100 | /// <summary>Git-строки для Workspace Health и bloom при смене UI-режима.</summary> |
| 101 | public UiChromeViewModel Chrome { get; } |
| 102 | |
| 103 | /// <summary>Открытые файлы, группы редакторов и Dock.</summary> |
| 104 | public DocumentsWorkspaceViewModel Documents { get; } |
| 105 | |
| 106 | /// <summary>Автономный агент (Power): цель, шаги, start/pause/resume.</summary> |
| 107 | public AutonomousAgentSessionViewModel Autonomous { get; } |
| 108 | |
| 109 | private void OnDocumentsPropertyChanged(object? _, PropertyChangedEventArgs e) |
| 110 | { |
| 111 | if (e.PropertyName is null) |
| 112 | return; |
| 113 | OnPropertyChanged(e.PropertyName); |
| 114 | if (e.PropertyName == nameof(DocumentsWorkspaceViewModel.SelectedDocumentGroup2)) |
| 115 | OnPropertyChanged(nameof(EditorTextGroup2)); |
| 116 | if (e.PropertyName == nameof(DocumentsWorkspaceViewModel.SelectedDocumentGroup3)) |
| 117 | OnPropertyChanged(nameof(EditorTextGroup3)); |
| 118 | } |
| 119 | |
| 120 | private void OnWorkspacePropertyChanged(string? propertyName) |
| 121 | { |
| 122 | switch (propertyName) |
| 123 | { |
| 124 | case nameof(SolutionWorkspaceViewModel.SolutionPath): |
| 125 | OnPropertyChanged(nameof(BreakpointLinesInCurrentFile)); |
| 126 | OnPropertyChanged(nameof(AllBreakpointLinesInCurrentFile)); |
| 127 | BuildSolutionCommand.NotifyCanExecuteChanged(); |
| 128 | ImportLaunchSettingsFromSelectionCommand.NotifyCanExecuteChanged(); |
| 129 | HandleSolutionPathChanged(Workspace.SolutionPath); |
| 130 | break; |
| 131 | case nameof(SolutionWorkspaceViewModel.SelectedSolutionItem): |
| 132 | HandleSelectedSolutionItemChanged(Workspace.SelectedSolutionItem); |
| 133 | SetStartupProjectFromSelectionCommand.NotifyCanExecuteChanged(); |
| 134 | ImportLaunchSettingsFromSelectionCommand.NotifyCanExecuteChanged(); |
| 135 | break; |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | /// <summary>Событие: перейти в активном редакторе на строку/колонку (1-based) после открытия файла.</summary> |
| 140 | public event Action<int, int>? GotoActiveEditorLineColumnRequested; |
| 141 | |
| 142 | private void NavigateToProblemFromList(ProblemListItem item) |
| 143 | { |
| 144 | if (string.IsNullOrWhiteSpace(item.FilePath)) |
| 145 | return; |
| 146 | Documents.OpenOrActivateDocument(item.FilePath); |
| 147 | var line = item.Line; |
| 148 | var col = item.Column; |
| 149 | UiScheduler.Default.Post(() => GotoActiveEditorLineColumnRequested?.Invoke(line, col), DispatcherPriority.Loaded); |
| 150 | } |
| 151 | |
| 152 | private AutonomousAgentService CreateAutonomousAgentService(Services.McpClientService mcpClientService) => |
| 153 | new AutonomousAgentService( |
| 154 | _aiProviderManager, |
| 155 | IdeMcp, |
| 156 | mcpClientService, |
| 157 | () => ActiveAiProvider, |
| 158 | () => SelectedOllamaModel, |
| 159 | () => UseMinimizedContext, |
| 160 | () => CurrentFilePath, |
| 161 | () => EditorText, |
| 162 | (kind, text, status, at) => InstrumentationPanel.AppendAgentTraceStep(kind, text, status, at), |
| 163 | msg => UiScheduler.Default.Post(() => InstrumentationPanel.EventTimeline.Insert(0, msg)), |
| 164 | _agentEnvironment.Orchestrator); |
| 165 | |
| 166 | /// <summary> |
| 167 | /// Полоса Workspace Health читает счётчики отладки с главного VM; при смене MCP-стека обновляем строки. |
| 168 | /// </summary> |
| 169 | private void OnInstrumentationPanelPropertyChanged(object? sender, PropertyChangedEventArgs e) |
| 170 | { |
| 171 | if (e.PropertyName != nameof(InstrumentationPanelViewModel.IsDebugPanelVisible)) |
| 172 | return; |
| 173 | OnPropertyChanged(nameof(IdeHealthDebugText)); |
| 174 | OnPropertyChanged(nameof(IdeHealthDebugCockpitShort)); |
| 175 | } |
| 176 | |
| 177 | /// <summary>Вывод сборки (нижняя вкладка Build output).</summary> |
| 178 | public BuildOutputPanelViewModel BuildOutputPanel { get; } |
| 179 | |
| 180 | /// <summary>Ошибки и предупреждения по открытым .cs (Roslyn).</summary> |
| 181 | public ProblemsPanelViewModel ProblemsPanel { get; } |
| 182 | |
| 183 | /// <summary>Markdown preview как shared state для MFD page/tool.</summary> |
| 184 | public MarkdownPreviewToolViewModel MarkdownPreviewTool { get; } |
| 185 | |
| 186 | /// <summary>Терминал (нижняя вкладка Terminal).</summary> |
| 187 | public TerminalPanelViewModel TerminalPanel { get; } |
| 188 | |
| 189 | /// <summary>Панель Git (нижняя вкладка); состояние и команды вынесены из <see cref="MainWindowViewModel"/>.</summary> |
| 190 | public GitPanelViewModel GitPanel { get; } |
| 191 | |
| 192 | /// <summary>Чат с LLM (правая колонка): история, ввод, отправка.</summary> |
| 193 | public ChatPanelViewModel ChatPanel { get; } |
| 194 | |
| 195 | /// <summary>Инструментирование: трасса агента, события, тесты, стек MCP-отладки. В разметке — <c>DataContext="{Binding InstrumentationPanel}"</c>.</summary> |
| 196 | public InstrumentationPanelViewModel InstrumentationPanel { get; } |
| 197 | |
| 198 | /// <summary>Гипотезы отладки (JSON в workspace). Вкладка нижней панели в режиме Debug.</summary> |
| 199 | public HypothesesPanelViewModel HypothesesPanel { get; } |
| 200 | |
| 201 | /// <summary>Общий экземпляр Roslyn для редактора, контекста чата и диагностик.</summary> |
| 202 | public Services.CSharpLanguageService CSharpLanguage => _csharpLanguageService; |
| 203 | |
| 204 | /// <summary>Кэш диагностик по открытым .cs и панель Problems.</summary> |
| 205 | public Services.WorkspaceDiagnosticsCoordinator WorkspaceDiagnostics => _workspaceDiagnostics; |
| 206 | |
| 207 | private void SaveSettingsIfChanged() |
| 208 | { |
| 209 | if (_lastSavedSettings is null || !_settings.Is(_lastSavedSettings)) |
| 210 | { |
| 211 | Services.SettingsService.Save(_settings); |
| 212 | _lastSavedSettings = (CascadeIdeSettings)_settings.Clone(); |
| 213 | } |
| 214 | } |
| 215 | |
| 216 | private void SaveAiKeysIfChanged() |
| 217 | { |
| 218 | if (_lastSavedAiKeys is null || !_aiKeys.Is(_lastSavedAiKeys)) |
| 219 | { |
| 220 | Services.AiKeysStorage.Save(_aiKeys); |
| 221 | _lastSavedAiKeys = (AiKeys)_aiKeys.Clone(); |
| 222 | } |
| 223 | } |
| 224 | |
| 225 | private Microsoft.Extensions.AI.IChatClient? TryCreateCloudMafIChatClientForChatPanel() |
| 226 | { |
| 227 | return ActiveAiProvider switch |
| 228 | { |
| 229 | "Anthropic" => Services.CascadeIdeMafChatClientFactories.CreateAnthropicChatClientOrNull( |
| 230 | AnthropicApiKey, |
| 231 | _settings.Ai.Cloud.Anthropic.Model), |
| 232 | "OpenAI" => Services.CascadeIdeMafChatClientFactories.CreateOpenAiCompatibleChatClientOrNull( |
| 233 | OpenAiApiKey, |
| 234 | _settings.Ai.Cloud.OpenAi.BaseUrl, |
| 235 | _settings.Ai.Cloud.OpenAi.Model), |
| 236 | "DeepSeek" => Services.CascadeIdeMafChatClientFactories.CreateOpenAiCompatibleChatClientOrNull( |
| 237 | DeepSeekApiKey, |
| 238 | _settings.Ai.Cloud.DeepSeek.BaseUrl, |
| 239 | _settings.Ai.Cloud.DeepSeek.Model), |
| 240 | _ => null, |
| 241 | }; |
| 242 | } |
| 243 | |
| 244 | private (Services.IAiChatProvider? Provider, string Model) ResolveProvider(string providerKey) |
| 245 | { |
| 246 | var key = providerKey ?? _settings.Ai.ResolveEffectiveProviderUiKey(); |
| 247 | return key switch |
| 248 | { |
| 249 | "Anthropic" => (new Services.AnthropicProvider(_aiKeys.AnthropicApiKey ?? "", _settings.Ai.Cloud.Anthropic.Model), _settings.Ai.Cloud.Anthropic.Model), |
| 250 | "OpenAI" => (new Services.OpenAiCompatibleProvider(_settings.Ai.Cloud.OpenAi.BaseUrl, _aiKeys.OpenAiApiKey ?? "", _settings.Ai.Cloud.OpenAi.Model), _settings.Ai.Cloud.OpenAi.Model), |
| 251 | "DeepSeek" => (new Services.OpenAiCompatibleProvider(_settings.Ai.Cloud.DeepSeek.BaseUrl, _aiKeys.DeepSeekApiKey ?? "", _settings.Ai.Cloud.DeepSeek.Model), _settings.Ai.Cloud.DeepSeek.Model), |
| 252 | "CursorACP" => (null, ""), |
| 253 | _ => (new Services.OllamaProvider(_ollama), SelectedOllamaModel ?? _settings.Ai.Local.Ollama.Model) |
| 254 | }; |
| 255 | } |
| 256 | |
| 257 | private Features.Chat.FmOpenAiCompatibleCredentials? ResolveFmOpenAiCredentialsForCatalog() => |
| 258 | ActiveAiProvider switch |
| 259 | { |
| 260 | "OpenAI" when !string.IsNullOrWhiteSpace(OpenAiApiKey) => new( |
| 261 | _settings.Ai.Cloud.OpenAi.BaseUrl, |
| 262 | OpenAiApiKey.Trim(), |
| 263 | _settings.Ai.Cloud.OpenAi.Model), |
| 264 | "DeepSeek" when !string.IsNullOrWhiteSpace(DeepSeekApiKey) => new( |
| 265 | _settings.Ai.Cloud.DeepSeek.BaseUrl, |
| 266 | DeepSeekApiKey.Trim(), |
| 267 | _settings.Ai.Cloud.DeepSeek.Model), |
| 268 | _ => null, |
| 269 | }; |
| 270 | |
| 271 | private readonly Services.AppDataService _appData = new(); |
| 272 | private readonly IGitCommandRunner _gitRunner = new GitCommandRunner(); |
| 273 | private readonly IDotnetCommandRunner _dotnetRunner = new DotnetCommandRunner(); |
| 274 | private readonly Services.McpDotnetBuildTestService _mcpBuildTest; |
| 275 | private readonly Services.McpAgentNotesService _mcpAgentNotes; |
| 276 | |
| 277 | private CancellationTokenSource? _openFileDebounceCts; |
| 278 | // Solution load version is owned by Workspace. |
| 279 | private string? _lastBuildBinlogPath; |
| 280 | |
| 281 | /// <summary>Краткий список языков с подсветкой в редакторе (для окна настроек).</summary> |
| 282 | public string SupportedEditorLanguagesSummary => Services.EditorLanguageSupport.GetSummary(); |
| 283 | |
| 284 | public void LoadSendMessageKeyFromStorage() |
| 285 | { |
| 286 | var stored = _appData.Get("SendMessageKey"); |
| 287 | if (!string.IsNullOrEmpty(stored) && SendMessageKeyOptions.Contains(stored)) |
| 288 | SendMessageKey = stored; |
| 289 | } |
| 290 | |
| 291 | public void LoadComposerNewLineKeyFromStorage() |
| 292 | { |
| 293 | var stored = _appData.Get("ComposerNewLineKey"); |
| 294 | if (!string.IsNullOrEmpty(stored) && SendMessageKeyOptions.Contains(stored)) |
| 295 | ComposerNewLineKey = stored; |
| 296 | else |
| 297 | ComposerNewLineKey = Features.Chat.ChatComposerChordOptions.ComplementaryChord(SendMessageKey); |
| 298 | |
| 299 | NormalizeChatEnterChordPair(); |
| 300 | } |
| 301 | |
| 302 | private bool _suppressChatEnterChordPair; |
| 303 | |
| 304 | /// <summary>Совпадение send/newline запрещено: сдвигаем перенос строки.</summary> |
| 305 | private void NormalizeChatEnterChordPair() |
| 306 | { |
| 307 | if (_suppressChatEnterChordPair) |
| 308 | return; |
| 309 | if (string.Equals(ComposerNewLineKey, SendMessageKey, StringComparison.Ordinal)) |
| 310 | { |
| 311 | _suppressChatEnterChordPair = true; |
| 312 | try |
| 313 | { |
| 314 | ComposerNewLineKey = Features.Chat.ChatComposerChordOptions.ComplementaryChord(SendMessageKey); |
| 315 | } |
| 316 | finally |
| 317 | { |
| 318 | _suppressChatEnterChordPair = false; |
| 319 | } |
| 320 | } |
| 321 | } |
| 322 | |
| 323 | private void HandleSelectedSolutionItemChanged(SolutionItem? value) |
| 324 | { |
| 325 | _openFileDebounceCts?.Cancel(); |
| 326 | _openFileDebounceCts = new CancellationTokenSource(); |
| 327 | var cts = _openFileDebounceCts; |
| 328 | _ = Documents.OpenFileAfterDebounceAsync(cts.Token); |
| 329 | } |
| 330 | |
| 331 | private void HandleSolutionPathChanged(string value) |
| 332 | { |
| 333 | if (string.IsNullOrWhiteSpace(value)) |
| 334 | { |
| 335 | ClearStartupProjectInMemoryOnly(); |
| 336 | RefreshLaunchProfilePickerFromStore(); |
| 337 | } |
| 338 | |
| 339 | var ws = WorkspaceDirectoryFromSolutionPath.Resolve(value); |
| 340 | UiModeCatalog.ApplyRepositoryWorkspaceTomlOverlay(ws); |
| 341 | NotifyDockedInstrumentSlotBindings(); |
| 342 | OnPropertyChanged(nameof(ChatPanelColumnPixelWidth)); |
| 343 | OnPropertyChanged(nameof(IsChatPanelColumnVisible)); |
| 344 | OnPropertyChanged(nameof(IsMfdColumnVisible)); |
| 345 | OnPropertyChanged(nameof(IsPfdRegionExpanded)); |
| 346 | OnPropertyChanged(nameof(IsPfdColumnVisible)); |
| 347 | |
| 348 | ChatPanel.DisposeCursorAcpSession(); |
| 349 | _ = ChatPanel.ReloadIntercomSessionFromDiskAsync(); |
| 350 | RefreshIntercomOnSolutionOpen(); |
| 351 | EnsurePfdBackgroundStatusSubscription(); |
| 352 | if (string.IsNullOrWhiteSpace(value)) |
| 353 | _hciReindexPending = false; |
| 354 | else |
| 355 | MarkHciReindexPendingForPfdStatus(); |
| 356 | |
| 357 | ApplySolutionWarmupForCurrentSolution(); |
| 358 | ApplyHybridCodebaseIndexOrchestrationForCurrentSolution(pokeWhenAutoReindex: true); |
| 359 | Editor.AttachBreakpointsFileWatcher(value); |
| 360 | _ = RefreshGitSummaryAsync(); |
| 361 | _ = GitPanel.RefreshRepositoryFlagAsync(); |
| 362 | if (IsGitPanelVisible) |
| 363 | _ = GitPanel.RefreshGitPanelAsync(); |
| 364 | _ = RestartCSharpLanguageServerAsync(); |
| 365 | _ = RestartMarkdownLanguageServerAsync(); |
| 366 | HypothesesPanel.LoadFromWorkspace(); |
| 367 | RestoreCompactAuxiliaryPanelAfterSolutionScopeChange(value); |
| 368 | } |
| 369 | |
| 370 | private void RestoreCompactAuxiliaryPanelAfterSolutionScopeChange(string? solutionPath) |
| 371 | { |
| 372 | if (!IsCompactPresentationTier || string.IsNullOrWhiteSpace(solutionPath)) |
| 373 | return; |
| 374 | |
| 375 | ApplyPfdRegionExpanded(false); |
| 376 | if (!IsMfdRegionExpanded) |
| 377 | ApplyMfdRegionExpanded(true); |
| 378 | |
| 379 | TryNavigateToMfdShellPage(MfdShellPage.Chat); |
| 380 | } |
| 381 | |
| 382 | private (string WorkspaceRoot, string? SolutionPath) ResolveHybridIndexScope(string workspaceRoot, string? solutionPath) => |
| 383 | HybridIndexScopeResolver.ApplyScopeMode(_settings.HybridIndex.ScopeMode, workspaceRoot, solutionPath); |
| 384 | |
| 385 | /// <summary> |
| 386 | /// ADR 0106: синхронизация оркестратора HCI с <see cref="CascadeIdeSettings.HybridIndex"/> и текущим решением в <see cref="SolutionWorkspaceViewModel"/>. |
| 387 | /// </summary> |
| 388 | private void ApplyHybridCodebaseIndexOrchestrationForCurrentSolution(bool pokeWhenAutoReindex) |
| 389 | { |
| 390 | var value = Workspace.SolutionPath ?? ""; |
| 391 | var ws = WorkspaceDirectoryFromSolutionPath.Resolve(value); |
| 392 | if (string.IsNullOrWhiteSpace(ws)) |
| 393 | return; |
| 394 | |
| 395 | HybridIndexOrchestrationPolicy.ApplyForCurrentScope( |
| 396 | _hybridIndex, |
| 397 | _settings.HybridIndex, |
| 398 | ChatMcpOnly, |
| 399 | ws, |
| 400 | value, |
| 401 | pokeWhenAutoReindex); |
| 402 | } |
| 403 | |
| 404 | private string? BuildChatMinimizedContextBlockCore() |
| 405 | { |
| 406 | string? block = null; |
| 407 | if (UseMinimizedContext |
| 408 | && !string.IsNullOrEmpty(CurrentFilePath) |
| 409 | && !string.IsNullOrEmpty(EditorText)) |
| 410 | { |
| 411 | var minimized = _contextMinimizer.Minimize(CurrentFilePath, EditorText, CancellationToken.None); |
| 412 | if (!string.IsNullOrWhiteSpace(minimized)) |
| 413 | block = minimized; |
| 414 | } |
| 415 | |
| 416 | var hot = ChatPanel.Harness.HotContextBlock; |
| 417 | var telemetry = ChatPanel.Harness.BuildTelemetryContextBlock( |
| 418 | ChatPanel.Harness.GetTelemetry(), |
| 419 | _agentEnvironment.EpochTracker.IsUiStale); |
| 420 | |
| 421 | string? prefix = null; |
| 422 | if (!string.IsNullOrWhiteSpace(hot)) |
| 423 | prefix = hot; |
| 424 | if (!string.IsNullOrWhiteSpace(telemetry)) |
| 425 | prefix = string.IsNullOrWhiteSpace(prefix) ? telemetry : prefix + "\n\n---\n\n" + telemetry; |
| 426 | |
| 427 | if (string.IsNullOrWhiteSpace(prefix)) |
| 428 | return block; |
| 429 | |
| 430 | return string.IsNullOrWhiteSpace(block) |
| 431 | ? prefix |
| 432 | : prefix + "\n\n---\n\n" + block; |
| 433 | } |
| 434 | |
| 435 | /// <summary>MCP / палитра / MAF — единая поверхность команд IDE (Wave 2 Big Bang).</summary> |
| 436 | public Services.IIdeMcpActions IdeMcp => _ideMcpHost; |
| 437 | |
| 438 | } |
| 439 | |