* feat(gateway): recover channels and health promptly after host sleep
A dependency-free thaw detector rides the existing 30s maintenance tick:
when the process resumes after being frozen >=45s beyond cadence (laptop
sleep, VM pause, SIGSTOP), the gateway restarts running channel accounts
(dead sockets otherwise take up to ~35 minutes to notice), refreshes
health/presence, and resets the event-loop histogram so the freeze does
not read as degradation. Admission is rechecked before every recovery
side effect; a suspension beginning mid-recovery re-pends the thaw, and
timed-out channel stops complete their two-call restart in one pass.
The macOS app cooperates: NSWorkspace sleep/wake observers in
GatewayConnectivityCoordinator best-effort prepare a local gateway
suspension before sleep and resume it on wake, never blocking sleep.
The lease is bound to the route that prepared it and always cleared on
wake; route or mode changes across sleep drop it to self-expiry.
Live proof: SIGSTOP 85s on an isolated dev gateway -> 'host thaw
detected: process was frozen ~57683ms', channels restarted, health ok,
eventLoop degraded=false after thaw.
* fix(macos): resume a sleep lease whose prepare response arrives after wake
A prepare completing after didWake previously discarded the lease id,
fencing the gateway until the two-minute expiry after micro-sleeps; the
late response now resumes immediately. Document the conservative
route-token drift tradeoff.
* fix(macos): retry wake resume after refreshing the dead post-sleep transport
After real sleep the WebSocket is usually dead exactly when resume runs;
refresh the endpoint first, then attempt resume up to three times with
bounded delays, clearing the lease only on success or exhaustion. A new
sleep cycle aborts in-flight retries.
* fix(gateway): bound plugin stopAccount so channel stops cannot wedge recovery
stopChannel awaited plugin stopAccount unbounded; a never-settling stop
hung the thaw restart (and health-monitor sweeps) and held the
single-flight recovery guard forever. Race it against the existing
5s stop timeout; the timed-out path flows into the established
recoveryStopTimedOut two-call restart contract. Regression wedges
pre-fix.
* refactor(gateway): move thaw channel restart off ChannelManager and fence mid-pass
restartRunningChannelAccounts is a standalone helper over the public
manager surface with a shouldContinue probe checked before every stop
and start, so a suspension committing while an account stop is awaited
leaves later accounts untouched. Regression covers the mid-pass close.
* fix(gateway): sanitize late writes from an abandoned stopAccount
An abandoned (timed-out) stopAccount can settle after its replacement
started; route its late setStatus writes through the existing
stale-task sanitizer so they cannot repaint or tear down the
replacement. Regression fails pre-fix.
* fix(macos): make the whole AI candidate row clickable and clear stale exhausted verdicts
Live-testing pick-during-testing on 2026-08-11 showed clicks on a candidate
row's blank stretch (between the title/subtitle texts or over the spacer)
silently doing nothing: plain-style buttons only hit-test opaque label
pixels. A user trying to pick Claude Code while Codex auto-tests can click
the visually highlighted row and get no outcome. .contentShape(Rectangle())
makes the full row hit-test.
Also, after auto-candidates exhaust, a user-picked retest left the stale
"None of the found options worked" card up while the new test visibly ran;
userSelect now clears exhaustedAutoCandidates when a fresh attempt begins.
* test(tooling): drop stateless poll tests from stateful-helper fixtures
Main went red when #121923 rewrote the outbound poll tests to be
order-independent and removed their stateful helper import/file; this
lane-config test hardcoded both as stateful-helper classification
fixtures. Folded into this PR per red-main landing policy.
* fix(mac): scope managed CLI install and detection to the active app profile
Under an active OPENCLAW_PROFILE the onboarding ready page detected the
host-global managed CLI (~/.openclaw/bin/openclaw) and its update/install
flow rewrote the operator's real managed copy. The managed install prefix
now follows the profile state directory (~/.openclaw-<name>), preferred
paths exclude other profiles' managed trees (including stale validated
executables and inherited shell PATH entries), and external CLIs stay
detectable read-only. Default-profile behavior is unchanged.
* chore(mac): refresh native i18n inventory for shifted source lines
* fix(macos): let AI setup proceed when the login keychain is unavailable
A missing/locked login keychain made GatewayActivationBindingKeyStore
unable to mint the activation binding key, and onboarding refused every
candidate and manual-key activation with 'Secure storage is unavailable'.
The fingerprint only protects the crash/relaunch resume receipt, so
degrade instead of refusing: write an ownerless pending record (still
matched exactly, never as a wildcard), keep the full activation-ambiguity
window, and skip only restart reconciliation, which needs fingerprint
proof by design. A relaunch then repeats activation rather than trusting
the receipt.
Reported by Peter Steinberger.
* fix(macos): render proper provider brand icons in onboarding AI setup
Nearly every provider icon URL the gateway sends is a simpleicons.org
SVG, which AsyncImage cannot decode, so the Connect your AI page fell
back to generic symbols for every tool and provider; the two GitHub
avatar PNGs that did render clashed as full-color rasters.
Bundle nine monochrome template marks (Simple Icons CC0; xAI from
LobeHub icons, MIT) and resolve them local-first by brand id/kind,
including composed choice ids like xai-oauth. Remote icons now load via
NSImage(data:), which decodes SVG, with vector payloads tinted as
templates so plugin-supplied icons blend too. Every glyph sits in a
uniform rounded well; install cards show host-only links, hover states,
and a softer translucent surface shared by all setup sections instead
of controlBackgroundColor.
Requested by Peter Steinberger.
* fix(macos): use failable UTF-8 decode in icon vector sniffing (swiftlint)
* fix(macos): address ClawSweeper review of onboarding icon/keychain changes
- Refuse relaunch handoff from ownerless (keychain-unavailable) completed
receipts at the reconciliation trust boundary; such receipts can belong
to replaced credentials, so setup repeats a fresh activation instead.
- Decode and pass the canonical brandId the gateway sends for candidates,
auth options, and manual providers so bundled marks resolve for opaque
choice ids.
- Scan the bounded XML prolog (comments, declarations, doctype) when
sniffing remote SVG payloads; comment-prefixed vectors now tint as
templates.
* refactor(macos): move setup error enum to support file (swiftlint file length)
* chore(i18n): refresh native source inventory
* fix(macos): give keychain-unavailable activations attempt-specific unbound leases
A nil owner made concurrent unbound attempts indistinguishable: a stale
attempt's delayed response could complete or clear a newer attempt's
record. Unbound attempts now mint a random per-attempt lease id with a
sentinel fingerprint — live matching stays attempt-exact, restart
reconciliation's fingerprint guard rejects them, and relaunch
verification refuses unbound receipts before any handoff.
* chore(macos): ship third-party artwork notices with provider icons
Simple Icons (CC0) attribution and the LobeHub Icons MIT license text
for the xAI mark now travel inside the bundled ProviderIcons directory.
* fix(ci): refresh plugin-sdk API manifest for private-type hashing
#120975 changed the baseline generator to hash private types but landed
without regenerating the manifest; the check-plugin-sdk-api-baseline
lane was gated off on that PR and first failed here. Regenerated via
pnpm plugin-sdk:api:gen with no SDK surface change in this branch.
Make timeout paths cancel or release every pending test continuation before cleanup, so failures cannot poison the remaining Swift suite. Inject the retry sleeper to preserve invalidation-before-backoff ordering without real-time polling.
Fresh Dev installs now preflight disk space and stream honest stages, while Codex activation probes the refreshed request-scoped registry.
Closes#120779Closes#120780
* feat(macos): add native camera PTZ controls
Add physical UVC pan, tilt, and zoom through the signed Mac app, with camera.ptz.control kept behind dangerous-command approval. Verified against real Insta360 Link 2 Pro hardware.
* refactor(agents): split message tool display config
* fix(mac): harden camera PTZ contracts