XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFEEDToolBox

Fix trade page cancellation for externally started workflows

e9c511e
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

5 个文件 +48 -6
Modified XFEEDToolBox/ViewModels/AutomaticTradeViewModel.cs +20 -3
@@ -12,6 +12,7 @@ public partial class ToolboxViewModel
12 12 {
13 13 private sealed record AutomaticTradeMembership(string CommanderId,int ProcessId,DateTimeOffset ObservedAt,OriginalCommunityGoalRead Read);
14 14 private CancellationTokenSource? automaticTradeStop;
15 private string? automaticTradeConfigurationPath;
15 16 internal async Task RunAutomaticTradeAsync(string configurationPath)
16 17 {
17 18 if(RunActive||automaticTradeStop is not null)throw new InvalidOperationException("工具箱已有跑商流程。");
@@ -19,6 +20,7 @@ public partial class ToolboxViewModel
19 20 ??throw new InvalidDataException("自动跑商配置为空。");configuration.Validate();
20 21 Directory.CreateDirectory(configuration.WorkingDirectory);
21 22 using var lease=new FileStream(Path.Combine(configuration.WorkingDirectory,"workflow.lock"),FileMode.OpenOrCreate,FileAccess.ReadWrite,FileShare.None);
23 automaticTradeConfigurationPath=Path.GetFullPath(configurationPath);
22 24 using var stop=CancellationTokenSource.CreateLinkedTokenSource(lifetime.Token);automaticTradeStop=stop;RunActive=true;
23 25 var token=stop.Token;var root=configuration.WorkingDirectory;var phase="preflight";var initial=runtime.Player;
24 26 var completedVisits=0;var completedBatches=0;decimal net=0;string? activeJourney=null;NativeTradeCommandDriver? driver=null;
@@ -36,7 +38,8 @@ public partial class ToolboxViewModel
36 38 }
37 39 async Task State(string value,string? issue=null)
38 40 {
39 phase=value;RunStatus=issue is null?"自动跑商中":"需要处理";RunMessage=issue??value;ActualProfit=$"{net:N0} Cr";ProfitLabel="本次已确认净利润";runtime.Log("自动跑商",issue??value);
41 phase=value;RunStatus=value switch{"stopped"=>"已取消","stopped-pending-reconciliation"=>"已取消,待核对",_=>issue is null?"自动跑商中":"需要处理"};
42 RunMessage=issue??(value=="stopped"?"自动跑商已取消,已确认交易和恢复检查点已保留。":value);ActualProfit=$"{net:N0} Cr";ProfitLabel="本次已确认净利润";runtime.Log("自动跑商",RunMessage);
40 43 var payload=new{at=DateTimeOffset.UtcNow,phase,issue,executor="XFEEDToolBox",agentControlsGame=false,scheduledTask=false,completedVisits,completedBatches,activeJourney,
41 44 runtime.Player.SystemName,runtime.Player.StationName,runtime.Player.Docked,runtime.Player.CargoCount,netProfit=net};
42 45 var path=Path.Combine(root,"state.json");await File.WriteAllTextAsync(path+".tmp",JsonSerializer.Serialize(payload,ToolboxSettings.JsonOptions));File.Move(path+".tmp",path,true);
@@ -264,8 +267,16 @@ public partial class ToolboxViewModel
264 267 await File.WriteAllTextAsync(Path.Combine(activeJourney,"leg.json"),JsonSerializer.Serialize(leg,ToolboxSettings.JsonOptions),token);
265 268 await CompleteJourney(activeJourney,leg,membershipClient);destinationQuotes=await Quotes(configuration.Destination);
266 269 }
270 token.ThrowIfCancellationRequested();
271 }
272 catch(Exception error)when(token.IsCancellationRequested)
273 {
274 var unresolved=Directory.EnumerateFiles(root,"*.pending.json",SearchOption.AllDirectories)
275 .Any(path=>!File.Exists(path.Replace(".pending.json",".confirmed.json",StringComparison.Ordinal)));
276 await File.WriteAllTextAsync(Path.Combine(root,"cancellation-"+DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()+".json"),
277 JsonSerializer.Serialize(new{at=DateTimeOffset.UtcNow,phase,unresolved,error=error.ToString()},ToolboxSettings.JsonOptions));
278 await State(unresolved?"stopped-pending-reconciliation":"stopped",unresolved?"跑商已取消;已提交请求尚待核对,不会重复下单。":null);
267 279 }
268 catch(OperationCanceledException)when(token.IsCancellationRequested){await State("stopped");}
269 280 catch(Exception error)
270 281 {
271 282 await File.WriteAllTextAsync(Path.Combine(root,"failure-"+DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()+".json"),
@@ -274,7 +285,13 @@ public partial class ToolboxViewModel
274 285 }
275 286 finally
276 287 {
277 if(driver is not null)await driver.ReleaseAllAsync();automaticTradeStop=null;RunActive=false;
288 try{if(driver is not null)await driver.ReleaseAllAsync();}
289 catch(Exception error)
290 {
291 await File.WriteAllTextAsync(Path.Combine(root,"release-failure-"+DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()+".json"),error.ToString());
292 await State("requires-recovery","释放跑商控制失败:"+error.Message);ShowError(error.Message);
293 }
294 finally{automaticTradeStop=null;RunActive=false;RefreshTradePlanCommands();CalculateCommand.NotifyCanExecuteChanged();}
278 295 }
279 296 }
280 297 }
Modified XFEEDToolBox/ViewModels/ToolboxViewModel.cs +12 -1
@@ -186,6 +186,7 @@ public partial class ToolboxViewModel : ViewModelBase, IDisposable
186 186 { var values=next.ToArray();return current.SequenceEqual(values)?current:values; }
187 187 private void OnRunChanged(RunProgress progress) => dispatcher.TryEnqueue(() =>
188 188 {
189 if (automaticTradeStop is not null) return;
189 190 lastRun = progress;
190 191 RunActive = progress.State is RunState.Running or RunState.Paused;
191 192 RunStatus = progress.State switch { RunState.Running => "执行中", RunState.Paused => "已暂停", RunState.Completed => "已完成", RunState.Stopped => "已停止", RunState.Failed => "需要处理", _ => "等待路线" };
@@ -480,7 +481,17 @@ public partial class ToolboxViewModel : ViewModelBase, IDisposable
480 481 }
481 482 [RelayCommand] private void PauseRun() => runtime.Runner.Pause();
482 483 [RelayCommand] private void ResumeRun() { try { runtime.Runner.Resume(); } catch (Exception e) { ShowError(e.Message); } }
483 [RelayCommand] private void StopRun() {automaticTradeStop?.Cancel();runtime.Runner.Stop();}
484 [RelayCommand] private void StopRun()
485 {
486 if (automaticTradeStop is { } stop)
487 {
488 RunStatus = "正在取消";
489 RunMessage = "正在停止跑商,核对已提交请求并释放控制。";
490 stop.Cancel();
491 return;
492 }
493 runtime.Runner.Stop();
494 }
484 495 [RelayCommand] private void PreviousMarketPage() { marketPage = Math.Max(0, marketPage - 1); Interlocked.Increment(ref marketRequest); OnDataChanged(RuntimeChange.Markets); }
485 496 [RelayCommand] private void NextMarketPage() { if (MarketRows.Count == 150) marketPage++; Interlocked.Increment(ref marketRequest); OnDataChanged(RuntimeChange.Markets); }
486 497 [RelayCommand] private void CopyDestination()
Modified XFEEDToolBox/ViewModels/TradePlanExecutionViewModel.cs +6 -1
@@ -5,7 +5,7 @@ namespace XFEEDToolBox.ViewModels;
5 5
6 6 public partial class ToolboxViewModel
7 7 {
8 private bool CanExecuteTradePlan() => SelectedStart is not null && CanCalculate() && !NavigationActive
8 private bool CanExecuteTradePlan() => (SelectedStart is not null || automaticTradeConfigurationPath is not null) && CanCalculate() && !NavigationActive
9 9 && !DemoMode && !Environment.GetCommandLineArgs().Contains("--offline-preview");
10 10
11 11 partial void OnSelectedStartChanged(StationLiveRow? value) => RefreshTradePlanCommands();
@@ -20,6 +20,11 @@ public partial class ToolboxViewModel
20 20 private async Task ExecuteTradePlanAsync(CancellationToken cancellationToken)
21 21 {
22 22 if (!CanExecuteTradePlan()) return;
23 if (automaticTradeConfigurationPath is { } configurationPath)
24 {
25 await RunAutomaticTradeAsync(configurationPath);
26 return;
27 }
23 28 ExecutionModeIndex = 2;
24 29 Plan = null;
25 30 SelectedLeg = null;
Modified XFEEDToolBox/Views/PlannerPage.xaml +1 -1
@@ -35,7 +35,7 @@
35 35 <StackPanel Spacing="14">
36 36 <Grid ColumnSpacing="16"><Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
37 37 <StackPanel Spacing="6"><TextBlock Text="本次执行" Style="{StaticResource Eyebrow}"/><TextBlock Text="{x:Bind ViewModel.RunStatus, Mode=OneWay}" FontSize="23" FontWeight="SemiBold"/></StackPanel>
38 <Button x:Name="ExecuteTradePlanButton" Grid.Column="1" Content="自动执行" AutomationProperties.Name="自动执行跑商路线" Style="{StaticResource PrimaryButton}" MinWidth="132" VerticalAlignment="Center" controls:AsyncAction.Command="{x:Bind ViewModel.ExecuteTradePlanCommand}" controls:AsyncAction.CancelCommand="{x:Bind ViewModel.StopRunCommand}"/>
38 <Button x:Name="ExecuteTradePlanButton" Grid.Column="1" Content="自动执行" AutomationProperties.Name="自动执行跑商路线" AutomationProperties.AutomationId="ExecuteTradePlanButton" Style="{StaticResource PrimaryButton}" MinWidth="132" VerticalAlignment="Center" controls:AsyncAction.Command="{x:Bind ViewModel.ExecuteTradePlanCommand}" controls:AsyncAction.CancelCommand="{x:Bind ViewModel.StopRunCommand}" controls:AsyncAction.IsRunning="{x:Bind ViewModel.RunActive, Mode=OneWay}"/>
39 39 </Grid>
40 40 <TextBlock Text="{x:Bind ViewModel.RunMessage, Mode=OneWay}" TextWrapping="Wrap"/>
41 41 <ProgressBar Value="{x:Bind ViewModel.RunPercent, Mode=OneWay}" Maximum="100"/>
Modified docs/TRADE-WORK-COORDINATION.md +9 -0
@@ -2,6 +2,15 @@
2 2
3 3 更新:2026-10-24(Asia/Shanghai)。最新状态以紧接下方的记录为准,后文保留历史故障及验收过程。
4 4
5 ## 跑商页取消已完成实际验证;新采购失败单独保留(2026-10-24 21:54)
6
7 - 用户要求以最终目标和实际验收作为交付标准,不把源码修改、构建通过或阶段进度称为完成;本次具体目标是运行中直接点跑商页取消,保留工具箱窗口。
8 - 根因是自动流程从工具箱入口启动时,按钮只看 ExecuteTradePlanCommand.IsRunning,没有绑定真正的 RunActive。现同一个按钮绑定实际运行状态;独立流程不会被旧 Runner 的回调覆盖;取消过程中明确显示正在取消,结束后显示已取消。再次点击自动执行继续同一配置和检查点。
9 - Release 构建零警告、零错误。实际工具箱 PID90544/startTicks639284460207594112,在同一个窗口中连续通过两次启动/取消;状态均为 stopped,工作锁释放,按钮恢复启用。13:40:24Z 至13:41:30Z的验证区间内没有新采购、销售、维护订单或离站,未核对订单为0。原图、执行区放大图、中文OCR、UIA与行为回执交叉核验,两个场景的视觉gate均pass,证据 work/visual/trade-cancel/。
10 - 取消不重放已提交请求;保留原始回执、账本和恢复检查点。有未核对订单时显示已取消,待核对;释放控制异常保留失败原因,运行状态仍能正确收尾。没有改原生ABI、使用键鼠、创建定时任务或调用子智能体。
11 - 上述验证结束后,21:42出现新采购操作668aa9c4b307410a8aa94b9aacf68134:原生state=4/error=7,mutationsSent=1、clientApplied=false、reconciliationRequired=true;客户端货舱0、钱包68785438,但这些不足以证明服务器未成交。原单和 pending 文件完整保留,未删除、补写成功或重发。该交易故障不是取消按钮验证成功的证据,也不能把取消修复称为跑商已恢复。
12 - 21:46:30原始Undocked、随后LTT18046/Phanes的FSDJump、21:51:30 Leiber Installation停靠及21:51:52 ShipyardSwap确认当前已变为ShipID7。后台原图与原生当前星系读取核对Phanes。此前把停靠状态8归因于打开站务界面、要求返回驾驶舱,依据不足,已撤回;不得为解决此问题直接把8加入可交易停靠状态。当前旧Corsair配置保持requires-recovery,不自动接管换船后的航行或重放该采购。
13
5 14 ## 新流程两批已全部结算,新增确认净19145877 Cr(2026-10-24 21:15)
6 15
7 16 - 第二批 `journey-350a1809f44d4218ae0fd8bdc6159510` 已完整passed:采购7f3a87ac3b244bfea48e0acaee767ab5的192t铀全部售出,唯一售出2961988d665543bf9be27e3fcccbbb6f。售出后只读市场状态一次IPC超时,已保留原回执;恢复只完成剩余服务及余额核对,绝不重买/重卖。只读同generation状态轮询现在在原35秒上限内重试已知瞬态,不重发交易或服务请求。