Repository navigation
Expand file tree
/
Copy pathLiveMissionMonitorViewModel.cs
More file actions
180 lines (155 loc) · 6.27 KB
/
Copy pathLiveMissionMonitorViewModel.cs
File metadata and controls
180 lines (155 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml;
using MissionController.Core;
using MissionController.Core.Crew;
using MissionController.Core.Enums;
using MissionController.Core.Interfaces;
using MissionController.Core.Models;
namespace MissionController.UI.ViewModels;
public record LaunchMissionRequest(
SingleToolTemplate Template,
Dictionary<string, string> Parameters,
string OperatorIdentity);
public partial class LiveMissionMonitorViewModel : ObservableObject
{
private readonly IMissionExecutionEngine _engine;
private readonly IProjectExecutionEngine _projectEngine;
private CancellationTokenSource? _cts;
[ObservableProperty]
private string _currentStatus = "Idle";
[ObservableProperty]
private string _missionLog = string.Empty;
[ObservableProperty]
private string _stdoutStream = string.Empty;
[ObservableProperty]
private string _aiAnalysisStream = string.Empty;
[ObservableProperty]
private double _cpuUsagePercent = 0.0;
[ObservableProperty]
private long _memoryUsageBytes = 0;
[ObservableProperty]
private bool _isRunning = false;
[ObservableProperty]
private string _jailPath = "None";
// True once the engine signals no live model is connected. Drives a persistent banner so the
// simulated analysis in the AI pane can never be mistaken for genuine model output.
[ObservableProperty]
[NotifyPropertyChangedFor(nameof(SimulatedBannerVisibility))]
private bool _isSimulatedAnalysis;
public Visibility SimulatedBannerVisibility =>
IsSimulatedAnalysis ? Visibility.Visible : Visibility.Collapsed;
private readonly IOperatorIdentityProvider _identityProvider;
private readonly IGovernedCrewEngine? _crewEngine;
public LiveMissionMonitorViewModel(
IMissionExecutionEngine engine,
IProjectExecutionEngine projectEngine,
IOperatorIdentityProvider identityProvider,
IGovernedCrewEngine? crewEngine = null)
{
_engine = engine;
_projectEngine = projectEngine;
_identityProvider = identityProvider;
_crewEngine = crewEngine;
}
[RelayCommand]
public async Task LaunchAsync(LaunchMissionRequest request)
{
await StartMissionAsync(request.Template, request.Parameters);
}
// The operatorIdentity parameter these two used to take was supplied by the caller as a string
// literal - "Operator_SecOpsTier1", "Operator_ProjectLead" - so the ledger's actor column was
// decided by whichever view happened to dispatch. Resolving it here removes the opportunity.
public async Task StartMissionAsync(SingleToolTemplate template, Dictionary<string, string> parameters)
{
if (IsRunning) return;
var actor = await _identityProvider.ResolveAsync();
PrepareRun("Starting Mission...");
await RunStreamAsync(_engine.ExecuteMissionAsync(template, parameters, actor.ToLedgerIdentity(), _cts!.Token));
}
public async Task StartProjectAsync(ProjectTemplate project, Dictionary<string, string> parameters)
{
if (IsRunning) return;
var actor = await _identityProvider.ResolveAsync();
PrepareRun("Starting Project...");
await RunStreamAsync(_projectEngine.ExecuteProjectAsync(project, parameters, actor.ToLedgerIdentity(), _cts!.Token));
}
public async Task StartCrewAsync(GovernedCrewDefinition crew, Dictionary<string, string> parameters)
{
if (IsRunning) return;
if (_crewEngine == null) return;
var actor = await _identityProvider.ResolveAsync();
PrepareRun($"Starting Governed Crew '{crew.Name}'...");
await RunStreamAsync(_crewEngine.ExecuteCrewAsync(crew, parameters, actor.ToLedgerIdentity(), _cts!.Token));
}
[RelayCommand]
public void CancelMission()
{
_cts?.Cancel();
}
private void PrepareRun(string status)
{
IsRunning = true;
CurrentStatus = status;
MissionLog = string.Empty;
StdoutStream = string.Empty;
AiAnalysisStream = string.Empty;
IsSimulatedAnalysis = false;
_cts?.Dispose();
_cts = new CancellationTokenSource();
}
private async Task RunStreamAsync(IAsyncEnumerable<MissionChunk> stream)
{
try
{
await foreach (var chunk in stream)
{
switch (chunk.ChunkType)
{
case MissionChunkType.StatusChange:
CurrentStatus = chunk.Content;
MissionLog += $"[{chunk.TimestampUtc:HH:mm:ss}] STATUS: {chunk.Content}\n";
break;
case MissionChunkType.Stdout:
StdoutStream += chunk.Content;
break;
case MissionChunkType.Stderr:
MissionLog += $"[{chunk.TimestampUtc:HH:mm:ss}] STDERR: {chunk.Content}\n";
break;
case MissionChunkType.AiToken:
AiAnalysisStream += chunk.Content;
break;
case MissionChunkType.SimulatedNotice:
IsSimulatedAnalysis = true;
MissionLog += $"[{chunk.TimestampUtc:HH:mm:ss}] SIMULATED: {chunk.Content}\n";
break;
case MissionChunkType.Metric:
MissionLog += $"[{chunk.TimestampUtc:HH:mm:ss}] METRIC: {chunk.Content}\n";
break;
case MissionChunkType.Completed:
CurrentStatus = "Completed";
MissionLog += $"[{chunk.TimestampUtc:HH:mm:ss}] {chunk.Content}\n";
break;
}
}
}
catch (OperationCanceledException)
{
CurrentStatus = "Mission Aborted";
MissionLog += "\n[ABORT] Mission terminated by operator.\n";
}
catch (Exception ex)
{
CurrentStatus = "Mission Failed";
MissionLog += $"\n[ERROR] {ex.Message}\n";
}
finally
{
IsRunning = false;
}
}
}