macOS/onboarding: fix prep follow-ups for openclaw#47263, thanks @ImLukeF

This commit is contained in:
ImLukeF 2026-03-18 10:35:03 +11:00
parent 49c4ff67d4
commit 09d972c1dd
No known key found for this signature in database
10 changed files with 102 additions and 14 deletions

View File

@ -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

View File

@ -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):

View File

@ -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] {

View File

@ -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 {

View File

@ -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: [:]))
}
}

View File

@ -111,7 +111,7 @@ async function runGitHubCopilotAuth(ctx: ProviderAuthContext) {
credential,
},
],
defaultModel: "github-copilot/gpt-4o",
defaultModel: "github-copilot/gpt-5.4",
};
}

View File

@ -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) {

View File

@ -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,

View File

@ -238,7 +238,7 @@ describe("provider auth contract", () => {
},
},
],
defaultModel: "github-copilot/gpt-4o",
defaultModel: "github-copilot/gpt-5.4",
});
} finally {
if (previousIsTTYDescriptor) {

View File

@ -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";