diff --git a/Launcher.Next.App/PilotCli.cs b/Launcher.Next.App/PilotCli.cs index cc908ee..47e17e9 100644 --- a/Launcher.Next.App/PilotCli.cs +++ b/Launcher.Next.App/PilotCli.cs @@ -237,9 +237,11 @@ internal static class PilotCli // terminal evidence is durable. Killing an active AutoCAD // command before that boundary would turn ForceAfterEvidence // into an unsafe general process killer. - var evidenceTimeout = Task.Delay(TimeSpan.FromSeconds(Math.Clamp(config.GracefulExitTimeoutSeconds, 1, 120))); - while (!exit.IsCompleted && !HasCompletedEvidence(evidencePath)) - if (await Task.WhenAny(exit, evidenceTimeout) == evidenceTimeout) break; + // Evidence is written by the Plant process, not by a task owned + // by this host. Poll it with a bounded interval: waiting only on + // the timeout task delays an already durable result by a full + // timeout and makes the preview/apply hand-off unnecessarily slow. + await WaitForEvidenceOrExitAsync(exit, evidencePath, TimeSpan.FromSeconds(Math.Clamp(config.GracefulExitTimeoutSeconds, 1, 120))); if (exit.IsCompleted) gracefulExit = true; else { @@ -297,6 +299,12 @@ internal static class PilotCli private static bool HashesEqual(IReadOnlyDictionary x, IReadOnlyDictionary y) => x.Count == y.Count && x.All(p => y.TryGetValue(p.Key, out var value) && value == p.Value); private static PilotExecutionService CreateService(ResolvedLauncherPaths paths) => new(new PackageManifestValidator(), new PilotRootPolicy(paths.LocalLauncherRoot), new NextPilotSafetyGate(), new Sha256FileHasher(), new StartupScriptBuilder(), PermitService, new Probe(), new OperatingSystemProcessInvoker(), new PathResolver()); private static async Task WaitForExitAsync(int processId) { using var process = Process.GetProcessById(processId); await process.WaitForExitAsync(); } + private static async Task WaitForEvidenceOrExitAsync(Task exit, string evidencePath, TimeSpan timeout) + { + var deadline = DateTimeOffset.UtcNow + timeout; + while (!exit.IsCompleted && !HasCompletedEvidence(evidencePath) && DateTimeOffset.UtcNow < deadline) + await Task.WhenAny(exit, Task.Delay(TimeSpan.FromMilliseconds(250))); + } private static bool HasCompletedEvidence(string path) { try { using var json = JsonDocument.Parse(File.ReadAllText(path)); return json.RootElement.TryGetProperty("Completed", out var completed) && completed.ValueKind == JsonValueKind.True; }