Forge
csharpdeeb25a2
1using Avalonia.Threading;
2using CascadeIDE.Features.UiChrome;
3using CommunityToolkit.Mvvm.ComponentModel;
4using CommunityToolkit.Mvvm.Input;
5
6namespace CascadeIDE.Features.AutonomousAgent;
7
8/// <summary>
9/// Состояние и команды автономного агента (Power): цель, шаги, start/pause/resume и быстрые сценарии.
10/// </summary>
11public sealed partial class AutonomousAgentSessionViewModel : ObservableObject
12{
13 private readonly IAutonomousAgentSessionHost _host;
14
15 private AutonomousAgentService _agentService;
16 private CancellationTokenSource? _cts;
17 private Task? _task;
18 private AutonomousRunState? _runState;
19
20 public AutonomousAgentSessionViewModel(AutonomousAgentService agentService, IAutonomousAgentSessionHost host)
21 {
22 _agentService = agentService;
23 _host = host;
24 }
25
26 /// <summary>Автономный цикл разрешён только в семействе Power (кокпит); иначе сценарии уходят в чат.</summary>
27 private bool AutonomousCockpitActive => _host.UiModeFamily.IsPowerFamily();
28
29 private bool HasResumableAutonomousRun =>
30 _runState?.HasResumableSteps == true;
31
32 public bool IsAutonomousPaused =>
33 AutonomousCockpitActive && !IsAutonomousRunning && HasResumableAutonomousRun;
34
35 public void NotifyHostPowerContextChanged() =>
36 OnPropertyChanged(nameof(IsAutonomousPaused));
37
38 public void ReplaceAgentService(AutonomousAgentService agentService) =>
39 _agentService = agentService;
40
41 /// <summary>Отмена из-за смены внешних MCP: только CTS, UI добьёт async.</summary>
42 public void CancelForHostReconfiguration() =>
43 _cts?.Cancel();
44
45 /// <summary>Emergency stop: сбросить сессию и полосу задач.</summary>
46 public void CancelAutonomousRunCompletely()
47 {
48 _cts?.Cancel();
49 IsAutonomousRunning = false;
50 _host.SetActiveTaskStrip("Autonomous Agent", "Paused", 0);
51 _host.ResultSummary = "Autonomous flow paused by operator.";
52 _runState = null;
53 OnPropertyChanged(nameof(IsAutonomousPaused));
54 StartAutonomousCommand.NotifyCanExecuteChanged();
55 ResumeAutonomousCommand.NotifyCanExecuteChanged();
56 }
57
58 private bool CanStartAutonomous() =>
59 AutonomousCockpitActive
60 && !IsAutonomousRunning
61 && !HasResumableAutonomousRun
62 && !string.IsNullOrWhiteSpace(AutonomousObjective)
63 && AutonomousMaxSteps > 0;
64
65 [RelayCommand(CanExecute = nameof(CanStartAutonomous))]
66 private void StartAutonomous() =>
67 StartAutonomousFlow(AutonomousObjective, AutonomousMaxSteps);
68
69 private bool CanPauseAutonomous() => IsAutonomousRunning;
70
71 [RelayCommand(CanExecute = nameof(CanPauseAutonomous))]
72 private void PauseAutonomous()
73 {
74 _cts?.Cancel();
75 IsAutonomousRunning = false;
76 _host.SetActiveTaskStrip("Autonomous Agent", "Paused", 0);
77 var state = _runState;
78 _host.ResultSummary = state is null
79 ? "Autonomous flow paused."
80 : $"Autonomous paused. Next step: {state.NextStep + 1}/{state.MaxSteps}.";
81 OnPropertyChanged(nameof(IsAutonomousPaused));
82 StartAutonomousCommand.NotifyCanExecuteChanged();
83 ResumeAutonomousCommand.NotifyCanExecuteChanged();
84 }
85
86 private bool CanResumeAutonomous() =>
87 AutonomousCockpitActive && !IsAutonomousRunning && HasResumableAutonomousRun;
88
89 [RelayCommand(CanExecute = nameof(CanResumeAutonomous))]
90 private void ResumeAutonomous() =>
91 ResumeAutonomousFlow();
92
93 private void StartAutonomousFlow(string objective, int maxSteps)
94 {
95 if (!AutonomousCockpitActive)
96 return;
97
98 AutonomousObjective = objective;
99 AutonomousMaxSteps = maxSteps;
100
101 _runState = new AutonomousRunState
102 {
103 Objective = objective,
104 SafetyLevel = _host.SafetyLevel,
105 MaxSteps = maxSteps,
106 NextStep = 0
107 };
108
109 _cts?.Cancel();
110 _cts = new CancellationTokenSource();
111 var ct = _cts.Token;
112
113 IsAutonomousRunning = true;
114 _host.SetActiveTaskStrip("Autonomous Agent", "Running", 0);
115 _host.ResultSummary = "Autonomous flow started…";
116 OnPropertyChanged(nameof(IsAutonomousPaused));
117
118 _task = Task.Run(async () =>
119 {
120 try
121 {
122 var state = _runState;
123 var result = await _agentService.RunAutonomousAsync(
124 objective,
125 _host.SafetyLevel,
126 maxSteps,
127 state,
128 ct)
129 .ConfigureAwait(false);
130 UiScheduler.Default.Post(() =>
131 {
132 IsAutonomousRunning = false;
133 _host.SetActiveTaskStrip("Autonomous Agent", "Done", 100);
134 _host.ResultSummary = result;
135 _runState = null;
136 OnPropertyChanged(nameof(IsAutonomousPaused));
137 StartAutonomousCommand.NotifyCanExecuteChanged();
138 ResumeAutonomousCommand.NotifyCanExecuteChanged();
139 });
140 }
141 catch (OperationCanceledException)
142 {
143 var state = _runState;
144 UiScheduler.Default.Post(() =>
145 {
146 IsAutonomousRunning = false;
147 _host.SetActiveTaskStrip("Autonomous Agent", "Paused", 0);
148 _host.ResultSummary = state is null
149 ? "Autonomous flow cancelled."
150 : $"Autonomous paused. Next step: {state.NextStep + 1}/{state.MaxSteps}.";
151 OnPropertyChanged(nameof(IsAutonomousPaused));
152 StartAutonomousCommand.NotifyCanExecuteChanged();
153 ResumeAutonomousCommand.NotifyCanExecuteChanged();
154 });
155 }
156 catch (Exception ex)
157 {
158 UiScheduler.Default.Post(() =>
159 {
160 IsAutonomousRunning = false;
161 _host.SetActiveTaskStrip("Autonomous Agent", "Error", 0);
162 _host.ResultSummary = "Autonomous error: " + ex.Message;
163 _runState = null;
164 OnPropertyChanged(nameof(IsAutonomousPaused));
165 StartAutonomousCommand.NotifyCanExecuteChanged();
166 ResumeAutonomousCommand.NotifyCanExecuteChanged();
167 });
168 }
169 }, ct);
170 }
171
172 private void ResumeAutonomousFlow()
173 {
174 if (!CanResumeAutonomous())
175 return;
176
177 var state = _runState;
178 if (state is null)
179 return;
180
181 _host.SafetyLevel = state.SafetyLevel;
182 AutonomousObjective = state.Objective;
183 AutonomousMaxSteps = state.MaxSteps;
184
185 _cts?.Cancel();
186 _cts = new CancellationTokenSource();
187 var ct = _cts.Token;
188
189 IsAutonomousRunning = true;
190 _host.SetActiveTaskStrip("Autonomous Agent", "Running", 0);
191 _host.ResultSummary = $"Autonomous resumed from step {state.NextStep + 1}/{state.MaxSteps}…";
192 OnPropertyChanged(nameof(IsAutonomousPaused));
193
194 var capturedState = state;
195
196 _task = Task.Run(async () =>
197 {
198 try
199 {
200 var result = await _agentService.RunAutonomousAsync(
201 capturedState.Objective,
202 capturedState.SafetyLevel,
203 capturedState.MaxSteps,
204 capturedState,
205 ct)
206 .ConfigureAwait(false);
207 UiScheduler.Default.Post(() =>
208 {
209 IsAutonomousRunning = false;
210 _host.SetActiveTaskStrip("Autonomous Agent", "Done", 100);
211 _host.ResultSummary = result;
212 _runState = null;
213 OnPropertyChanged(nameof(IsAutonomousPaused));
214 StartAutonomousCommand.NotifyCanExecuteChanged();
215 ResumeAutonomousCommand.NotifyCanExecuteChanged();
216 });
217 }
218 catch (OperationCanceledException)
219 {
220 UiScheduler.Default.Post(() =>
221 {
222 IsAutonomousRunning = false;
223 _host.SetActiveTaskStrip("Autonomous Agent", "Paused", 0);
224 _host.ResultSummary =
225 $"Autonomous paused. Next step: {capturedState.NextStep + 1}/{capturedState.MaxSteps}.";
226 OnPropertyChanged(nameof(IsAutonomousPaused));
227 StartAutonomousCommand.NotifyCanExecuteChanged();
228 ResumeAutonomousCommand.NotifyCanExecuteChanged();
229 });
230 }
231 catch (Exception ex)
232 {
233 UiScheduler.Default.Post(() =>
234 {
235 IsAutonomousRunning = false;
236 _host.SetActiveTaskStrip("Autonomous Agent", "Error", 0);
237 _host.ResultSummary = "Autonomous error: " + ex.Message;
238 _runState = null;
239 OnPropertyChanged(nameof(IsAutonomousPaused));
240 StartAutonomousCommand.NotifyCanExecuteChanged();
241 ResumeAutonomousCommand.NotifyCanExecuteChanged();
242 });
243 }
244 }, ct);
245 }
246
247 [RelayCommand]
248 private void FixFailingTests()
249 {
250 var objective = "Fix failing tests using minimal-risk changes. Use get_ide_state / run_affected_tests, then propose safe fixes.";
251 if (AutonomousCockpitActive)
252 {
253 StartAutonomousFlow(objective, maxSteps: 10);
254 return;
255 }
256
257 _host.OpenChatForAgentFallback(
258 "Fix failing tests using minimal-risk changes. Start with ide_run_affected_tests and explain each step.");
259 }
260
261 [RelayCommand]
262 private void InvestigateNullref()
263 {
264 var objective = "Investigate possible null reference in current context. Show the shortest safe fix plan with evidence from diagnostics/tests.";
265 if (AutonomousCockpitActive)
266 {
267 StartAutonomousFlow(objective, maxSteps: 8);
268 return;
269 }
270
271 _host.OpenChatForAgentFallback(
272 "Investigate possible null reference in current context. Show the shortest safe fix plan.");
273 }
274
275 [RelayCommand]
276 private void PrepareCommit()
277 {
278 var objective = "Prepare a clean commit plan grouped by logical changes and include verification steps.";
279 if (AutonomousCockpitActive)
280 {
281 StartAutonomousFlow(objective, maxSteps: 6);
282 return;
283 }
284
285 _host.OpenChatForAgentFallback(
286 "Prepare a clean commit plan grouped by logical changes and include verification steps.");
287 }
288
289 [ObservableProperty]
290 [NotifyCanExecuteChangedFor(nameof(StartAutonomousCommand))]
291 private string _autonomousObjective = "Autonomous objective: fix issues in the current workspace.";
292
293 [ObservableProperty]
294 [NotifyCanExecuteChangedFor(nameof(StartAutonomousCommand))]
295 private int _autonomousMaxSteps = 10;
296
297 [ObservableProperty]
298 [NotifyCanExecuteChangedFor(nameof(StartAutonomousCommand))]
299 [NotifyCanExecuteChangedFor(nameof(PauseAutonomousCommand))]
300 [NotifyPropertyChangedFor(nameof(IsAutonomousPaused))]
301 private bool _isAutonomousRunning;
302}
303
View only · write via MCP/CIDE