feat(windows): add native WinUI 3 companion app [AI-assisted]#54588
feat(windows): add native WinUI 3 companion app [AI-assisted]#54588AlexAlves87 wants to merge 33 commits intoopenclaw:mainfrom
Conversation
C# 13 / .NET 10 / WinUI 3 / Windows App SDK 1.8 implementation of the OpenClaw node with 1:1 functional parity with the macOS Swift app. Features: gateway WebSocket + ed25519 auth, system tray, canvas (WebView2), camera.snap/clip, screen.record, system.run + exec approvals, Talk Mode STT/TTS, onboarding wizard, autostart, deep links, pairing, cron, skills. Architecture: Hexagonal (Ports & Adapters), MVVM, MediatR, MSIX x64 + ARM64. AI-assisted Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…, and keypair Cover previously untested security-critical components: - ExecAllowlistMatcher: glob wildcard semantics (* vs **), case insensitivity, backslash normalization, invalid pattern skipping, MatchAll all-or-nothing - ExecShellWrapperParser: POSIX shells / cmd / PowerShell detection, recursive env unwrapping, MaxWrapperDepth, -- separator, unknown-flag fail-safe - HostEnvSanitizer: PATH + 25 blocked override keys, DYLD_/LD_/BASH_FUNC_ host env stripping, shell wrapper mode restriction, case insensitivity - ExecCommandResolution: $()/ backtick/<()/>() fail-closed, single-quoted literal safe, unclosed quotes, chain splitting (;/&&/|/\n), empty segment rejection, tilde expansion, cwd-relative resolution - DeepLinkParser: scheme validation, non-TLS remote host rejection, setup code parsing, IsLoopbackHost, GatewayConnectDeepLink.FromSetupCode edge cases - Ed25519KeyPair: generate, storage round-trip, corrupt blob rejection, DeviceId/PublicKeyBase64Url encoding, Sign/VerifySignature - DpapiKeypairStorageAdapter: DPAPI save/load round-trip, corrupt blob → error 228 new tests, suite total 2047 passing. 🤖 Generated with Claude Code (claude-sonnet-4-6) Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
|
Too many files changed for review. ( |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b87afda777
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayReceiveLoopHostedService.cs
Outdated
Show resolved
Hide resolved
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
… before buffering
- GatewayReceiveLoopHostedService: SendConnectRequestAsync now reads
GatewayPassword() when no token is present and sends auth: { password }
so password-auth gateways can accept the connection (P1 Codex review)
- GatewayCliClient.ReceiveFrameAsync: copy each received chunk into a new
array before buffering; the reused receive buffer was overwritten on
subsequent ReceiveAsync calls, corrupting fragmented frames (P2 Codex review)
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e01ae75f30
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/node_mode/WindowsNodeModeCoordinator.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/gateway/GatewayReceiveLoopHostedService.cs
Outdated
Show resolved
Hide resolved
- WindowsNodeModeCoordinator: same password-auth fallback applied to the node connect handshake (P1 Codex review on second pass) - GatewayReceiveLoopHostedService: remove diagnostic log that emitted the serialized connect frame, which includes token/password material (P2 security finding Codex review) Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b83a42d0af
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayReceiveLoopHostedService.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/node_mode/WindowsNodeModeCoordinator.cs
Outdated
Show resolved
Hide resolved
…p, csproj AnyCPU default - OpenClawConfigFile.GatewayPassword() now reads ~/.openclaw/openclaw.json → gateway.auth.password first (mirrors ReadGatewayAuthToken), then falls back to gateway.remote.password — local password-auth gateways were silently broken - GatewayReceiveLoopHostedService: call MarkDisconnected when socket drops from Connected state, not only from Connecting/Reconnecting — coordinator can now schedule a retry after an unexpected disconnection - WindowsNodeModeCoordinator: remove diagnostic log that leaked connect frame (including auth material) at INFO level - OpenClawWindows.csproj: default Platform to x64 when AnyCPU/unset so that bare "dotnet build/test" without -p:Platform works; ARM64 explicit builds unaffected Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9b4683d734
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/settings/JsonSettingsRepositoryAdapter.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/settings/JsonSettingsRepositoryAdapter.cs
Outdated
Show resolved
Hide resolved
…fallback and URI scheme Auth centralization (Codex P1/P2): - GatewayReceiveLoopHostedService and WindowsNodeModeCoordinator now resolve token/password from IGatewayEndpointStore.CurrentState instead of reading OpenClawConfigFile directly, picking up env-var overrides and local/remote precedence that the store already implements correctly - GatewayEndpointStore.ResolveConfigToken returns null when gateway.auth.mode=password so callers naturally fall through to the password credential (Codex P2) JsonSettingsRepositoryAdapter fixes (Codex GitHub bot P1 x2): - LoadAsync in Remote mode now falls back to local file when gateway is offline instead of returning DefaultSettings(), preserving persisted mode/endpoint so the reconnect coordinator can continue retrying - InjectTokenIntoUri preserves the original URI scheme (ws/wss) instead of hardcoding ws://, preventing silent TLS downgrade for wss endpoints Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 301c07610b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayReconnectCoordinatorHostedService.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/settings/JsonSettingsRepositoryAdapter.cs
Outdated
Show resolved
Hide resolved
…sts to temp dir - GatewayReceiveLoopHostedService and WindowsNodeModeCoordinator now call RefreshAsync before reading CurrentState so a token/password rotation or auth.mode change is picked up on the next reconnect without requiring restart - DpapiKeypairStorageAdapter gains an internal test constructor accepting an explicit storage path so the test suite never touches the real keypair.dpapi in %APPDATA%\OpenClaw — a crash or aborted run can no longer leave the developer's device unpaired - DpapiKeypairStorageAdapterTests updated to use an isolated temp directory Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fc16f9d477
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/exec_approvals/NamedPipeExecApprovalAdapter.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/exec_approvals/NamedPipeExecApprovalAdapter.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/exec_approvals/NamedPipeExecApprovalAdapter.cs
Outdated
Show resolved
Hide resolved
- NamedPipeExecApprovalAdapter: accept both `type` and `messageType` discriminators; unwrap `approved` from `payloadJson` envelope; loop to ensure full 4-byte length prefix is read - GatewayReceiveLoopHostedService / WindowsNodeModeCoordinator: use RequireConfigAsync() instead of RefreshAsync()+CurrentState so SSH remote mode has credentials before sending the connect request; restore GatewayEndpointUri user-info token fallback for installs that embed the token in the URI - GatewayReceiveLoopTests: update mock to RequireConfigAsync pattern - windows-release.yml / windows-security.yml: fix oxfmt YAML format Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1367e325a6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayEndpointStore.cs
Outdated
Show resolved
Hide resolved
…rtup reconnect - Encrypt RemoteToken/RemotePassword with DPAPI in settings.json (no plaintext) - Restore credentials from local file after gateway config.get (not in gateway schema) - Save locally before gateway write so credentials survive gateway failures - DeepLinkHandler: persist RemoteTransport.Direct, token and password from deep link - GatewayRemoteConfig: add HasRemoteSection to distinguish absent vs present-but-no-transport - GatewayEndpointStore.SetModeAsync: transport fallback uses SSH when remote section present but no explicit transport; settings.RemoteTransport only when no remote section at all - GatewayEndpointStore.ResolveEffectiveMode: only AppSettings.Remote overrides openclaw.json; Local does not downgrade an operator-configured remote mode; adds openclaw.json fallback - GatewayConnectivityCoordinator.StartAsync: always call RefreshAsync so deep-link or UI-configured gateways reconnect after restart regardless of openclaw.json initial mode - Fix flaky TalkModeControllerTests: replace Task.Delay with TCS for fire-and-forget sync Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8280427d3f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayEndpointStore.cs
Outdated
Show resolved
Hide resolved
- KickRemoteEnsureIfNeeded: use settings.RemoteTarget (SSH host/user) instead of RemoteUrl (WebSocket URL, used for direct transport only) - GatewayUriNormalizer: use UriComponents.Port instead of IsDefaultPort to detect explicit port — IsDefaultPort is true for both absent and explicit :80 in .NET, causing valid ws://localhost:80 to be rewritten Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
FIX-A: CanvasNavigateHandler — whitelist http/https only; reject file://,
javascript:, data:, ftp:// from gateway-initiated canvas.navigate.
FIX-B: JsonExecApprovalsRepository — DPAPI-encrypt IPC token at rest;
auto-migrate plaintext on next save (zero-downtime).
FIX-C: FileSystemDeepLinkKeyStore — DPAPI-encrypt persisted deeplink key;
add SemaphoreSlim + volatile for thread-safe double-check locking.
FIX-D: NamedPipeExecApprovalAdapter — nonce cache (10 s TTL) to reject
replayed HMAC-authenticated IPC requests.
FIX-E: GatewayTlsPinStore — mark _pins volatile to prevent stale-read
in EnsureLoadedAsync without holding the lock.
FIX-G: DeepLinkHandler — replace DateTime _lastPromptAt with long +
Interlocked.Read/Exchange (volatile is invalid for DateTime struct).
FIX-H: CanvasSchemeHandlerAdapter — 10 MB file-size guard before
File.ReadAllBytes to prevent OOM on malicious large files.
Tests: +6 CanvasNavigateHandlerTests covering all invalid schemes.
All 2058 tests green (1 pre-existing skip).
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
The comment said "displayCommand is always the full argv so the user sees the complete command (security: no hidden shell wrapper)" but the code returned `inferred` (the extracted shell payload) when mustBindToFullArgv was false. Change to always return fullArgvDisplay. Fixes CI failures: - "env wrapper accepts canonical full argv raw command" - "shell wrapper accepts shell payload raw command" - "env wrapper shell payload accepted at ingress" Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 74e4a7ec0b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/SshRemoteTunnelService.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/exec_approvals/GatewayExecApprovalOrchestrator.cs
Outdated
Show resolved
Hide resolved
…ispatch P1 — IRemoteTunnelService.ConnectAsync gains an explicit remotePort parameter so the SSH -L forward uses the gateway port read from config (gateway.remote.url or settings.RemoteUrl) instead of the hardcoded DefaultRemotePort=18789. GatewayEndpointStore.KickRemoteEnsureIfNeeded derives the remote port via ResolveRemoteGatewayPort, which checks the config URL first, then the settings URL, then falls back to GatewayEnvironment.GatewayPort(). P2 — GatewayExecApprovalOrchestrator.ShowDialogOnUiThreadAsync captures the TryEnqueue return value; if the dispatcher queue is unavailable or full the TCS is resolved immediately as Deny so the gate semaphore is never permanently blocked. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8019d9a08a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/application/usecases/gateway/ApplyConnectionModeHandler.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/pairing/DevicePairingApprovalOrchestrator.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/pairing/NodePairingApprovalOrchestrator.cs
Outdated
Show resolved
Hide resolved
…ring TCS P1 — ApplyConnectionModeHandler now derives the remote gateway port from settings.RemoteUrl before calling ConnectAsync, so SSH mode tunnels to the correct port (e.g. 443 for wss://) instead of always using 18789. ResolveRemotePort parses the explicit port, falls back to the scheme default (wss → 443, ws → 18789). P2 — DevicePairingApprovalOrchestrator and NodePairingApprovalOrchestrator now capture the TryEnqueue return value; if the dispatcher queue is unavailable or full, the TCS is resolved immediately as PairingDecision.Later so PresentNextLoopAsync is never permanently blocked and subsequent pairing requests continue to be processed. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eaad666e3b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayReconnectCoordinatorHostedService.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 92ce5696f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/application/usecases/gateway/ResumeGatewayConnectionHandler.cs
Show resolved
Hide resolved
apps/windows/src/infrastructure/pairing/DevicePairingApprovalOrchestrator.cs
Show resolved
Hide resolved
…olve CF-015 (P1): ResumeGatewayConnectionHandler never cleared the paused state set by PauseGatewayConnectionHandler. Added GatewayConnection.MarkResumed() (Paused→ Connected) and inject GatewayConnection + ISettingsRepository into the resume handler; clears IsPaused=false in settings and transitions the state machine so the reconnect coordinator and WindowsNodeModeCoordinator stop gating on Paused. CF-016 (P2): DevicePairingApprovalOrchestrator Approve/Reject branches removed the request from _queue but did not call NotifyPendingChanged(). SystemTrayViewModel watches IDevicePairingPendingMonitor.Changed for badge updates, so the pending count stayed stale until an unrelated push event fired. Added NotifyPendingChanged() immediately after each RemoveAll in the Approve and Reject cases. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 75825f06ed
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayReceiveLoopHostedService.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/application/usecases/node_mode/BrowserProxyCommand.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/application/usecases/node_mode/BrowserProxyCommand.cs
Outdated
Show resolved
Hide resolved
…ttribute check CF-017 (P1): ApplyHelloOk looked for snapshot.sessiondefaults (all lowercase) but gateway sends sessionDefaults (camelCase), so mainSessionKey was always silently dropped and fell back to "main". CF-018 (P1): BrowserProxyCommand hardcoded port 18789 for the control endpoint. Now calls ResolveGatewayPort() which reads OPENCLAW_GATEWAY_PORT env var then openclaw.json gateway.port, matching GatewayEnvironment.GatewayPort() behaviour. CF-019 (P2): LoadProxyFiles filtered with HasFlag(Normal | Archive); Normal is exclusive and never combined with other flags on Windows, so regular files (typically Archive) were rejected. Now rejects only FileAttributes.Directory. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ab9937892f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/application/usecases/gateway/ApplyConnectionModeHandler.cs
Show resolved
Hide resolved
apps/windows/src/infrastructure/gateway/GatewayReconnectCoordinatorHostedService.cs
Show resolved
Hide resolved
… on remote load CF-020 (P2): OpenChatAsync catch block wrote to C:\temp\openchat_error.txt; that directory rarely exists so the recovery path itself threw DirectoryNotFoundException. Replaced with Debug.WriteLine (best-effort, never throws). CF-021 (P2): LoadAsync in remote mode merged RemoteToken/RemotePassword from local JSON but dropped IsPaused, so pause-on-restart was silently ignored when config.get succeeded. Now also copies IsPaused from local file. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
… honour IsPaused on reconnect CF-022: GatewayConnectDeepLink.IsLoopbackHost no longer treats .local mDNS hosts as loopback. ws:// deep links to *.local are rejected at parse time; GatewayUriNormalizer rejects them downstream anyway, so accepting them created a broken saved config. CF-023: ApplyConnectionModeHandler now injects IGatewayProcessManager and calls SetActive(true) when switching to Local mode (unless paused), and SetActive(false) in Remote/Unconfigured branches. Previously the Local branch never started the gateway process, leaving the app reconnecting to ws://127.0.0.1:18789 with nothing listening until a manual restart. CF-024: GatewayReconnectCoordinatorHostedService.ResolveEndpointAsync now returns null when settings.IsPaused is true. On app restart the in-memory state is Disconnected (not Paused), so the Paused state guard was ineffective; the persisted IsPaused flag is now the authoritative source for post-restart pause durability. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: da0ecf6c16
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/settings/JsonSettingsRepositoryAdapter.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/gateway/GatewayReconnectCoordinatorHostedService.cs
Outdated
Show resolved
Hide resolved
…overflow CF-025: Remove the catch block from SaveToGatewayAsync so that config.get/config.set failures (disconnect, auth error, hash conflict) propagate to SaveAsync callers. Previously the catch swallowed every exception and returned success, silently losing remote settings writes while the caller believed the save succeeded. CF-026: Use long arithmetic in ComputeBackoffMs to prevent int overflow. With int arithmetic, BaseDelayMs * (1 << attempt) overflows to a negative value after enough retries, collapsing the capped backoff to the 500ms floor and causing a rapid reconnect storm during prolonged outages. Caps the shift at 30 and uses long intermediates so the result is always a non-negative value clamped by MaxDelayMs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: baa1618c45
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/application/usecases/exec_approvals/EvaluateExecRequestHandler.cs
Outdated
Show resolved
Hide resolved
CF-027: EvaluateExecRequestHandler now reads both the env field (used by the gateway's node-invoke-system-run-approval.ts allowlist) and envOverrides (compat alias). Both are merged — envOverrides wins on conflict — so environment variables injected by agents are no longer silently dropped before policy evaluation and execution. CF-028: RemoteTunnelManager.EnsureControlTunnelAsync now tracks the active tunnelEndpoint and remotePort and only reuses the existing tunnel when both still match. Previously any connected tunnel was reused regardless of configuration, causing traffic to stay pinned to the old gateway after a host or port change until a manual restart. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 30f0fd3b68
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/application/usecases/exec_approvals/EvaluateExecRequestHandler.cs
Outdated
Show resolved
Hide resolved
apps/windows/src/infrastructure/pairing/NodePairingApprovalOrchestrator.cs
Show resolved
Hide resolved
apps/windows/src/infrastructure/pairing/DevicePairingApprovalOrchestrator.cs
Show resolved
Hide resolved
…equeues CF-029: IShellExecutor.RunAsync now accepts optional cwd and env parameters. ShellExecutorAdapter applies them to ProcessStartInfo (WorkingDirectory + Environment). EvaluateExecRequestHandler passes the parsed cwd and sanitized env to RunAsync so approved system.run commands run in the requested directory with the expected environment instead of the app's default process context. CF-030: NodePairingApprovalOrchestrator now calls NotifyPendingChanged() after every dequeue in the present loop — Approve, Reject and Later branches. Previously all three removed from _queue without notifying the monitor, leaving stale node-pair badge counts until an unrelated pairing event triggered a refresh. CF-031: DevicePairingApprovalOrchestrator now calls NotifyPendingChanged() when a remote resolution removes a request from _queue. Previously only the local Approve/Reject branches notified; remote resolutions left stale device-pair badge counts. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b8dd678de8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
apps/windows/src/infrastructure/gateway/GatewayEndpointStore.cs
Outdated
Show resolved
Hide resolved
…unnel endpoint
CF-032: GatewayRpcChannelAdapter.ExecApprovalResolveAsync sent { requestId, decision }
but the gateway schema expects { id, decision }. Every UI approval/deny failed server-side,
leaving approvals permanently pending.
CF-033: GatewayEndpointStore.KickRemoteEnsureIfNeeded built sshEndpoint from RemoteTarget
only, dropping RemoteIdentity. Remote setups using a non-default SSH identity file now
correctly compose the endpoint as identity@target, matching all other SSH connection paths.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
… resume; fix flaky test CF-052: ResolveSshLocalUri now checks gateway.remote.url from openclaw.json before settings.RemoteUrl so wss:// remotes keep TLS through the SSH tunnel when the URL is only configured in the config file (not in the UI settings). CF-053: ResolveRemotePort applies the same config-first precedence so the SSH tunnel forwards to the correct port (443 for wss://) instead of always defaulting to 18789. CF-054: GatewayEndpointStore no longer falls back to stale settings credentials when gateway.remote section exists in openclaw.json but has no auth fields — config is authoritative when the section is present, even if both credentials are absent. CF-055: ResumeGatewayConnectionHandler now calls SaveLocalAsync instead of SaveAsync to clear the persisted IsPaused flag. SaveAsync calls the gateway config.get/config.set RPC which fails while the socket is still disconnected (paused); SaveLocalAsync writes only to the local JSON file, which is correct since IsPaused has no gateway config schema counterpart. Timing: CoalescingFileSystemWatcherTests multiplier 4x→10x to prevent false failure under heavy parallel test load (Task.Delay can fire late when CPU is saturated). Security: log Console.Error warning when DPAPI is unavailable and credentials fall back to plaintext storage, so admins are aware in CI/headless environments. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: efe050436f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var configBytes = JsonSerializer.SerializeToUtf8Bytes(configEl); | ||
| var dto = JsonSerializer.Deserialize<AppSettingsDto>(configBytes, JsonOptions); | ||
| return dto is null ? null : MapFromDto(dto); |
There was a problem hiding this comment.
Avoid deserializing gateway config as AppSettingsDto
In remote mode, LoadAsync calls TryLoadFromGatewayAsync, which parses config.get's config payload into AppSettingsDto; that payload is the OpenClaw gateway config schema, not the Windows app-settings schema, so this produces mostly default values (including ConnectionMode.Unconfigured). Returning that object means downstream flows that call LoadAsync and then SaveAsync (for example pause/resume/settings updates) can persist a defaulted settings file and lose the user’s remote configuration, which then breaks subsequent reconnect behavior.
Useful? React with 👍 / 👎.
… mode switch; fix port precedence CF-056: TryLoadFromGatewayAsync no longer deserializes the config.get payload as AppSettingsDto. The gateway config uses the OpenClaw schema (agents/auth/commands/ gateway), not the AppSettings schema — deserialization produced all-defaults including ConnectionMode.Unconfigured, which caused subsequent SaveAsync calls to persist a defaulted settings file and wipe the user's remote configuration. The method now only caches the hash for conflict detection; settings are always loaded from the local file. CF-057: ApplyConnectionModeHandler no longer passes settings.IsPaused to GatewayAutostartPolicy when switching to Local mode. IsPaused is remote-only; a paused remote session was suppressing the local gateway start and leaving the app disconnected until the user manually hit resume. CF-058: ResolveRemotePort now tries settings.RemoteUrl before openclaw.json. When invoked from SaveSettingsHandler the user's freshly saved URL must win over a stale config-file entry. Config is the fallback for setups where the URL lives only in openclaw.json and settings.RemoteUrl is empty. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc0bf7f507
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| // Gateway v2026.3.13 enforces origin check for control-ui clients. | ||
| // Local loopback origin passes checkBrowserOrigin when isLocalClient=true. | ||
| _ws.Options.SetRequestHeader("Origin", $"http://{connectUri.Host}:{connectUri.Port}"); |
There was a problem hiding this comment.
Use HTTPS Origin for wss control-channel connections
ConnectAsync always sends Origin: http://... even when the socket URI is wss://.... The gateway origin check compares full origins (including scheme), so TLS deployments that allow https://<host> will reject this handshake because http://<host> does not match, causing remote secure control-channel connects to fail. Build the Origin header from the WebSocket scheme (wss -> https, ws -> http) before connecting.
Useful? React with 👍 / 👎.
| if (settings.ConnectionMode == ConnectionMode.Remote) | ||
| { |
There was a problem hiding this comment.
Respect config-backed remote mode before starting local gateway
SetActive suppresses local process startup only when settings.ConnectionMode == Remote, but startup always calls SetActive(true). In a config-only remote setup (remote set in openclaw.json before the app has persisted settings), ConnectionMode is still Unconfigured, so this path starts a local gateway and can subsequently flip persisted mode to local. This breaks remote-first installs and should resolve effective mode from config + settings before deciding to spawn locally.
Useful? React with 👍 / 👎.
…only remote mode CF-059: GatewayWebSocketAdapter now builds the Origin header from the WebSocket scheme (wss → https, ws → http). The previous hardcoded http:// caused TLS deployments to reject the handshake because the gateway origin check compares the full origin including scheme, so https://<host> != http://<host>. CF-060: WindowsGatewayProcessManager.SetActive now checks GatewayRemoteConfig.HasRemoteSection in addition to settings.ConnectionMode == Remote. In config-only remote setups (gateway.remote present in openclaw.json but ConnectionMode not yet persisted), the previous check missed the remote mode, started a local gateway, and AutoResolveConnectionModeAsync subsequently flipped ConnectionMode to Local, breaking remote-first installs. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
…etection CF-056: TryLoadFromGatewayAsync no longer deserializes gateway config into AppSettings (wrong schema); only caches the config hash to detect changes. CF-057: ApplyConnectionModeHandler passes paused:false for Local mode — IsPaused is remote-only and must not suppress local gateway autostart. CF-061: GatewayUriNormalizer port detection uses Authority.EndsWith instead of UriComponents.Port, which returns scheme default (80) for ws://. CF-062: GatewayConnectDeepLink applies the same port fix for deep-link URIs. CF-063/071/072: DeepLinkHandler redacts sensitive query params before logging or displaying URLs; preserves credentials only when same host, clears when different host to avoid credential forwarding to unrelated gateways. CF-064: ResolveSshLocalUri always returns ws://localhost:18789 — SSH tunnel is a TCP forward; using wss://localhost fails cert validation. CF-065: PauseGatewayConnectionHandler uses SaveLocalAsync (not SaveAsync) so the pause flag is written locally when the gateway RPC is unavailable. CF-066: WindowsNodeModeCoordinator ResolveSshLocalUri mirrors CF-064. CF-067: FileSystemDeepLinkKeyStore falls back to plaintext UTF-8 on CryptographicException to migrate pre-DPAPI keys without data loss. CF-068: WindowsGatewayProcessManager reverts CF-060 — HasRemoteSection in SetActive incorrectly suppressed local gateway in explicit Local mode. CF-069: GatewayEndpointStore.ResolveRemoteGatewayPort: settings-first for SSH port (user-configured URL wins), config as fallback. CF-070: TailscalePassword encrypted with DPAPI in JsonSettingsRepositoryAdapter (was stored plaintext, inconsistent with other credential fields). CF-073: ReconnectCoordinator mode detection adds config-backed remote fallback (GatewayRemoteConfig.HasRemoteSection) for config-only installs. CF-074: ApplyConnectionModeHandler.ResolveRemotePort reverted to config-first precedence — openclaw.json is authoritative for deployment config. CF-075: ReconnectCoordinator transport resolution mirrors GatewayEndpointStore: config explicit → SSH default when section present → settings fallback. Fixes config-only direct installs where settings.RemoteTransport defaults to Ssh, causing the coordinator to try tunnel reconnect instead of direct. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c260c48bf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var sameHost = !string.IsNullOrEmpty(settings.RemoteUrl) | ||
| && Uri.TryCreate(settings.RemoteUrl, UriKind.Absolute, out var existing) | ||
| && string.Equals(existing.Host, link.Host, StringComparison.OrdinalIgnoreCase); |
There was a problem hiding this comment.
Compare previous host before deciding to preserve credentials
HandleGatewayAsync computes sameHost from settings.RemoteUrl after settings.SetRemoteUrl(url) has already replaced it with the incoming link target, so sameHost is effectively always true for valid links. When a deep link points to a different host without token/password, stale credentials are incorrectly retained and can be sent to the new gateway host on the next connect. Capture/compare the old URL host before overwriting RemoteUrl.
Useful? React with 👍 / 👎.
| if (s.ConnectionMode == ConnectionMode.Unconfigured) | ||
| { | ||
| s.SetConnectionMode(ConnectionMode.Local); | ||
| s.SetOnboardingSeen(true); |
There was a problem hiding this comment.
Don’t force Local mode from unconfigured hello-ok sessions
This block rewrites any ConnectionMode.Unconfigured state to ConnectionMode.Local immediately after a successful handshake. That breaks valid remote setups that rely on legacy RemoteUrl/config-driven mode while persisted mode is still unconfigured: once this runs, later reconnects/restarts resolve as local and can no longer reach the remote gateway.
Useful? React with 👍 / 👎.
| <ToggleSwitch | ||
| Header="OpenClaw Active" | ||
| IsOn="{Binding IsActive, Mode=TwoWay}" | ||
| OnContent="Active" | ||
| OffContent="Paused" /> |
There was a problem hiding this comment.
Hook Active toggle to pause/resume behavior
The General Settings “OpenClaw Active” switch is only two-way bound to IsActive; it is not wired to ToggleActiveCommand, and SaveAsync does not persist IsPaused. As a result, changing this toggle in the UI does not actually pause/resume the gateway and the state is lost on reload.
Useful? React with 👍 / 👎.
Windows PLATFORM_DEFAULTS only had SYSTEM_COMMANDS, blocking all canvas.* invocations from the gateway before they reached the node. Added CANVAS_COMMANDS to match macOS parity. Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This reverts commit 44b3e57.
NodeInvokeDispatcher was using FirstError.Description which is always
the ErrorOr default "A failure has occurred." for single-arg Error.Failure()
calls. Now uses "{Code}: {Description}" so both the machine-readable code
(e.g. "FFMPEG_NO_OUTPUT", "Video mux error: ...") and any human-readable
description are visible in logs and gateway responses.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 898751c34c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| var target = settings.RemoteTarget?.Trim() ?? ""; | ||
| var identity = settings.RemoteIdentity?.Trim(); | ||
| var sshEndpoint = !string.IsNullOrEmpty(identity) ? $"{identity}@{target}" : target; | ||
| var desiredPort = GatewayEnvironment.GatewayPort(); |
There was a problem hiding this comment.
Keep SSH tunnel local port consistent with reconnect client
Use a fixed local forward port here instead of GatewayEnvironment.GatewayPort(). The reconnect and node coordinators both hardcode ws://localhost:18789 for SSH sessions, so if OPENCLAW_GATEWAY_PORT (or equivalent config) is non-default, this code can bring up the tunnel on a different local port while all reconnect attempts still target 18789. In that state _tunnel.IsConnected remains true, so retries keep failing against the wrong port and never recover.
Useful? React with 👍 / 👎.
| lock (_lock) { _queue.RemoveAll(r => r.RequestId == req.RequestId); } | ||
| await NotifyAsync(remoteDecision, req, "remote", ct); |
There was a problem hiding this comment.
Emit pending-change event after remote node dequeue
This remote-resolution path removes the request from _queue but does not call NotifyPendingChanged(). The tray badge/UI refresh depends on INodePairingPendingMonitor.Changed, so when a pairing request is resolved remotely while its dialog is open, the pending count can remain stale until another unrelated queue mutation occurs.
Useful? React with 👍 / 👎.
| var mode = settings.ConnectionMode != ConnectionMode.Unconfigured | ||
| ? settings.ConnectionMode | ||
| : !string.IsNullOrWhiteSpace(settings.RemoteUrl) | ||
| ? ConnectionMode.Remote | ||
| : settings.OnboardingSeen |
There was a problem hiding this comment.
Honor config remote mode when resolving node endpoint
This mode resolver only inspects AppSettings and never checks config-backed remote settings. In remote-first deployments where openclaw.json defines remote mode but local settings are still Unconfigured with empty RemoteUrl, it returns no endpoint and the node socket never starts, even though other gateway paths can connect via config-aware resolution.
Useful? React with 👍 / 👎.
Windows part of #75
Native WinUI 3 system tray app that connects to the OpenClaw gateway via WebSocket, exposing device capabilities to AI agents — mirroring the macOS companion app.
Stack: C# 13 / .NET 10 / Windows App SDK 1.8 / MSIX (x64 + ARM64)
Issue criteria
Tests
2047 passing, 0 failures. Unit + integration + property-based (FsCheck) + architecture (NetArchTest).
Security-critical components have dedicated test suites:
ExecAllowlistMatcher— glob semantics (*vs**), case insensitivity, backslash normalization, MatchAll all-or-nothingExecShellWrapperParser— all shell types, recursive env unwrapping, MaxWrapperDepth,--separatorHostEnvSanitizer— PATH + 25 blocked override keys, DYLD_/LD_ host stripping, shell wrapper modeExecCommandResolution—$()/ backtick /<()fail-closed, chain splitting, tilde expansionDeepLinkParser— scheme validation, non-TLS remote host rejection, setup code edge casesEd25519KeyPair— key generation, storage round-trip, corrupt blob rejection, Sign/VerifyDpapiKeypairStorageAdapter— DPAPI save/load round-trip, corrupt file → error (no throw)Manually verified
MSIX install, tray icon, context menu, onboarding wizard, gateway connection (hello-ok), real-time chat streaming, canvas A2UI, settings, quit.
Not yet verified end-to-end on real hardware
Camera, screen capture, notifications, location, voice wake, Talk Mode STT, deep links (E2E activation). Code and tests exist but I haven't validated all of these against live hardware yet. This is my immediate next block of work after this PR opens.
Screenshots
Onboarding
Tray menu · Settings
Canvas · Chat
AI-assisted development
This was built with AI-assisted development (Claude Code), with manual review and verification throughout.
I directed:
Prompts, session logs, and design artifacts available on request if useful for review.
Maintenance commitment
I'm committed to maintaining this code post-merge:
Out of scope (documented in KNOWN_ISSUES.md)
Signing
MSIX signed with self-signed certificate (CN=OpenClaw). Production signing should be handled by the maintainer.