fix: restore existing WeChat instance and add M6 validation tools

This commit is contained in:
2026-09-05 22:49:18 +08:00
parent 1bd0344e8b
commit 1e8e894dba
23 changed files with 1357 additions and 48 deletions
+137
View File
@@ -35,6 +35,33 @@ try
WriteJson(report);
return report.Errors.Count == 0 ? 0 : 2;
}
case "diagnose":
{
var directory = Path.GetFullPath(GetRequiredOption(args, "--output"));
Directory.CreateDirectory(directory);
var snapshot = await WechatUiInspector.CaptureAsync(Path.Combine(directory, "ui-tree.json"), cancellationToken);
var baseline = snapshot;
if (GetOption(args, "--baseline") is { } baselinePath)
{
await using var input = File.OpenRead(baselinePath);
baseline = await JsonSerializer.DeserializeAsync<UiNodeSnapshot>(input,
new JsonSerializerOptions { MaxDepth = 128 }, cancellationToken)
?? throw new WxAgentException(WxAgentErrorCode.InvalidArgument, "Baseline UI snapshot is empty.");
}
var report = new
{
correlationId = Guid.NewGuid().ToString("N"),
stage = "ui-compatibility-diagnostics",
capturedAt = DateTimeOffset.UtcNow,
doctor = WechatDoctor.Run(cancellationToken),
nativeWindows = WechatChatClient.InspectNativeWindows(),
comparison = UiSnapshotComparison.Compare(baseline, snapshot)
};
await File.WriteAllTextAsync(Path.Combine(directory, "diagnostics.json"),
JsonSerializer.Serialize(report, jsonOptions), cancellationToken);
WriteJson(report);
return report.doctor.Errors.Count == 0 && report.comparison.MissingRequiredControls.Count == 0 ? 0 : 2;
}
case "inspect-ui":
{
var output = GetRequiredOption(args, "--output");
@@ -62,6 +89,78 @@ try
});
return report.Errors.Count == 0 ? 0 : 2;
}
case "tray-status":
{
WriteJson(WechatChatClient.InspectTray(cancellationToken));
return 0;
}
case "window-status":
{
WriteJson(WechatChatClient.InspectNativeWindows());
return 0;
}
case "listener-smoke":
{
var result = await WechatChatClient.ListenerSmokeAsync(GetRequiredOption(args, "--output"), cancellationToken);
WriteJson(result);
return result.Sent && result.MatchingEvents == 1 && result.EventsAfterRestart == 0
&& result.Snapshots >= 2 && result.CheckpointSaved ? 0 : 2;
}
case "recovery-smoke":
{
var checks = await WechatChatClient.RecoverySmokeAsync(cancellationToken);
WriteJson(checks);
return checks.All(check => check.TransitionObserved && check.SameInstance && check.MainViewFound
&& check.SessionsFound && check.MessagesFound) ? 0 : 2;
}
case "recover-ui":
{
var result = await WechatChatClient.RecoverUiAsync(cancellationToken);
WriteJson(result);
return result.Success ? 0 : 2;
}
case "stability-smoke":
{
var seconds = GetPositiveIntOption(args, "--seconds", 15, 300);
var sampleSeconds = GetPositiveIntOption(args, "--sample-seconds", 5, seconds);
var output = GetOption(args, "--output") ?? Path.Combine("artifacts", "m6-stability-smoke");
var recovery = await WechatChatClient.RecoverUiAsync(cancellationToken);
var report = await WechatStabilityRunner.RunAsync(
TimeSpan.FromSeconds(seconds),
TimeSpan.FromSeconds(sampleSeconds),
output,
cancellationToken);
WriteJson(new
{
scope = "M6",
recovery.Success,
report.Healthy,
sampleCount = report.Samples.Count,
report.MessageEvents,
report.ReconnectEvents,
report.WorkingSetGrowthBytes,
report.HandleGrowth,
report.ThreadGrowth,
report.Findings,
output = Path.GetFullPath(output),
privacy = "message content omitted"
});
return report.Healthy ? 0 : 2;
}
case "endurance":
{
var hours = GetPositiveIntOption(args, "--hours", 24, 72);
var sampleSeconds = GetPositiveIntOption(args, "--sample-seconds", 60, 3600);
var output = GetOption(args, "--output") ?? Path.Combine("artifacts", $"endurance-{DateTimeOffset.UtcNow:yyyyMMddHHmmss}");
await WechatChatClient.RecoverUiAsync(cancellationToken);
var report = await WechatStabilityRunner.RunAsync(
TimeSpan.FromHours(hours),
TimeSpan.FromSeconds(sampleSeconds),
output,
cancellationToken);
WriteJson(report with { Samples = [] });
return report.Healthy ? 0 : 2;
}
case "m5-smoke":
{
var momentsWindow = await WechatChatClient.OpenMomentsAsync(cancellationToken);
@@ -372,6 +471,18 @@ static int ValidateCommandLine(string[] values)
return 30;
}
if (values[0] == "listener-smoke")
{
ValidateOptions(values, 1, ["--output", "--timeout"], []);
return 60;
}
if (values[0] == "diagnose")
{
ValidateOptions(values, 1, ["--output", "--baseline", "--timeout"], []);
return 60;
}
if (values[0] == "inspect-ui")
{
ValidateOptions(values, 1, ["--output", "--timeout"], []);
@@ -384,6 +495,24 @@ static int ValidateCommandLine(string[] values)
return 30;
}
if (values[0] is "recover-ui" or "window-status" or "tray-status" or "recovery-smoke")
{
ValidateOptions(values, 1, ["--timeout"], []);
return 30;
}
if (values[0] == "stability-smoke")
{
ValidateOptions(values, 1, ["--seconds", "--sample-seconds", "--output", "--timeout"], []);
return GetPositiveIntOption(values, "--seconds", 15, 300) + 60;
}
if (values[0] == "endurance")
{
ValidateOptions(values, 1, ["--hours", "--sample-seconds", "--output", "--timeout"], []);
return checked(GetPositiveIntOption(values, "--hours", 24, 72) * 3600 + 120);
}
if (values[0] == "m5-smoke")
{
ValidateOptions(values, 1, ["--timeout"], []);
@@ -586,10 +715,18 @@ static int CountNodes(UiNodeSnapshot node) => 1 + node.Children.Sum(CountNodes);
static void PrintHelp() => Console.WriteLine("""
WxAgent.Host commands:
doctor [--timeout 30]
diagnose --output <dir> [--baseline <ui-tree.json>] [--timeout 60]
inspect-ui --output <path> [--timeout 30]
smoke [--output <path>] [--timeout 30]
m4-smoke [--timeout 60]
m5-smoke [--timeout 60]
tray-status [--timeout 30]
window-status [--timeout 30]
listener-smoke --output <new-dir> [--timeout 60]
recovery-smoke [--timeout 30]
recover-ui [--timeout 30]
stability-smoke [--seconds 15] [--sample-seconds 5] [--output <dir>]
endurance [--hours 24] [--sample-seconds 60] [--output <dir>]
group at-all --group <name> --message <text> --confirm CONFIRM [--timeout 30]
group verify-at-all --group <name> --message <text> [--timeout 30]
chat send-url-card --to <name> --url <http-url> [--message <text>] --confirm CONFIRM [--timeout 60]