Skip to content

feat(windows): add native WinUI 3 companion app [AI-assisted]#54588

Closed
AlexAlves87 wants to merge 33 commits intoopenclaw:mainfrom
AlexAlves87:feat/windows-app
Closed

feat(windows): add native WinUI 3 companion app [AI-assisted]#54588
AlexAlves87 wants to merge 33 commits intoopenclaw:mainfrom
AlexAlves87:feat/windows-app

Conversation

@AlexAlves87
Copy link
Copy Markdown

@AlexAlves87 AlexAlves87 commented Mar 25, 2026

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

Criterion Status Evidence
Install, launch, onboarding MSIX installs, tray appears, onboarding wizard works. Screenshots below
Gateway reliable + survives reboot Health polling, auto-reconnect (Polly), Task Scheduler autostart
Connectivity verified in CI ci-x64 and ci-arm64 workflows green
Feature parity documented PARITY.md — full macOS vs Windows table
CI artifacts x64 + ARM64 MSIX uploaded as workflow artifacts
Docs: install/update/uninstall QUICKSTART.md + DEPLOYMENT.md + ROLLBACK.md
Update flow MSIX reinstall verified

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-nothing
  • ExecShellWrapperParser — all shell types, recursive env unwrapping, MaxWrapperDepth, -- separator
  • HostEnvSanitizer — PATH + 25 blocked override keys, DYLD_/LD_ host stripping, shell wrapper mode
  • ExecCommandResolution$() / backtick / <() fail-closed, chain splitting, tilde expansion
  • DeepLinkParser — scheme validation, non-TLS remote host rejection, setup code edge cases
  • Ed25519KeyPair — key generation, storage round-trip, corrupt blob rejection, Sign/Verify
  • DpapiKeypairStorageAdapter — 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

Welcome Gateway All set

Tray menu · Settings

Tray Settings

Canvas · Chat

Canvas Chat

AI-assisted development

This was built with AI-assisted development (Claude Code), with manual review and verification throughout.

I directed:

  • the scope and architecture decisions
  • manual verification of core flows
  • keeping the test suite green at every step

Prompts, session logs, and design artifacts available on request if useful for review.

Maintenance commitment

I'm committed to maintaining this code post-merge:

  • Immediate: end-to-end verification of camera, screen capture, notifications, Talk Mode STT, and deep link activation on real hardware
  • Review feedback: I'll address review comments and fix issues as they come up
  • Follow-up PRs:
    • Channel login/logout OAuth flow
    • Session submenu actions
    • Voice wake hotword integration (Porcupine)
  • CI: ready to adapt workflows to the monorepo CI structure if needed

Out of scope (documented in KNOWN_ISSUES.md)

  • Voice wake hotword — stub only; full Porcupine integration pending
  • Channel login/logout — read-only, OAuth pending
  • Session submenus — list present, no actions yet
  • Auto-update — MSIX reinstall; no Sparkle equivalent yet

Signing

MSIX signed with self-signed certificate (CN=OpenClaw). Production signing should be handled by the maintainer.

AlexAlves87 and others added 2 commits March 25, 2026 15:09
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]>
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 25, 2026

Too many files changed for review. (789 files found, 100 file limit)

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

@github-advanced-security
Copy link
Copy Markdown

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:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

- 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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

- 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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

- 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]>
AlexAlves87 and others added 2 commits March 26, 2026 05:50
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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

… 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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

AlexAlves87 and others added 2 commits March 27, 2026 21:28
…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +137 to +139
var configBytes = JsonSerializer.SerializeToUtf8Bytes(configEl);
var dto = JsonSerializer.Deserialize<AppSettingsDto>(configBytes, JsonOptions);
return dto is null ? null : MapFromDto(dto);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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}");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +89 to +90
if (settings.ConnectionMode == ConnectionMode.Remote)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

AlexAlves87 and others added 2 commits March 28, 2026 11:51
…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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +147 to +149
var sameHost = !string.IsNullOrEmpty(settings.RemoteUrl)
&& Uri.TryCreate(settings.RemoteUrl, UriKind.Absolute, out var existing)
&& string.Equals(existing.Host, link.Host, StringComparison.OrdinalIgnoreCase);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +341 to +344
if (s.ConnectionMode == ConnectionMode.Unconfigured)
{
s.SetConnectionMode(ConnectionMode.Local);
s.SetOnboardingSeen(true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +15 to +19
<ToggleSwitch
Header="OpenClaw Active"
IsOn="{Binding IsActive, Mode=TwoWay}"
OnContent="Active"
OffContent="Paused" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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]>
@openclaw-barnacle openclaw-barnacle bot added the gateway Gateway runtime label Mar 28, 2026
@openclaw-barnacle openclaw-barnacle bot removed the gateway Gateway runtime label Mar 28, 2026
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]>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +429 to +430
lock (_lock) { _queue.RemoveAll(r => r.RequestId == req.RequestId); }
await NotifyAsync(remoteDecision, req, "remote", ct);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +643 to +647
var mode = settings.ConnectionMode != ConnectionMode.Unconfigured
? settings.ConnectionMode
: !string.IsNullOrWhiteSpace(settings.RemoteUrl)
? ConnectionMode.Remote
: settings.OnboardingSeen
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants