Forge
csharp4405de34
1using System.Text.Json;
2using Avalonia;
3using Avalonia.Headless;
4using Avalonia.Themes.Fluent;
5using Avalonia.Threading;
6using CascadeIDE.ViewModels;
7
8namespace CascadeIDE.Services.AgentContract;
9
10/// <summary>
11/// Headless Avalonia + <see cref="MainWindowViewModel"/> для <see cref="AgentContractRunner"/> (ADR 0052).
12/// Без этого <see cref="IUiScheduler.InvokeAsync"/> не завершается.
13/// </summary>
14internal static class AgentContractHeadlessRuntime
15{
16 private static readonly object Gate = new();
17 private static bool _initialized;
18
19 /// <summary>Короткая сводка решения — тот же JSON, что MCP <c>ide_get_solution_info</c> / <see cref="IIdeMcpActions.GetSolutionInfo"/>.</summary>
20 public static string GetSolutionInfoJson()
21 {
22 EnsureInitialized();
23 return Dispatcher.UIThread.Invoke(() =>
24 {
25 var vm = new MainWindowViewModel();
26 return vm.IdeMcp.GetSolutionInfo();
27 });
28 }
29
30 /// <summary>CDS JSON — тот же объект, что <c>cockpit_surface</c> в <see cref="IIdeMcpActions.GetIdeStateAsync"/>.</summary>
31 public static string GetCockpitSurfaceJson()
32 {
33 EnsureInitialized();
34 return Dispatcher.UIThread.Invoke(() =>
35 {
36 var vm = new MainWindowViewModel();
37 return JsonSerializer.Serialize(vm.BuildCockpitSurfaceSnapshot());
38 });
39 }
40
41 /// <summary>Полная сводка — тот же JSON, что MCP <c>ide_get_ide_state</c>.</summary>
42 /// <remarks>
43 /// Внутри — вложенные <c>UiScheduler.InvokeAsync</c>; без прокрутки очереди (<see cref="Dispatcher.RunJobs"/>) задача не завершится.
44 /// </remarks>
45 public static string GetIdeStateJson()
46 {
47 EnsureInitialized();
48 return Dispatcher.UIThread.Invoke(() =>
49 {
50 var vm = new MainWindowViewModel();
51 IIdeMcpActions mcp = vm.IdeMcp;
52 var task = mcp.GetIdeStateAsync();
53 for (var n = 0; !task.IsCompleted; n++)
54 {
55 if (n > 500_000)
56 throw new InvalidOperationException("get_ide_state: dispatcher did not drain (possible deadlock).");
57 Dispatcher.UIThread.RunJobs();
58 }
59
60 return task.GetAwaiter().GetResult();
61 });
62 }
63
64 private static void EnsureInitialized()
65 {
66 lock (Gate)
67 {
68 if (_initialized)
69 return;
70
71 // Уже поднято (например Avalonia.Headless.XUnit в тестах) — не создаём второй Application.
72 if (Application.Current is not null)
73 {
74 _initialized = true;
75 return;
76 }
77
78 AppBuilder.Configure<AgentContractHeadlessApplication>()
79 .UseHeadless(new AvaloniaHeadlessPlatformOptions())
80 .SetupWithoutStarting();
81 _initialized = true;
82 }
83 }
84
85 private sealed class AgentContractHeadlessApplication : Application
86 {
87 public override void Initialize()
88 {
89 Styles.Add(new FluentTheme());
90 }
91 }
92}
93
View only · write via MCP/CIDE