Skip to content

Commit d9db8ee

Browse files
authored
Merge branch 'main' into fix/cron-watchdog-before-agent-reply-phase
2 parents 2b9fbf5 + fcedd37 commit d9db8ee

75 files changed

Lines changed: 1832 additions & 124 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/android/CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
## Unreleased
44

5-
Maintenance update for the current OpenClaw Android release.
5+
## 2026.6.9 - 2026-06-23
6+
7+
Adds settings detail panels, refreshes the Android overview controls, and routes exec approvals into the in-app inbox.
8+
9+
Improves chat acknowledgement handling, gateway pairing readiness, microphone foreground-service behavior, and release screenshot reliability.
610

711
## 2026.6.2 - 2026-06-02
812

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Adds settings detail panels, refreshes the Android overview controls, and routes exec approvals into the in-app inbox.
2+
3+
Improves chat acknowledgement handling, gateway pairing readiness, microphone foreground-service behavior, and release screenshot reliability.
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
Maintenance update for the current OpenClaw Android release.
1+
Adds settings detail panels, refreshes the Android overview controls, and routes exec approvals into the in-app inbox.
2+
3+
Improves chat acknowledgement handling, gateway pairing readiness, microphone foreground-service behavior, and release screenshot reliability.

apps/ios/Sources/Design/SettingsProTabActions.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ extension SettingsProTab {
326326
self.setupStatusText = "Tailscale is off on this device. Turn it on, then try again."
327327
return false
328328
}
329+
self.gatewayController.requestLocalNetworkAccess(reason: "settings_preflight")
329330
self.setupStatusText = "Checking gateway reachability..."
330331
let ok = await TCPProbe.probe(host: trimmed, port: port, timeoutSeconds: 3, queueLabel: "gateway.preflight")
331332
if !ok {

apps/ios/Sources/Gateway/GatewayConnectionController.swift

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ final class GatewayConnectionController {
127127
private let discovery = GatewayDiscoveryModel()
128128
private let discoveryEnabled: Bool
129129
private weak var appModel: NodeAppModel?
130+
private var localNetworkAccessRequested: Bool
131+
private var currentScenePhase: ScenePhase = .inactive
130132
private var didAutoConnect = false
131133
private var pendingServiceResolvers: [String: GatewayServiceResolver] = [:]
132134
private var pendingTrustConnect: PendingTrustConnect?
@@ -137,9 +139,14 @@ final class GatewayConnectionController {
137139
let useTLS: Bool
138140
}
139141

140-
init(appModel: NodeAppModel, startDiscovery: Bool = true) {
142+
init(
143+
appModel: NodeAppModel,
144+
startDiscovery: Bool = true,
145+
deferDiscoveryUntilLocalNetworkRequest: Bool = false)
146+
{
141147
self.discoveryEnabled = startDiscovery
142148
self.appModel = appModel
149+
self.localNetworkAccessRequested = !deferDiscoveryUntilLocalNetworkRequest
143150

144151
GatewaySettingsStore.bootstrapPersistence()
145152
let defaults = UserDefaults.standard
@@ -148,7 +155,7 @@ final class GatewayConnectionController {
148155
self.updateFromDiscovery()
149156
self.observeDiscovery()
150157

151-
if self.discoveryEnabled {
158+
if self.discoveryEnabled, self.localNetworkAccessRequested {
152159
self.discovery.start()
153160
}
154161
}
@@ -157,11 +164,29 @@ final class GatewayConnectionController {
157164
self.discovery.setDebugLoggingEnabled(enabled)
158165
}
159166

167+
func requestLocalNetworkAccess(reason: String) {
168+
guard self.discoveryEnabled else {
169+
self.discovery.stop()
170+
self.updateFromDiscovery()
171+
return
172+
}
173+
174+
self.localNetworkAccessRequested = true
175+
GatewayDiagnostics.log("local network access requested reason=\(reason)")
176+
177+
guard self.currentScenePhase != .background else { return }
178+
self.discovery.start()
179+
self.updateFromDiscovery()
180+
self.attemptAutoReconnectIfNeeded()
181+
}
182+
160183
func setScenePhase(_ phase: ScenePhase) {
184+
self.currentScenePhase = phase
161185
guard self.discoveryEnabled else {
162186
self.discovery.stop()
163187
return
164188
}
189+
guard self.localNetworkAccessRequested else { return }
165190

166191
switch phase {
167192
case .background:
@@ -181,6 +206,10 @@ final class GatewayConnectionController {
181206
self.updateFromDiscovery()
182207
return
183208
}
209+
guard self.localNetworkAccessRequested else {
210+
self.requestLocalNetworkAccess(reason: "restart_discovery")
211+
return
212+
}
184213

185214
self.discovery.stop()
186215
self.didAutoConnect = false
@@ -197,6 +226,7 @@ final class GatewayConnectionController {
197226
_ gateway: GatewayDiscoveryModel.DiscoveredGateway,
198227
forceReconnect: Bool = false) async -> String?
199228
{
229+
self.requestLocalNetworkAccess(reason: "connect_discovered_gateway")
200230
let instanceId = UserDefaults.standard.string(forKey: "node.instanceId")?
201231
.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
202232
if instanceId.isEmpty {
@@ -275,6 +305,7 @@ final class GatewayConnectionController {
275305
authOverride: ManualAuthOverride? = nil,
276306
forceReconnect: Bool = false) async
277307
{
308+
self.requestLocalNetworkAccess(reason: "connect_manual")
278309
let instanceId = GatewaySettingsStore.currentInstanceID()
279310
let token =
280311
authOverride.map(\.token) ?? GatewaySettingsStore.loadGatewayToken(instanceId: instanceId)
@@ -340,6 +371,7 @@ final class GatewayConnectionController {
340371
}
341372

342373
func connectLastKnown() async {
374+
self.requestLocalNetworkAccess(reason: "connect_last_known")
343375
guard let last = GatewaySettingsStore.loadLastGatewayConnection() else { return }
344376
switch last {
345377
case let .manual(host, port, useTLS, _):

apps/ios/Sources/Onboarding/OnboardingWizardView.swift

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,16 @@ struct OnboardingWizardView: View {
7373
private static let pairingAutoResumeTicker = Timer.publish(every: 2.0, on: .main, in: .common).autoconnect()
7474

7575
let allowSkip: Bool
76+
let onRequestLocalNetworkAccess: (String) -> Void
7677
let onClose: () -> Void
7778

78-
init(allowSkip: Bool, onClose: @escaping () -> Void) {
79+
init(
80+
allowSkip: Bool,
81+
onRequestLocalNetworkAccess: @escaping (String) -> Void,
82+
onClose: @escaping () -> Void)
83+
{
7984
self.allowSkip = allowSkip
85+
self.onRequestLocalNetworkAccess = onRequestLocalNetworkAccess
8086
self.onClose = onClose
8187
_step = State(
8288
initialValue: OnboardingStateStore.shouldPresentFirstRunIntro() ? .intro : .welcome)
@@ -231,6 +237,7 @@ struct OnboardingWizardView: View {
231237
}
232238
.onAppear {
233239
self.initializeState()
240+
self.requestLocalNetworkAccessIfPastIntro(reason: "onboarding_appear")
234241
}
235242
.onDisappear {
236243
self.discoveryRestartTask?.cancel()
@@ -864,10 +871,20 @@ extension OnboardingWizardView {
864871

865872
private func advanceFromIntro() {
866873
OnboardingStateStore.markFirstRunIntroSeen()
874+
self.requestLocalNetworkAccess(reason: "onboarding_continue")
867875
self.statusLine = "In your OpenClaw chat, run /pair qr, then scan the code here."
868876
self.step = .welcome
869877
}
870878

879+
private func requestLocalNetworkAccessIfPastIntro(reason: String) {
880+
guard self.step != .intro else { return }
881+
self.requestLocalNetworkAccess(reason: reason)
882+
}
883+
884+
private func requestLocalNetworkAccess(reason: String) {
885+
self.onRequestLocalNetworkAccess(reason)
886+
}
887+
871888
private func navigateBack() {
872889
guard let target = self.step.previous else { return }
873890
self.connectingGatewayID = nil

apps/ios/Sources/OpenClawApp.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -646,7 +646,8 @@ struct OpenClawApp: App {
646646
_gatewayController = State(
647647
initialValue: GatewayConnectionController(
648648
appModel: appModel,
649-
startDiscovery: !Self.screenshotModeEnabled))
649+
startDiscovery: !Self.screenshotModeEnabled,
650+
deferDiscoveryUntilLocalNetworkRequest: true))
650651
}
651652

652653
var body: some Scene {

apps/ios/Sources/RootTabs.swift

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ struct RootTabs: View {
683683
self.updateIdleTimer()
684684
self.updateHomeCanvasState()
685685
guard newValue == .active else { return }
686+
self.maybeRequestLocalNetworkAccess(reason: "scene_active")
686687
Task {
687688
await self.appModel.refreshGatewayOverviewIfConnected()
688689
await MainActor.run {
@@ -729,6 +730,10 @@ struct RootTabs: View {
729730
.onChange(of: self.onboardingRequestID) { _, _ in
730731
self.evaluateOnboardingPresentation(force: true)
731732
}
733+
.onChange(of: self.showOnboarding) { _, newValue in
734+
guard !newValue else { return }
735+
self.maybeRequestLocalNetworkAccess(reason: "onboarding_dismissed")
736+
}
732737
.onChange(of: self.appModel.openChatRequestID) { _, _ in
733738
self.selectSidebarDestination(.chat)
734739
}
@@ -767,6 +772,9 @@ struct RootTabs: View {
767772
.fullScreenCover(isPresented: self.$showOnboarding) {
768773
OnboardingWizardView(
769774
allowSkip: self.onboardingAllowSkip,
775+
onRequestLocalNetworkAccess: { reason in
776+
self.requestLocalNetworkAccess(reason: reason)
777+
},
770778
onClose: {
771779
self.showOnboarding = false
772780
})
@@ -1045,13 +1053,14 @@ extension RootTabs {
10451053
shouldPresentOnLaunch: OnboardingStateStore.shouldPresentOnLaunch(appModel: self.appModel))
10461054
switch route {
10471055
case .none:
1048-
break
1056+
self.maybeRequestLocalNetworkAccess(reason: "root_appear")
10491057
case .onboarding:
10501058
self.onboardingAllowSkip = true
10511059
self.showOnboarding = true
10521060
case .settings:
10531061
self.didAutoOpenSettings = true
10541062
self.selectSidebarDestination(.gateway)
1063+
self.maybeRequestLocalNetworkAccess(reason: "root_appear")
10551064
}
10561065
}
10571066

@@ -1078,6 +1087,7 @@ extension RootTabs {
10781087
guard route == .settings else { return }
10791088
self.didAutoOpenSettings = true
10801089
self.selectSidebarDestination(.gateway)
1090+
self.maybeRequestLocalNetworkAccess(reason: "auto_open_settings")
10811091
}
10821092

10831093
private func maybeOpenSettingsForGatewaySetup() {
@@ -1088,6 +1098,19 @@ extension RootTabs {
10881098
self.presentedSheet = nil
10891099
self.didAutoOpenSettings = true
10901100
self.selectSidebarDestination(.gateway)
1101+
self.requestLocalNetworkAccess(reason: "gateway_setup_deeplink")
1102+
}
1103+
1104+
private func maybeRequestLocalNetworkAccess(reason: String) {
1105+
guard self.didEvaluateOnboarding else { return }
1106+
guard self.scenePhase == .active else { return }
1107+
guard !self.showOnboarding else { return }
1108+
self.requestLocalNetworkAccess(reason: reason)
1109+
}
1110+
1111+
private func requestLocalNetworkAccess(reason: String) {
1112+
guard !self.appModel.isAppleReviewDemoModeEnabled else { return }
1113+
self.gatewayController.requestLocalNetworkAccess(reason: reason)
10911114
}
10921115

10931116
private func applyInitialChatSessionIfNeeded() {

apps/ios/Tests/RootTabsSourceGuardTests.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,7 @@ struct RootTabsSourceGuardTests {
594594
#expect(actionsSource.contains("self.gatewayController.refreshActiveGatewayRegistrationFromSettings()"))
595595
#expect(actionsSource.contains("self.gatewayController.restartDiscovery()"))
596596
#expect(actionsSource.contains("await self.appModel.refreshGatewayOverviewIfConnected()"))
597+
#expect(actionsSource.contains("self.gatewayController.requestLocalNetworkAccess(reason: \"settings_preflight\")"))
597598
#expect(actionsSource.contains("await TCPProbe.probe(host: trimmed, port: port"))
598599
#expect(actionsSource.contains("Check Tailscale or LAN."))
599600
#expect(actionsSource.contains("Tailscale is off on this device. Turn it on, then try again."))
@@ -610,6 +611,32 @@ struct RootTabsSourceGuardTests {
610611
#expect(controllerSource.contains("trustRotatedGatewayCertificate(from problem: GatewayConnectionProblem)"))
611612
}
612613

614+
@Test func `local network access is requested from visible gateway flows`() throws {
615+
let appSource = try String(contentsOf: Self.openClawAppSourceURL(), encoding: .utf8)
616+
let rootSource = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
617+
let onboardingSource = try String(contentsOf: Self.onboardingWizardSourceURL(), encoding: .utf8)
618+
let actionsSource = try String(contentsOf: Self.settingsProTabActionsSourceURL(), encoding: .utf8)
619+
let controllerSource = try String(contentsOf: Self.gatewayConnectionControllerSourceURL(), encoding: .utf8)
620+
621+
#expect(appSource.contains("deferDiscoveryUntilLocalNetworkRequest: true"))
622+
#expect(controllerSource.contains("func requestLocalNetworkAccess(reason: String)"))
623+
#expect(controllerSource.contains("guard self.localNetworkAccessRequested else"))
624+
#expect(controllerSource.contains("self.requestLocalNetworkAccess(reason: \"connect_manual\")"))
625+
#expect(controllerSource.contains("self.requestLocalNetworkAccess(reason: \"connect_discovered_gateway\")"))
626+
#expect(controllerSource.contains("self.requestLocalNetworkAccess(reason: \"connect_last_known\")"))
627+
628+
#expect(rootSource.contains("self.maybeRequestLocalNetworkAccess(reason: \"root_appear\")"))
629+
#expect(rootSource.contains("self.maybeRequestLocalNetworkAccess(reason: \"scene_active\")"))
630+
#expect(rootSource.contains("self.maybeRequestLocalNetworkAccess(reason: \"onboarding_dismissed\")"))
631+
#expect(rootSource.contains("self.requestLocalNetworkAccess(reason: \"gateway_setup_deeplink\")"))
632+
#expect(rootSource.contains("guard self.didEvaluateOnboarding else { return }"))
633+
#expect(rootSource.contains("onRequestLocalNetworkAccess: { reason in"))
634+
635+
#expect(onboardingSource.contains("self.requestLocalNetworkAccess(reason: \"onboarding_continue\")"))
636+
#expect(onboardingSource.contains("self.requestLocalNetworkAccessIfPastIntro(reason: \"onboarding_appear\")"))
637+
#expect(actionsSource.contains("self.gatewayController.requestLocalNetworkAccess(reason: \"settings_preflight\")"))
638+
}
639+
613640
@Test func `gateway settings preview matrix covers primary states`() throws {
614641
let supportSource = try String(contentsOf: Self.settingsProTabSupportSourceURL(), encoding: .utf8)
615642

@@ -800,6 +827,13 @@ struct RootTabsSourceGuardTests {
800827
.appendingPathComponent("Sources/Design/SettingsProTab.swift")
801828
}
802829

830+
private static func onboardingWizardSourceURL() -> URL {
831+
URL(fileURLWithPath: #filePath)
832+
.deletingLastPathComponent()
833+
.deletingLastPathComponent()
834+
.appendingPathComponent("Sources/Onboarding/OnboardingWizardView.swift")
835+
}
836+
803837
private static func openClawAppSourceURL() -> URL {
804838
URL(fileURLWithPath: #filePath)
805839
.deletingLastPathComponent()

docs/clawhub/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ OpenClaw agent or Gateway.
2525
openclaw skills search "calendar"
2626
openclaw skills install @owner/<slug>
2727
openclaw skills update @owner/<slug>
28-
openclaw skills verify <slug>
28+
openclaw skills verify @owner/<slug>
2929

3030
openclaw plugins search "calendar"
3131
openclaw plugins install clawhub:<package>

0 commit comments

Comments
 (0)