diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs index f826095..2bbbbaf 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/FirstLimitedApplyAutomation.cs @@ -180,7 +180,10 @@ internal sealed class FirstLimitedApplyAutomationCoordinator { transitions.Add("LifecycleProbeCompleted"); WriteEvidence(true, null); - RequestControlledExit(); + // LifecycleProbe is already running from the first verified Idle callback. + // Do not wait for a second Idle: a startup-script host is permitted not to + // raise one, which otherwise leaves acad.exe responsive but stranded. + RequestControlledExit(idleSafe: true); } private void OnIdle(object? sender, EventArgs e) => RunCallback("Idle", BeginContextVerification); @@ -285,18 +288,28 @@ internal sealed class FirstLimitedApplyAutomationCoordinator // AutoCAD's direct Application.Quit can leave a startup-script host alive. // Dispatching the native QUIT command from the next quiescent Idle cycle // preserves normal command shutdown semantics. - private void RequestControlledExit() + private void RequestControlledExit(bool idleSafe = false) { if (exitQueued) return; exitQueued = true; Transition(CoordinatorState.ExitQueued, "ExitQueued"); + if (idleSafe) + { + DispatchNativeQuit(); + return; + } AcadApplication.Idle += OnExitIdle; exitHandlerSubscribed = true; } private void OnExitIdle(object? sender, EventArgs args) { - RunCallback("ControlledExitIdle", () => + RunCallback("ControlledExitIdle", DispatchNativeQuit); + } + + private void DispatchNativeQuit() + { + try { if (exitHandlerSubscribed) AcadApplication.Idle -= OnExitIdle; exitHandlerSubscribed = false; @@ -306,7 +319,13 @@ internal sealed class FirstLimitedApplyAutomationCoordinator if (active is null) { AcadApplication.Quit(); return; } transitions.Add("NativeQuitDispatched"); active.SendStringToExecute("_.QUIT _N ", true, false, false); - }); + } + catch (System.Exception exception) + { + // This is terminal cleanup: contain the exception and leave process + // cleanup to Launcher rather than re-entering document APIs. + FailFromCallback("ControlledExitIdle", exception, "AUTOMATION_CONTROLLED_EXIT_FAILED"); + } } private bool IsTerminal => state is CoordinatorState.Completed or CoordinatorState.Failed or CoordinatorState.ExitQueued;