mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-25 20:05:46 -06:00
fix(macos): resolve the onboarding install prompt when the gateway is already running (#128273)
Fixes #128194. Local-mode onboarding has two rightful actors on the CLI page: the page's own install flow, which may open an install-target prompt on unreleased builds, and the connection-mode commit, which starts the gateway through ConnectionModeCoordinator the moment the user picks This Mac. When the gateway comes up on its own (dev-root builds, or an externally attached gateway), AI setup auto-connects and finish() runs — but close() was silently ineffective because AppKit ignores NSWindow.close() while a sheet is attached. The result, reproduced live with os_log instrumentation: a completed onboarding (dashboard open, onboardingSeen set) with a zombie onboarding window showing a dead CLI page and a stale channel-choice sheet. Two coupled fixes. OnboardingController.close() now ends any attached sheet before closing, so completion always tears the window down. And a running local gateway now resolves a pending install prompt directly: a new tested static (shouldResolveInstallPromptForRunningGateway) extends the existing gateway-status revise path to the choosingTarget phase, marking the step installed and dismissing the moot sheet; runCLIInstall returns quietly in that case instead of writing a cancellation over a resolved step. Genuine declines keep the cancelled status.
This commit is contained in:
committed by
GitHub
parent
7d154d32c1
commit
a85db8cea7
@@ -605,10 +605,17 @@ final class OnboardingController: NSObject, NSWindowDelegate {
|
||||
|
||||
func close() {
|
||||
self.busyReason = nil
|
||||
// AppKit ignores close while its modal sheet is still attached.
|
||||
self.dismissAttachedSheet()
|
||||
self.window?.close()
|
||||
self.window = nil
|
||||
}
|
||||
|
||||
func dismissAttachedSheet() {
|
||||
guard let window, let sheet = window.attachedSheet else { return }
|
||||
window.endSheet(sheet)
|
||||
}
|
||||
|
||||
func setWindowCloseEnabled(_ enabled: Bool) {
|
||||
self.window?.standardWindowButton(.closeButton)?.isEnabled = enabled
|
||||
}
|
||||
|
||||
@@ -94,8 +94,24 @@ extension OnboardingView {
|
||||
}
|
||||
}
|
||||
|
||||
static func shouldResolveInstallPromptForRunningGateway(
|
||||
gatewayStatus: GatewayProcessManager.Status,
|
||||
isLocal: Bool,
|
||||
phase: CLIInstallPhase) -> Bool
|
||||
{
|
||||
guard isLocal, phase == .choosingTarget else { return false }
|
||||
return switch gatewayStatus {
|
||||
case .running, .attachedExisting: true
|
||||
case .stopped, .starting, .failed: false
|
||||
}
|
||||
}
|
||||
|
||||
func reviseCLIActivationFailureIfGatewayReady(_ status: GatewayProcessManager.Status) {
|
||||
guard Self.shouldReviseCLIActivationFailure(
|
||||
let resolvesInstallPrompt = Self.shouldResolveInstallPromptForRunningGateway(
|
||||
gatewayStatus: status,
|
||||
isLocal: state.connectionMode == .local,
|
||||
phase: cliInstallPhase)
|
||||
guard resolvesInstallPrompt || Self.shouldReviseCLIActivationFailure(
|
||||
gatewayStatus: status,
|
||||
isLocal: state.connectionMode == .local,
|
||||
executableReady: cliExecutableReady,
|
||||
@@ -104,6 +120,10 @@ extension OnboardingView {
|
||||
cliInstalled = true
|
||||
cliStatusKnown = true
|
||||
cliStatus = nil
|
||||
if resolvesInstallPrompt {
|
||||
// A running local gateway already fulfills the pending install prompt.
|
||||
OnboardingController.shared.dismissAttachedSheet()
|
||||
}
|
||||
}
|
||||
|
||||
/// LocalGatewayActivation.failed carries the reason bound to that specific activation
|
||||
@@ -167,6 +187,8 @@ extension OnboardingView {
|
||||
guard let target = await CLIInstallPrompter.shared.installTargetForCurrentBuild(
|
||||
presentingSheetOn: OnboardingController.shared.sheetPresentationWindow)
|
||||
else {
|
||||
// Gateway readiness can resolve onboarding while this sheet is open.
|
||||
guard !cliInstalled else { return }
|
||||
cliStatus = "CLI installation cancelled."
|
||||
return
|
||||
}
|
||||
|
||||
@@ -255,6 +255,33 @@ struct OnboardingViewSmokeTests {
|
||||
installed: false))
|
||||
}
|
||||
|
||||
@Test func `running local gateway resolves only its pending CLI install prompt`() {
|
||||
for status in [GatewayProcessManager.Status.running(details: nil), .attachedExisting(details: "pid 4242")] {
|
||||
#expect(OnboardingView.shouldResolveInstallPromptForRunningGateway(
|
||||
gatewayStatus: status,
|
||||
isLocal: true,
|
||||
phase: .choosingTarget))
|
||||
}
|
||||
for status in [GatewayProcessManager.Status.starting, .stopped, .failed("unavailable")] {
|
||||
#expect(!OnboardingView.shouldResolveInstallPromptForRunningGateway(
|
||||
gatewayStatus: status,
|
||||
isLocal: true,
|
||||
phase: .choosingTarget))
|
||||
}
|
||||
for mode in [AppState.ConnectionMode.remote, .unconfigured] {
|
||||
#expect(!OnboardingView.shouldResolveInstallPromptForRunningGateway(
|
||||
gatewayStatus: .running(details: nil),
|
||||
isLocal: mode == .local,
|
||||
phase: .choosingTarget))
|
||||
}
|
||||
for phase in [OnboardingView.CLIInstallPhase.idle, .installing, .startingService] {
|
||||
#expect(!OnboardingView.shouldResolveInstallPromptForRunningGateway(
|
||||
gatewayStatus: .running(details: nil),
|
||||
isLocal: true,
|
||||
phase: phase))
|
||||
}
|
||||
}
|
||||
|
||||
@Test func `gateway start failure message retains the concrete reason`() {
|
||||
#expect(
|
||||
OnboardingView.gatewayStartFailureMessage(
|
||||
|
||||
Reference in New Issue
Block a user