ensureExtensionDepsResolvable called symlinkSync without a type argument
and without path normalization. On Windows without Developer Mode or
admin rights, plain symlinkSync throws EPERM; the failure was silently
swallowed, leaving extension-owned packages unresolvable from shared
dist/ chunks and breaking gateway startup.
Extract the link logic into electron/gateway/fs-link.ts:
- linkDirSafe prefers junction on Windows (works without elevation),
falls back to a plain dir symlink only if junction creation fails
(e.g. cross-volume).
- normalizeFsPath centralizes the \\?\ extended-length + UNC prefixing
that was previously an inline helper in config-sync.ts.
Also drop the now-redundant inline fsPath helper in config-sync.ts and
replace the two bare symlinkSync calls with linkDirSafe.
Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
The fallback exists as a safety net for the server-side gateway.ready
event. In practice OpenClaw's plugin bootstrap can push the real event
well past 30s (observed: handshake completes, then 30s tick by, then the
fallback fires with no event having arrived). That long tail kept the
stale gating code blocking UI state for the full 30s.
Step 1 moved sessions.list off the gatewayReady gate, so this value now
only matters as a belt-and-braces signal for any future consumer. 5s is
long enough to preserve "event wins when it actually fires on a healthy
boot" while avoiding a multi-second stall whenever the server is slow.
Updated gateway-ready-fallback.test.ts to advance timers around the new
boundary.
The maybeLoadSessions() guard previously waited for status.gatewayReady
to become true, which is driven by the server-side gateway.ready event
and backed by a 30s fallback timer in GatewayManager. In practice,
OpenClaw's plugin bootstrap often exceeds that window, so the fallback
fired and users stared at the loading state for ~30s after the WS
handshake had already completed.
sessions.list is a plain RPC — it needs the handshake to be done, not
plugins to be up. Gate it on state === 'running' so the session list is
fetched immediately after handshake completion. Existing throttling via
LOAD_SESSIONS_MIN_INTERVAL_MS still prevents spam on state flaps.