diff --git a/CHANGELOG.md b/CHANGELOG.md index 4592c1ae307..273868a8488 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Docs: https://docs.openclaw.ai - Plugins/Chutes: add a bundled Chutes provider with plugin-owned OAuth/API-key auth, dynamic model discovery, and default-on extension wiring. (#41416) Thanks @Veightor. - Plugins/binding: add `onConversationBindingResolved(...)` so plugins can react immediately after bind approvals or denies without blocking channel interaction acknowledgements. (#48678) Thanks @huntharo. - CLI/config: expand `config set` with SecretRef and provider builder modes, JSON/batch assignment support, and `--dry-run` validation with structured JSON output. (#49296) Thanks @joshavant. +- macOS/onboarding: refresh the local setup flow, keep existing configured local installs connected across onboarding-version bumps, and update OpenAI/GitHub Copilot onboarding defaults to GPT-5.4. (#47263) Thanks @ImLukeF. ### Breaking diff --git a/apps/macos/Sources/OpenClaw/CronModels.swift b/apps/macos/Sources/OpenClaw/CronModels.swift index d4c7406eb70..78016ff9f88 100644 --- a/apps/macos/Sources/OpenClaw/CronModels.swift +++ b/apps/macos/Sources/OpenClaw/CronModels.swift @@ -14,10 +14,6 @@ enum CronCustomSessionTarget: Codable, Equatable { case predefined(CronSessionTarget) case session(id: String) - static let main: CronCustomSessionTarget = .predefined(.main) - static let isolated: CronCustomSessionTarget = .predefined(.isolated) - static let current: CronCustomSessionTarget = .predefined(.current) - var rawValue: String { switch self { case .predefined(let target): diff --git a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift index fda6730da43..7a3dfa4aab0 100644 --- a/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift +++ b/apps/macos/Sources/OpenClaw/NodeMode/MacNodeModeCoordinator.swift @@ -42,9 +42,11 @@ final class MacNodeModeCoordinator { continue } + let root = OpenClawConfigFile.loadDict() let onboardingComplete = Self.shouldConnectNodeMode( onboardingSeen: defaults.bool(forKey: onboardingSeenKey), - onboardingVersion: defaults.integer(forKey: onboardingVersionKey)) + onboardingVersion: defaults.integer(forKey: onboardingVersionKey), + root: root) if !onboardingComplete { if !lastBlockedOnOnboarding { self.logger.info("mac node waiting for onboarding completion") @@ -131,8 +133,18 @@ final class MacNodeModeCoordinator { } } - static func shouldConnectNodeMode(onboardingSeen: Bool, onboardingVersion: Int) -> Bool { - onboardingSeen && onboardingVersion >= currentOnboardingVersion + static func shouldConnectNodeMode( + onboardingSeen: Bool, + onboardingVersion: Int, + root: [String: Any] + ) -> Bool { + if onboardingSeen && onboardingVersion >= currentOnboardingVersion { + return true + } + + // Preserve runtime connectivity for existing local installs when a newer + // app build refreshes onboarding copy or flow. + return OnboardingWizardModel.hasExistingLocalSetup(root: root) } private func currentCaps() -> [String] { diff --git a/apps/macos/Sources/OpenClaw/OnboardingWizard.swift b/apps/macos/Sources/OpenClaw/OnboardingWizard.swift index bd237dbc209..367ff7c0c7b 100644 --- a/apps/macos/Sources/OpenClaw/OnboardingWizard.swift +++ b/apps/macos/Sources/OpenClaw/OnboardingWizard.swift @@ -196,12 +196,35 @@ final class OnboardingWizardModel { return Self.shouldSkipWizard(root: root) } - static func shouldSkipWizard(root: [String: Any]) -> Bool { + static func hasExistingLocalSetup(root: [String: Any]) -> Bool { if let wizard = root["wizard"] as? [String: Any], !wizard.isEmpty { return true } + if let gateway = root["gateway"] as? [String: Any], + let auth = gateway["auth"] as? [String: Any] + { + if let mode = auth["mode"] as? String, + !mode.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + { + return true + } + if let token = auth["token"] as? String, + !token.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + { + return true + } + if let password = auth["password"] as? String, + !password.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + { + return true + } + } return false } + + static func shouldSkipWizard(root: [String: Any]) -> Bool { + Self.hasExistingLocalSetup(root: root) + } } struct OnboardingWizardStepView: View { diff --git a/apps/macos/Tests/OpenClawIPCTests/OnboardingWizardModelTests.swift b/apps/macos/Tests/OpenClawIPCTests/OnboardingWizardModelTests.swift new file mode 100644 index 00000000000..fd7e980ec0c --- /dev/null +++ b/apps/macos/Tests/OpenClawIPCTests/OnboardingWizardModelTests.swift @@ -0,0 +1,56 @@ +import Testing +@testable import OpenClaw + +@Suite(.serialized) +@MainActor +struct OnboardingWizardModelTests { + @Test func `skip wizard for legacy gateway auth config`() { + let root: [String: Any] = [ + "gateway": [ + "auth": [ + "token": "legacy-token", + ], + ], + ] + + #expect(OnboardingWizardModel.shouldSkipWizard(root: root)) + } + + @Test func `do not skip wizard for empty config`() { + #expect(OnboardingWizardModel.shouldSkipWizard(root: [:]) == false) + } + + @Test func `node mode keeps connecting for configured installs after onboarding refresh`() { + let root: [String: Any] = [ + "gateway": [ + "auth": [ + "token": "legacy-token", + ], + ], + ] + + #expect( + MacNodeModeCoordinator.shouldConnectNodeMode( + onboardingSeen: true, + onboardingVersion: currentOnboardingVersion - 1, + root: root)) + #expect( + MacNodeModeCoordinator.shouldConnectNodeMode( + onboardingSeen: false, + onboardingVersion: 0, + root: root)) + } + + @Test func `node mode blocks truly unconfigured installs until onboarding is current`() { + #expect( + MacNodeModeCoordinator.shouldConnectNodeMode( + onboardingSeen: false, + onboardingVersion: 0, + root: [:]) == false) + #expect( + MacNodeModeCoordinator.shouldConnectNodeMode( + onboardingSeen: true, + onboardingVersion: currentOnboardingVersion, + root: [:])) + } +} diff --git a/extensions/github-copilot/index.ts b/extensions/github-copilot/index.ts index ee85f76fd61..ec753e35d47 100644 --- a/extensions/github-copilot/index.ts +++ b/extensions/github-copilot/index.ts @@ -111,7 +111,7 @@ async function runGitHubCopilotAuth(ctx: ProviderAuthContext) { credential, }, ], - defaultModel: "github-copilot/gpt-4o", + defaultModel: "github-copilot/gpt-5.4", }; } diff --git a/src/commands/auth-choice.test.ts b/src/commands/auth-choice.test.ts index dd270a6d3d2..224fba0adc5 100644 --- a/src/commands/auth-choice.test.ts +++ b/src/commands/auth-choice.test.ts @@ -1060,7 +1060,7 @@ describe("applyAuthChoice", () => { }, }, ], - defaultModel: "github-copilot/gpt-4o", + defaultModel: "github-copilot/gpt-5.4", })), }, ], @@ -1089,7 +1089,7 @@ describe("applyAuthChoice", () => { }); expect(resolveAgentModelPrimaryValue(result.config.agents?.defaults?.model)).toBe( - "github-copilot/gpt-4o", + "github-copilot/gpt-5.4", ); } finally { if (previousIsTTYDescriptor) { diff --git a/src/commands/openai-model-default.test.ts b/src/commands/openai-model-default.test.ts index 504dc0b8556..93570f5e77d 100644 --- a/src/commands/openai-model-default.test.ts +++ b/src/commands/openai-model-default.test.ts @@ -112,7 +112,7 @@ describe("applyDefaultModelChoice", () => { }); it("uses applyDefaultConfig path when setDefaultModel is true", async () => { - const defaultModel = "openai/gpt-5.1-codex"; + const defaultModel = "openai/gpt-5.4"; const applied = await applyDefaultModelChoice({ config: {}, setDefaultModel: true, diff --git a/src/plugins/contracts/auth.contract.test.ts b/src/plugins/contracts/auth.contract.test.ts index 355ceb43962..894c71a0230 100644 --- a/src/plugins/contracts/auth.contract.test.ts +++ b/src/plugins/contracts/auth.contract.test.ts @@ -238,7 +238,7 @@ describe("provider auth contract", () => { }, }, ], - defaultModel: "github-copilot/gpt-4o", + defaultModel: "github-copilot/gpt-5.4", }); } finally { if (previousIsTTYDescriptor) { diff --git a/src/plugins/provider-model-defaults.ts b/src/plugins/provider-model-defaults.ts index 60a18c1a759..c4c2216b513 100644 --- a/src/plugins/provider-model-defaults.ts +++ b/src/plugins/provider-model-defaults.ts @@ -3,7 +3,7 @@ import { ensureModelAllowlistEntry } from "./provider-model-allowlist.js"; import { applyAgentDefaultPrimaryModel } from "./provider-model-primary.js"; export const GOOGLE_GEMINI_DEFAULT_MODEL = "google/gemini-3.1-pro-preview"; -export const OPENAI_DEFAULT_MODEL = "openai/gpt-5.1-codex"; +export const OPENAI_DEFAULT_MODEL = "openai/gpt-5.4"; export const OPENCODE_GO_DEFAULT_MODEL_REF = "opencode-go/kimi-k2.5"; export const OPENCODE_ZEN_DEFAULT_MODEL = "opencode/claude-opus-4-6";