mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-26 12:26:38 -06:00
34c3d15a6b
* fix(setup): refresh Codex registry with staged install * fix(macos): verify inference before onboarding handoff * fix(setup): use native Codex home for subscription auth * fix(codex): honor attempt-scoped setup config * fix(macos): align onboarding handoff with reopen * fix(setup): await prepared model convergence * fix(ui): avoid false auth state for empty catalog * fix(setup): scope catalog convergence to Codex gateway * fix(setup): publish the committed runtime catalog * fix(models): project configured static runtime models * fix(codex): expose app-server model catalog * fix(models): preserve Codex auth across reloads * fix(ci): align Codex onboarding checks * test(ui): stabilize dock suppression environment * fix(codex): honor discovery config in app-server model catalog The manifest documents discovery.enabled (bundled fallback list) and discovery.timeoutMs (default 2500ms) for model discovery; the new catalog path used the generic 60s request timeout and ignored the enable gate. Also drop the test-only listModels injection seam in favor of vi.mock. * fix(setup): refuse prepared Codex auth over an explicit remote transport configureCodexCliPreparedAuth silently rewrote an explicitly configured websocket/unix app-server to local stdio (keeping a dangling url), moving the credential boundary onto this host. Fail setup with actionable guidance instead; also surface the root cause when the prepared model catalog refresh fails after activation. * refactor(agents): one canonical model-catalog identity key Three near-identical key helpers existed (models-list-result, models-list-configured-static, harness/model-catalog). Export resolveModelCatalogIdentityKey from the route-policy owner, collapse the duplicate dedupe loops into dedupeByKey, make donor enrichment Map-based, and inline the one-off harness-augment wrapper. * fix(macos): restore custodian handoff for fresh activations Landing every finish on the plain dashboard stranded the custodian first-run flow (memory import, channels, permissions, hatch). Fresh activations now hand off to custodian onboarding; live-verified pre-existing setups reopen the normal dashboard, matching the removed already-configured shortcut. Tests pin the destination per path. Also isolate the post-startup Codex login test from developer machines: ambient OPENAI_API_KEY and a real Codex login made it assert-fail. --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
133 lines
5.1 KiB
Swift
133 lines
5.1 KiB
Swift
import Foundation
|
|
import OpenClawDiscovery
|
|
import SwiftUI
|
|
|
|
extension OnboardingView {
|
|
func selectLocalGateway() {
|
|
if state.connectionMode != .local {
|
|
resetGatewayBoundAIState()
|
|
}
|
|
defaultsToLocalGateway = false
|
|
state.connectionMode = .local
|
|
preferredGatewayID = nil
|
|
showAdvancedConnection = false
|
|
showRemoteChoices = false
|
|
GatewayDiscoveryPreferences.setPreferredStableID(nil)
|
|
probeConfiguredGatewayForDashboard()
|
|
}
|
|
|
|
func selectUnconfiguredGateway() {
|
|
resetGatewayBoundAIState()
|
|
defaultsToLocalGateway = false
|
|
state.connectionMode = .unconfigured
|
|
preferredGatewayID = nil
|
|
showAdvancedConnection = false
|
|
showRemoteChoices = false
|
|
GatewayDiscoveryPreferences.setPreferredStableID(nil)
|
|
}
|
|
|
|
func selectRemoteGateway(_ gateway: GatewayDiscoveryModel.DiscoveredGateway) {
|
|
let shouldResetGatewayState = Self.shouldResetGatewayBoundAIState(
|
|
connectionMode: state.connectionMode,
|
|
currentPreferredGatewayID: self.effectivePreferredGatewayID,
|
|
persistedPreferredGatewayID: GatewayDiscoveryPreferences.preferredStableID(),
|
|
selectedGatewayID: gateway.stableID)
|
|
if shouldResetGatewayState {
|
|
// The mode can remain `.remote` while the selected Gateway changes,
|
|
// so its onChange hook alone cannot retire route-bound state.
|
|
resetGatewayBoundAIState()
|
|
resetRemoteProbeFeedback()
|
|
}
|
|
defaultsToLocalGateway = false
|
|
preferredGatewayID = gateway.stableID
|
|
GatewayDiscoverySelectionSupport.applyRemoteSelection(gateway: gateway, state: state)
|
|
|
|
state.connectionMode = .remote
|
|
MacNodeModeCoordinator.shared.setPreferredGatewayStableID(gateway.stableID, state: state)
|
|
probeConfiguredGatewayForDashboard()
|
|
}
|
|
|
|
static func shouldResetGatewayBoundAIState(
|
|
connectionMode: AppState.ConnectionMode,
|
|
currentPreferredGatewayID: String?,
|
|
persistedPreferredGatewayID: String?,
|
|
selectedGatewayID: String) -> Bool
|
|
{
|
|
let currentGatewayID = Self.normalizedGatewayID(currentPreferredGatewayID) ??
|
|
Self.normalizedGatewayID(persistedPreferredGatewayID)
|
|
return connectionMode != .remote || currentGatewayID != Self.normalizedGatewayID(selectedGatewayID)
|
|
}
|
|
|
|
private static func normalizedGatewayID(_ value: String?) -> String? {
|
|
let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
return trimmed?.isEmpty == false ? trimmed : nil
|
|
}
|
|
|
|
var effectivePreferredGatewayID: String? {
|
|
let persisted = Self.normalizedGatewayID(GatewayDiscoveryPreferences.preferredStableID())
|
|
guard let local = Self.normalizedGatewayID(preferredGatewayID) else {
|
|
return persisted
|
|
}
|
|
// Config-watcher endpoint changes clear the persisted owner. Ignore the
|
|
// stale @State copy until the view's next render catches up.
|
|
return local == persisted ? local : persisted
|
|
}
|
|
|
|
func openSettings(tab: SettingsTab) {
|
|
AppNavigationActions.openSettings(tab: tab)
|
|
}
|
|
|
|
func handleBack() {
|
|
withAnimation {
|
|
self.currentPage = max(0, self.currentPage - 1)
|
|
}
|
|
}
|
|
|
|
func handleNext() {
|
|
guard canAdvance else { return }
|
|
let remoteDecision = Self.remoteGatewayAdvanceDecision(
|
|
connectionMode: state.connectionMode,
|
|
activePageIndex: activePageIndex,
|
|
connectionPageIndex: connectionPageIndex,
|
|
authIssue: remoteAuthIssue,
|
|
probeState: remoteProbeState,
|
|
input: remoteGatewayProbeInput)
|
|
guard remoteDecision.canAdvance else {
|
|
if remoteDecision.shouldProbe {
|
|
Task { await self.probeRemoteConnection(advanceOnSuccess: true) }
|
|
}
|
|
return
|
|
}
|
|
self.commitRecommendedConnectionIfNeeded(for: activePageIndex)
|
|
if currentPage < pageCount - 1 {
|
|
withAnimation { self.currentPage += 1 }
|
|
} else {
|
|
self.finish()
|
|
}
|
|
}
|
|
|
|
func commitRecommendedConnectionIfNeeded(for pageIndex: Int) {
|
|
if pageIndex == connectionPageIndex,
|
|
defaultsToLocalGateway,
|
|
state.connectionMode == .unconfigured
|
|
{
|
|
self.selectLocalGateway()
|
|
}
|
|
}
|
|
|
|
@discardableResult
|
|
func finish() -> Bool {
|
|
guard !finishState.didFinish else { return false }
|
|
finishState.didFinish = true
|
|
aiSetup.clearCompletedHandoffIfOwned()
|
|
OnboardingController.markComplete()
|
|
OnboardingController.shared.close()
|
|
guard state.connectionMode != .unconfigured else { return true }
|
|
// Fresh activation hands off to the dashboard's custodian onboarding, which
|
|
// owns the remaining first-run steps (memory import, channels, permissions,
|
|
// hatch). A live-verified pre-existing setup reopens the normal dashboard.
|
|
dashboardHandoffOpener(aiSetup.verifiedExistingInference ? .dashboard : .custodianOnboarding)
|
|
return true
|
|
}
|
|
}
|