openclaw-windows-node/DEVELOPMENT.md
Régis Brid f2fa038bd0
Companion application refactoring (#272)
* feat: unified Hub window with NavigationView, slim tray menu, and inline toggles

Consolidate 8 separate windows into a single Hub app:
- New HubWindow with NavigationView (Chat, Home, Activity, Settings pages)
- Chat page embeds gateway Control UI via WebView2 (default landing page)
- Home page with live status cards, quick actions, and activity feed
- Settings page with Expander sections, Test Connection, SSH tunnel fields
- Activity page with category filters and live ActivityStreamService binding
- Slim tray menu: status + 3 inline toggles + Hub/QuickSend + Settings/Exit
- Acrylic backdrop on tray flyout, auto-collapsing nav, page transitions
- Deep links (openclaw://) redirected to Hub pages
- Deleted 5 old windows: StatusDetail, ActivityStream, NotificationHistory, WebChat, Settings
- WebView2 event handler cleanup on page Unloaded (code review fix)
- Deferred page init to avoid null Settings during Frame.Navigate

25 files changed, 1350 insertions(+), 2033 deletions(-) — net code reduction

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: settings validation, CSS sidebar hiding, toggle labels, and SSH tunnel support

- Settings save validates gateway URL and SSH tunnel fields before saving
- Test Connection supports SSH tunnel mode (starts temp tunnel for test)
- SSH toggle auto-updates gateway URL field (shows loopback in tunnel mode)
- Chat page injects CSS to hide web Control UI sidebar (no dual navigation)
- Tray toggle switches hide On/Off labels to prevent clipping

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat: full native UI with 12 pages, config editor, dual connection, and live gateway data

New pages: Sessions, Channels, Usage, Nodes, Cron, Skills, Config, About
- Sessions: live session list with reset/delete/compact actions
- Channels: channel health cards with start/stop controls
- Usage: cost breakdown, provider stats, daily costs
- Nodes: node inventory with capabilities and device ID copy
- Cron: scheduled jobs with run/remove actions (gateway wired)
- Skills: installed skills status (gateway wired)
- Config: TreeView + detail panel with editable values via config.set protocol
- About: version info, debug tools, documentation links

Infrastructure:
- Dual WebSocket connection: operator client (UI data) + node client (commands)
- 14 new gateway methods: cron.*, skills.*, config.* with JsonElement.Clone()
- Data caching in HubWindow for instant page navigation
- Session startedAt parsing fix (handles number + string timestamps)
- Hub window synced after settings reconnect
- Tray test updated for Auto scrollbar

Build: 0 errors | Tests: 774 passing (652 shared + 122 tray)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* UX Round 2: Agent events, discovery, pairing, models, presence, menu redesign, timer removal

Features:
- Agent Events page with stream filters and 400-event ring buffer
- Gateway discovery via mDNS (Zeroconf) with scan UI in General page
- Node/Device pairing UI with approve/reject in Nodes page
- Models list in Sessions page via models.list gateway method
- Instances page rewired to presence data from handshake snapshot
- Context cards in tray menu (session summary + token usage bars)
- Pairing pending count in tray menu

Menu & UX:
- Merged status + toggle into rich header card with status dot
- Permission toggles section (browser, camera, exec, canvas, screen)
- Renamed hub to Windows Companion with smart disconnect navigation
- Custom title bar with live connection status + gateway version
- Single-click tray -> chat, double-click -> hub
- Chat window pre-warmed on startup, hides instead of closing

Architecture:
- Removed all background timers (10s poll + 30s health check)
- On-demand data loading only (pages fetch on navigation)
- Fixed ParseSessions to merge instead of clear-then-rebuild (no flicker)
- Fixed HandleAgentEvent sessionKey parsing (was reading from root, not payload)
- Symmetric subscribe/unsubscribe for all new gateway events
- Caches cleared on disconnect, seeded into HubWindow on open

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Restructure navigation: agents as hierarchical nav items under Gateway

- Replace flat Agent section with hierarchical Agents > {agentId} > {sub-pages} structure
- Move Conversations shortcut to top level, pointing to SessionsPage
- Remove AgentSelectorNav ComboBox, replaced with dynamic nav tree (RebuildAgentNavItems)
- Add FindAndSelectNavItem for recursive nested nav item selection
- Add agent: tag parsing (ResolveAgentPageType, ParseAgentIdFromTag)
- Update NavigateTo with legacy flat tag mapping to new agent: prefix format
- Strip Zone B (Agent Roster) and Zone C (Capability Toggles) from HomePage
- Remove Node Mode toggle from SettingsPage (lives in CapabilitiesPage)
- Update command palette to use agent-scoped tags
- NavigateToDefault now goes to Home instead of Conversations

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* UX Round 3: Hearth redesign, hierarchical nav, command palette, agent APIs

Navigation restructure:
- 16 flat pages → domain-grouped hierarchical nav (Gateway, This Computer)
- Each agent gets expandable nav item with Sessions, Events, Skills, Workspace
- Dynamic agent nav built from agents.list gateway response
- Nodes nested under Instances (superset relationship)
- Cron moved to gateway section (gateway-wide, not per-agent)
- Connection page extracted from Settings into Gateway section
- Settings simplified to local-only (startup, notifications)

New pages:
- ConnectionPage — gateway URL, token, SSH tunnel, discovery, status
- CapabilitiesPage — device toggles + node status indicator
- WorkspacePage — TabView per-file viewer via agents.files.list/get
- BindingsPage — routing rules viewer (channel→agent)
- ConversationsPage — cross-agent session browser (legacy)

Command palette (Ctrl+K / Ctrl+F):
- Inline overlay with light dismiss (click outside, Escape, Enter)
- 20+ navigation + 5 toggle + dynamic session commands
- Fuzzy substring filtering

Home page (The Hearth):
- Molty status indicator with colored ring
- Natural language status text
- Quick action buttons

Agent-scoped data:
- Sessions, Skills pass agentId to gateway calls
- Agent Events filtered by sessionKey prefix
- Workspace scoped to current agent in hierarchy

Real gateway APIs:
- agents.list → dynamic nav + agent roster
- agents.files.list/get → workspace file viewer
- Cached agents list for hub seeding on open

Architecture:
- Canvas window styled with Mica + custom title bar
- Open Canvas menu item uses NodeService.ShowCanvasWindow()

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(tray): add rich session tooltips and connected devices section

- Add rich ToolTip to each session label showing model, provider,
  channel, thinking level, token breakdown, context window, status,
  and age
- Add Connected Devices section between context summary and Permissions
  with online indicator dots, platform badges, and rich tooltips
- Show connected client count from presence data in status header subtitle

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Redesign session and device tray menu with rich compact cards

Replace plain text session/device rows with structured Grid cards featuring:
- Status dot (green/amber/gray) + name + model/platform badge + chevron
- Token usage with percentage and color-coded progress bar
- Channel badges and capability icons for devices
- Section headers with right-aligned summary stats

Add AddFlyoutCustomItem() to TrayMenuWindow for custom UIElement
flyout items with hover-to-show and click-to-navigate behavior.

Build detailed side flyout panels with headers, token breakdowns,
capability listings, and session metadata.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* UX Round 4: Chat popup, rich tray menu, schema config editor

Chat panel:
- Tray-anchored borderless popup (bottom-right, DPI-aware)
- WS_EX_TOOLWINDOW + no caption/frame (hidden from taskbar)
- Auto-hide on deactivate, instant show (no animation — WebView2 incompatible)
- Single-click toggle, double-click opens Hub (400ms detection)
- Chat window recreated on settings change (stale URL fix)

Tray menu:
- Rich 2-line session cards: status dot, model badge, token progress bar
- Rich device cards: capability emoji strip, platform badge
- Flyout detail panels: non-interactive TextBlocks (not menu items)
- Session flyout: model/provider, channel, ASCII token bar, thinking/verbose
- Device flyout: capabilities merged with commands (cap as header, cmds indented)
- Dynamic capability toggles under local device (from node.Capabilities)
- Flyout dismisses on any menu item hover (including separators/headers/toggles)
- Section headers with aggregates (sessions/tokens, online/caps)
- AddFlyoutCustomItem + AddToggleItem indent support

Schema config editor:
- SchemaConfigEditor UserControl renders from JSON Schema
- Supports string/number/boolean/enum/array/nested objects
- Sensitive field detection (PasswordBox)
- Fallback RenderConfigDirectly when schema unavailable
- Config detail panel uses schema for selected tree node
- Pending changes preserved on save failure

Command palette:
- Rebuilt as inline overlay Grid (light dismiss: Escape + click-outside)
- Ctrl+F added as alternate shortcut
- TextBox replaces AutoSuggestBox (proper Escape handling)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* UX Round 5: Connection dashboard, tray polish, review fixes

Connection Management:
- Redesigned ConnectionPage with 6-section card layout: status card with
  live gateway info, gateway discovery picker with mDNS scan, setup code
  paste (from openclaw qr), manual connection expander, device identity
  card with pairing status and copyable approval command, connection log
- Auto-discovery when disconnected or unconfigured
- Setup code applies both bootstrapToken and Token for immediate connect
- PreferredGatewayId persisted in settings

Tray Menu Polish:
- Compact 3-column ToggleButton grid for capability toggles (all 7 shown)
- Split header subtitle into 2 lines (connection details + node status)
- Auth failure warning shown inline
- Reconnect and Connection quick actions
- Dropped 'Open' prefix from action items, added bottom padding

Hub Window:
- Removed XAML KeyboardAccelerators (caused tooltip flicker on hover)
- Replaced with PreviewKeyDown handler for Ctrl+K/F
- Added ReconnectAction, LastGatewaySelf, node state properties

Connection Lifecycle Fixes (from 3-model rubber-duck review):
- Capability toggles now use ReconnectNodeServiceOnly() instead of full
  teardown — no longer kills gateway client or chat window
- Reconnect action uses lightweight ReconnectGateway() (preserves chat)
- SyncHubNodeState() pushes live pairing/identity to hub on every
  node status and pairing change
- Gateway matching uses host:port comparison (not full URL with scheme)
- Discovery service disposed on page Unloaded
- Connection log refreshes on every status change
- SanitizeUrl guards against port -1
- Null-conditional restored on _hub?.RaiseSettingsSaved()
- Synthesized current gateway entry doesn't mutate cached list

Other:
- Instant single-click chat toggle (removed double-click debounce)
- Catch-all ShowHub(action) for menu nav tags
- SSH tunnel section flattened (removed redundant nested expander)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* UX Round 6: Token bar ProgressBar, title bar search, MCP toggle

Session flyout:
- Replaced ASCII token bar (█░) with WinUI ProgressBar (green/orange/red)
- Built flyout as native UIElement (StackPanel) instead of text items
- Added AddFlyoutCustomItem(UIElement, UIElement, action) overload

Title bar search:
- Replaced hidden command palette overlay with AutoSuggestBox in title bar
- Standard Windows pattern, always visible, Ctrl+K/F focuses it
- Lobster icon 14px → 20px, title shortened to 'OpenClaw'
- Removed overlay XAML, smoke layer, and palette methods

MCP server toggle:
- Added Local MCP Server card on Capabilities page
- Toggle, endpoint URL display, Copy Token/URL buttons
- Shows token readiness status

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix session flyout to match device flyout style

Replaced custom UIElement session flyout panel with simple
TrayMenuFlyoutItem list matching the device flyout pattern.
ProgressBar stays in the main menu session card only.
Removed unused AddFlyoutCustomItem(UIElement, UIElement) overload
and ShowCascadingFlyoutElement helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* UX Round 7: Nav restructure, App MCP, config editor, review fixes

Navigation:
- Sessions, Agent Events, Skills promoted to top-level with agent filter
- Agents folded to one level (direct nav → Workspace)
- Instances merged into Nodes with Connected Clients section
- Title restored to 'OpenClaw Windows Companion'
- Title bar: 48px height, responsive search (Ctrl+E), lobster 20px

Config page:
- Schema-driven tree (objects only, no leaf nodes)
- Editor + Raw JSON tabs
- config.patch sends { raw, baseHash } (hash from config.get response)
- Subtitle shows actual config file path from gateway
- All expanders open by default

App MCP capability (10 tools):
- app.navigate/status/sessions/agents/nodes/config.get
- app.settings.get/set with security allowlist (no secrets)
- app.menu/search for tray and command palette testing
- All handlers return structured data (not stringified JSON)
- Sessions filter by session key prefix (not channel)

Bug fixes:
- AgentEventsPage: init NRE guard, filter applies to display
- CapabilitiesPage: MCP toggle suppress during init
- SessionsPage: removed unused agent filter
- Config save: proper baseHash from gateway hash field

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* 5-model adversarial review fixes + regression tests

Fixes from Opus 4.7, Sonnet 4.5, GPT-5.4, GPT-5.2, Opus 4.6 reviews:
- Guard IndexOutOfRangeException on empty session Status
- Fix TCS hang when DispatcherQueue unavailable in app.navigate
- Static s_emptyObject replacing leaked JsonDocument in config tree
- Always prune stale sessions (removed incomingKeys.Count > 0 guard)
- try/catch/finally on 6 async void handlers (Channels, Sessions, Config)
- Seed ALL cached data before NavigateToDefault in ShowHub
- Move CurrentStatus/_cachedCommands inside DispatcherQueue in UpdateStatus
- Raw JSON tab uses 'parsed' not wrapper object
- Null-safe Subtitle in search handler
- Invalidate command cache on agent switch
- Dispose SemaphoreSlim in GatewayDiscoveryService

Regression tests (9 new):
- AppCapabilityTests: category, commands, CanHandle, handlers, errors
- SessionInfo empty Status guard
- ParseSessions empty array clears sessions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Tray menu: header redesign, dismiss fix, session reliability

Header:
- Split into brand header (🦞 OpenClaw) + Gateway section
- Gateway section: status dot, version/host:port, node status, labeled
  ToggleButton ('Connected'/'Disconnected') with tooltip
- Gateway info clickable → opens Connection page
- Menu dismisses after connect/disconnect toggle (avoids stale header)

Dismiss:
- Unified 150ms delayed foreground check for all deactivation cases
- Checks this window, flyout child, and owner parent before dismissing
- Fixes: click-away dismisses everything, hover between items doesn't
- Set _isShown=true in ShowAtCursor (was missing, broke dismiss guard)

Sessions:
- Removed connection status gate — show cached sessions always
- Zero gateway requests on menu open (health check was clearing sessions
  via ParseSessions in the response)
- Session cards click → 'sessions' top-level route

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Connection UX: localhost probe, auth errors, token prompt, gateway switching

Discovery:
- Localhost probe enumerates listening TCP ports via GetActiveTcpListeners
- Probes for gateway HTML signature (<title>OpenClaw Control</title>)
- Excludes MCP port (8765) to avoid false positives
- Runs in parallel with mDNS, results merged

Connection page:
- Auth error InfoBar with contextual guidance (token/pairing/password/signature)
- HubWindow.LastAuthError forwarded from OnAuthenticationFailed
- Cleared on successful connect and new connection attempts
- Token prompt always shows when switching gateways (pre-fills current token)
- Cancel button on token prompt
- Discovery list refreshes after connecting to show ✓

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Prepare UX experiments branch for PR

Fix gateway discovery host resolution, harden branch-introduced security paths, complete localization coverage, remove newly introduced dead code, refresh documentation, wire functional UX flows, and stabilize the Config page rendering path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix MCP-only tray startup

Initialize the local node service when MCP mode is enabled even if gateway node mode is disabled, so MCP-only tray launches start the HTTP server used by integration tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Ranjesh Jaganathan <ranjeshj@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-04 20:48:24 -07:00

26 KiB

Development Guide

A comprehensive guide for building, running, and contributing to the OpenClaw Windows Hub.

Table of Contents

Prerequisites

Required

  • .NET 10 SDK - Download here
  • Windows 10/11 - WinUI 3 and Windows App SDK require Windows 10 version 1903 or later
  • WebView2 Runtime - Usually pre-installed on Windows 10+ (Manual download)
  • Visual Studio 2022 (optional) - For easier development and debugging with WinUI 3 designer support

For Testing

  • A running OpenClaw gateway instance - The gateway provides the backend for chat, sessions, and notifications
    • Default gateway URL: ws://localhost:18789
    • You'll need a valid authentication token from your OpenClaw instance

For PowerToys Extension Development

  • PowerToys (latest version) - Required for testing the Command Palette extension

Project Structure

This monorepo contains three projects:

openclaw-windows-hub/
├── src/
│   ├── OpenClaw.Shared/              # Shared gateway client library
│   │   ├── OpenClawGatewayClient.cs  # WebSocket client for gateway protocol
│   │   ├── Models.cs                 # Data models (SessionInfo, ChannelHealth, etc.)
│   │   └── IOpenClawLogger.cs        # Logging interface
│   │
│   ├── OpenClaw.Tray.WinUI/          # WinUI 3 system tray application (primary)
│   │   ├── App.xaml.cs               # Main application, tray icon, gateway connection
│   │   ├── Services/                 # Settings, logging, hotkeys, deep links
│   │   ├── Windows/                  # UI windows (Settings, WebChat, Status, etc.)
│   │   ├── Dialogs/                  # Modal dialogs
│   │   └── Helpers/                  # Icon generation, utilities
│   │
│   └── OpenClaw.CommandPalette/      # PowerToys Command Palette extension
│       ├── OpenClaw.cs               # Extension entry point
│       ├── OpenClawCommandsProvider.cs  # Command provider implementation
│       └── Pages/                    # XAML pages for command results
│
├── tests/
│   ├── OpenClaw.Shared.Tests/        # Unit tests for shared library
│   └── OpenClaw.Tray.Tests/          # Tests for tray helpers (menu, settings, deep links)
│
├── tools/
│   ├── cmdpal-dev.ps1                # Helper script for Command Palette development
│   └── icongen/                      # Icon generation tool
│
├── .github/workflows/
│   └── ci.yml                        # GitHub Actions CI/CD workflow
│
├── openclaw-windows-node.slnx        # Solution file
├── README.md                         # User-facing documentation
└── DEVELOPMENT.md                    # This file

Project Dependencies

OpenClaw.Tray.WinUI  ──depends on──▶  OpenClaw.Shared
OpenClaw.CommandPalette  ──depends on──▶  OpenClaw.Shared
OpenClaw.Shared.Tests  ──tests──▶  OpenClaw.Shared
OpenClaw.Tray.Tests  ──tests──▶  OpenClaw.Shared

Key Subsystems

Subsystem Location Purpose
Gateway Communication OpenClaw.Shared/OpenClawGatewayClient.cs WebSocket client with protocol v3, reconnect/backoff logic
Notification System OpenClaw.Tray.WinUI/App.xaml.cs Event routing, toast notifications, classification
WebView2 Integration OpenClaw.Tray.WinUI/Windows/ChatWindow.xaml.cs Embedded chat panel with lifecycle management
Tray Icon Management OpenClaw.Tray.WinUI/Helpers/IconHelper.cs GDI handle management, dynamic icon generation
Session Tracking OpenClaw.Shared/OpenClawGatewayClient.cs Session state, activity tracking, polling
Settings & Logging OpenClaw.Tray.WinUI/Services/ JSON settings persistence, file rotation logging

Building

Build the Entire Solution

From the repository root:

dotnet restore
dotnet build

This builds all projects (Shared library, Tray app, Command Palette extension).

Build Individual Projects

Shared Library:

dotnet build src/OpenClaw.Shared

Tray App (WinUI):

dotnet build src/OpenClaw.Tray.WinUI

Command Palette Extension:

dotnet build src/OpenClaw.CommandPalette -p:Platform=x64

Note: Command Palette requires explicit platform (x64 or arm64).

Platform and Architecture Notes

x64 vs ARM64

The solution supports both Intel/AMD (x64) and ARM (arm64) architectures:

  • Tray App: Can be built for either architecture

    dotnet build src/OpenClaw.Tray.WinUI -r win-x64
    dotnet build src/OpenClaw.Tray.WinUI -r win-arm64
    
  • Command Palette: Must match your system architecture

    # On x64 systems:
    dotnet build src/OpenClaw.CommandPalette -p:Platform=x64
    
    # On ARM64 systems:
    dotnet build src/OpenClaw.CommandPalette -p:Platform=arm64
    

⚠️ Important for ARM64 Users: Both the Command Palette extension AND the Tray app must be built for ARM64 architecture for WebView2 and deep links to work correctly. Running an x64 build on ARM64 will cause errors.

Cross-Platform Building

The Shared library is cross-platform and can be built on Windows, Linux, or macOS:

cd src/OpenClaw.Shared
dotnet build

The WinUI Tray app and Command Palette are Windows-only but can be built on Linux using:

dotnet build -p:EnableWindowsTargeting=true

Running in Debug Mode

Visual Studio

  1. Open openclaw-windows-node.slnx in Visual Studio 2022
  2. Set OpenClaw.Tray.WinUI as the startup project
  3. Press F5 to run with debugging

Command Line

dotnet run --project src/OpenClaw.Tray.WinUI

For verbose output:

dotnet run --project src/OpenClaw.Tray.WinUI -c Debug

Command Palette Development

Use the provided helper script for rapid iteration:

.\tools\cmdpal-dev.ps1 cycle

This script:

  1. Removes the currently installed extension
  2. Builds the extension for your platform
  3. Deploys it via Add-AppxPackage -Register
  4. Reminds you to run "Reload" in Command Palette

Manual steps:

# Build
dotnet build src/OpenClaw.CommandPalette -p:Platform=x64

# Deploy (development mode, no MSIX needed)
$manifest = "src/OpenClaw.CommandPalette/bin/x64/Debug/net10.0-windows10.0.26100.0/win-x64/AppxManifest.xml"
Add-AppxPackage -Register $manifest -ForceApplicationShutdown

# Test: Open PowerToys Command Palette (Win+Alt+Space), type "Reload", then "OpenClaw"

Publishing (Self-Contained)

For distribution:

dotnet publish src/OpenClaw.Tray.WinUI -c Release -r win-x64 --self-contained -o publish

This creates a standalone executable with all dependencies bundled.

Architecture Overview

Gateway WebSocket Connection

The OpenClawGatewayClient manages the connection to the OpenClaw gateway:

Connection Flow:

  1. WebSocket connects to gateway URL (default: ws://localhost:18789)
  2. Client waits for challenge event from gateway
  3. Client responds with authentication token
  4. Gateway sends connected event confirming authentication
  5. Client begins receiving events and can send requests

Reconnect & Backoff Logic:

  • Automatic reconnection on disconnect or error
  • Exponential backoff: 1s, 2s, 4s, 8s, 15s, 30s, 60s (max)
  • Resets backoff counter on successful connection
  • Connection state exposed via StatusChanged event

Implementation:

// Backoff sequence in milliseconds
private static readonly int[] BackoffMs = { 1000, 2000, 4000, 8000, 15000, 30000, 60000 };

Event Parsing and Notification Types

The gateway sends structured events over WebSocket. The client parses these into typed notifications:

Event Types

Event Type Handler Description UI Result
challenge Initial handshake Gateway requests authentication Client sends token
connected Authentication success Gateway confirms connection Status → Connected
agent (stream=job) HandleJobEvent Job/task activity Activity indicator, tray badge
agent (stream=tool) HandleToolEvent Tool execution (exec, read, write, etc.) Activity with tool name + args
chat HandleChatEvent Assistant chat messages Toast notification for short messages
health ParseChannelHealth Channel health status Channel status in tray menu
session HandleSessionEvent Session list updates Session display refresh
usage ParseUsage Token usage, cost, requests Usage info in status window

Notification Classification

Notifications are classified using two strategies:

  1. Structured (preferred): Events with explicit type, category, or notificationType fields
  2. Text-based (fallback): Keyword matching on notification content

Categories:

  • health - Blood sugar, glucose, CGM readings
  • urgent - Critical alerts requiring immediate attention
  • reminder - Calendar reminders, tasks
  • stock - Stock price alerts
  • email - Email notifications
  • calendar - Calendar events
  • error - Error messages
  • build - CI/CD build status
  • info - General information (default)

Routing:

  • Notifications trigger Windows toast notifications (if enabled in settings)
  • Stored in notification history for later review
  • Can be filtered by category

WebView2 Lifecycle

The ChatWindow uses Microsoft Edge WebView2 for embedded web content:

Initialization:

  1. WebView2 control created in XAML
  2. CoreWebView2 environment initialized on window load
  3. User data folder: %LOCALAPPDATA%\OpenClawTray\WebView2
  4. Navigation guard prevents external navigation

Lifecycle:

Window Created → WebView2.EnsureCoreWebView2Async() → Navigate to Chat URL → User Interaction → Window Hidden (not disposed)

Key Design Decisions:

  • Singleton pattern: Only one chat window instance exists
  • Hidden instead of disposed: Window is hidden when closed to preserve state
  • Separate user data folder: Isolates cookies/storage from browser
  • Navigation guard: Prevents accidental navigation away from chat

Implementation:

// Initialize WebView2 environment
await WebView.EnsureCoreWebView2Async();
WebView.CoreWebView2.Navigate(chatUrl);

// Navigation guard
WebView.CoreWebView2.NavigationStarting += (s, e) => {
    if (!e.Uri.StartsWith(allowedHost)) {
        e.Cancel = true;
    }
};

GDI Handle Management

The tray icon system uses GDI handles for icon creation. Proper management prevents handle leaks:

Icon Creation Pattern:

// Create bitmap
using var bitmap = new Bitmap(16, 16);
using var graphics = Graphics.FromImage(bitmap);
graphics.DrawSomething(...);

// Convert to icon (creates GDI handle)
var hIcon = bitmap.GetHicon();
var icon = Icon.FromHandle(hIcon);

// Clone to own the data
var result = (Icon)icon.Clone();

// CRITICAL: Destroy the GDI handle
DestroyIcon(hIcon);

return result;

Why This Matters:

  • GDI handles are a limited system resource (10,000 per process on Windows)
  • Not calling DestroyIcon() causes handle leaks
  • Each tray icon update could leak a handle without proper cleanup
  • The pattern: Create → Clone → Destroy ensures we own the icon data and release the GDI handle

Caching: Icons are cached to avoid repeated GDI operations:

private static Icon? _connectedIcon;
private static Icon? _disconnectedIcon;
// ... etc

Session Tracking and Polling

The client tracks active agent sessions with intelligent display logic:

Session State:

  • Main session: Primary user conversation
  • Sub-sessions: Background tasks, tool executions
  • Each session has: key, status, model, channel, activity

Polling:

  • RequestSessionsAsync() called periodically (every 5 seconds when connected)
  • Gateway responds with session list
  • Client updates internal _sessions dictionary

Display Selection Algorithm:

  1. Active main session always takes priority
  2. Currently displayed session kept if still active (prevents flipping)
  3. Falls back to most recently active sub-session
  4. 3-second debounce prevents jitter during rapid changes

Why This Matters: Without stable selection, the activity display would rapidly flip between sessions during concurrent operations, creating a poor user experience.

Logging

File-based logging with automatic rotation:

Log File:

  • Location: %LOCALAPPDATA%\OpenClawTray\openclaw-tray.log
  • Rotation: When log exceeds 5MB, old log → openclaw-tray.log.old
  • Thread-safe: Uses lock for concurrent writes

Log Levels:

  • INFO - Normal operation (connections, events)
  • WARN - Recoverable issues (reconnects, timeouts)
  • ERROR - Failures (connection errors, exceptions)
  • DEBUG - Detailed diagnostics (only in DEBUG builds)

Format:

[2026-02-01 12:34:56.789] [INFO] Gateway connected, waiting for challenge...
[2026-02-01 12:34:57.123] [WARN] Reconnecting with 2000ms backoff...
[2026-02-01 12:34:58.456] [ERROR] Connection failed: Host not found

Debug Output: In DEBUG builds, logs are also written to Visual Studio Output window via System.Diagnostics.Debug.WriteLine().

Security: Sensitive data (authentication tokens) are never logged.

Testing

Running Unit Tests

Two test projects cover the shared library and tray helpers:

# Run all tests
dotnet test

# Run with detailed output
dotnet test --verbosity detailed

# Run specific test class
dotnet test --filter "FullyQualifiedName~AgentActivityTests"

Test Coverage:

  • 1182 tests in OpenClaw.Shared.Tests — models, gateway client, exec approvals, capabilities, URL helpers, notification categorization, shell quoting, MCP, device identity, and WinNode client coverage
  • 388 tests in OpenClaw.Tray.Tests — settings round-trip, deep link parsing, onboarding state, setup code decoder, gateway health/chat helpers, security validation, wizard step parsing, gateway discovery, localization validation
  • All tests are pure unit tests (no network, no file system, no external dependencies)

See tests/OpenClaw.Shared.Tests/README.md for detailed test documentation.

Manual Testing Without Live Gateway

You can test the UI and basic functionality without a running gateway:

Tray App:

  1. Launch the app: dotnet run --project src/OpenClaw.Tray.WinUI
  2. Right-click tray icon → Settings
  3. Enter a dummy gateway URL (e.g., ws://localhost:18789)
  4. The app will show "Disconnected" status but you can:
    • Test the tray menu structure
    • Open the Settings page and configure preferences
    • Test auto-start functionality
    • View logs

Command Palette:

  1. Deploy the extension: .\tools\cmdpal-dev.ps1 deploy
  2. Open PowerToys Command Palette (Win+Alt+Space)
  3. Type "OpenClaw"
  4. Commands will show but most require a connected gateway to function

Manual Test Scenarios

Tray Icon States

  1. Disconnected (Gray):

    • Start app without gateway running
    • Verify icon is gray
    • Verify tooltip shows "Disconnected"
  2. Connecting (Amber):

    • Configure valid gateway URL but don't start gateway yet
    • Restart app
    • Briefly observe amber icon during connection attempt
  3. Connected (Green):

    • Start gateway
    • Verify icon turns green
    • Verify tooltip shows "Connected"
  4. Error (Red):

    • Connect to gateway, then stop gateway
    • Verify icon turns red after timeout
  5. Activity Badge:

    • Connect to gateway
    • Send a chat message that triggers tool use
    • Verify small colored dot appears on tray icon during tool execution

Notifications

  1. Toast Notifications:

    • Connect to gateway
    • Send a message that triggers a chat response
    • Verify Windows toast notification appears (if enabled)
    • Click toast → should open relevant UI
  2. Activity / notification history:

    • Right-click tray → Activity Stream or Notification History
    • Verify past notifications are listed
    • Test filtering by category
  3. Notification Settings:

    • Settings → Disable notifications
    • Send a chat message
    • Verify no toast appears (but history still records it)

WebChat Panel

  1. Open WebChat:

    • Right-click tray → Open Web Chat
    • Verify window opens with WebView2 content
    • Test sending a message
  2. Window State Persistence:

    • Move/resize WebChat window
    • Close and reopen
    • Verify position/size restored (future feature)
  3. WebView2 Fallback:

    • Test on system without WebView2 Runtime
    • Verify graceful fallback (opens browser instead)

CI/CD

GitHub Actions Workflow

The repository uses GitHub Actions for continuous integration and release automation.

Workflow File: .github/workflows/ci.yml

Trigger Events:

  • Push to main or master branch
  • Pull requests to main or master
  • Git tags matching v* (e.g., v1.2.3) for releases

Build Matrix

The CI builds multiple configurations:

Test Job:

  • Runs on windows-latest
  • Builds Shared library, Tray app (WinUI), Tests (Shared + Tray)
  • Runs unit tests: dotnet test tests/OpenClaw.Shared.Tests and dotnet test tests/OpenClaw.Tray.Tests
  • Uses GitVersion for semantic versioning

Build Job (Tray):

  • Matrix: win-x64, win-arm64
  • Builds WinUI Tray app for both architectures
  • Publishes self-contained executables
  • Signs with Azure Trusted Signing (on tag releases only)

Build Job (Command Palette):

  • Matrix: x64, arm64
  • Builds Command Palette extension for both platforms
  • Produces MSIX packages for deployment

Artifacts

On every build, the following artifacts are uploaded:

Artifact Contents Purpose
openclaw-tray-win-x64 x64 Tray app binaries Testing, distribution
openclaw-tray-win-arm64 ARM64 Tray app binaries Testing, distribution
openclaw-commandpalette-x64 x64 Command Palette MSIX Testing, distribution
openclaw-commandpalette-arm64 ARM64 Command Palette MSIX Testing, distribution

Release Process

When a tag is pushed (e.g., git tag v1.2.3 && git push origin v1.2.3):

  1. Build & Sign:

    • All artifacts built for x64 and ARM64
    • Executables signed with Azure Trusted Signing certificate
  2. Create Installers:

    • Inno Setup creates Windows installers
    • Includes both Tray app and Command Palette extension
    • Separate installers for x64 and ARM64
  3. GitHub Release:

    • Automatic release created with tag name
    • Includes:
      • Installers: OpenClawTray-Setup-x64.exe, OpenClawTray-Setup-arm64.exe
      • Portable ZIPs: OpenClawTray-{version}-win-x64.zip, OpenClawTray-{version}-win-arm64.zip
    • Release notes auto-generated from commits

Monitoring CI

Check Latest Build:

gh run list --repo shanselman/openclaw-windows-hub --limit 5

View Specific Run:

gh run view <run-id> --repo shanselman/openclaw-windows-hub

Download Artifacts:

gh run download <run-id> --repo shanselman/openclaw-windows-hub

What CI Checks

Build Success:

  • All projects compile without errors
  • Both x64 and ARM64 builds succeed
  • Dependencies restore correctly

Unit Tests:

  • All tests pass
  • No test failures or skips

Code Signing:

  • Executables signed (on releases)
  • Signature verification passes

Not Currently Checked:

  • Linting/code style (no linter configured)
  • Integration tests (no integration test suite)
  • Code coverage metrics (no coverage reporting)

Contributing

Development Workflow

  1. Fork and Clone:

    git clone https://github.com/YOUR_USERNAME/openclaw-windows-hub.git
    cd openclaw-windows-hub
    
  2. Create Feature Branch:

    git checkout -b feature/my-new-feature
    
  3. Make Changes:

    • Follow existing code style and patterns
    • Add tests for new functionality
    • Update documentation as needed
  4. Test Locally:

    dotnet build
    dotnet test
    dotnet run --project src/OpenClaw.Tray.WinUI
    
  5. Commit and Push:

    git add .
    git commit -m "Add my new feature"
    git push origin feature/my-new-feature
    
  6. Open Pull Request:

    • Go to GitHub and open a PR from your branch
    • Describe your changes
    • Wait for CI to pass
    • Address review feedback

Code Style

  • C#: Follow standard .NET conventions
  • XAML: Consistent indentation, organize resources logically
  • Naming: Descriptive names, avoid abbreviations
  • Comments: Explain "why", not "what"
  • Error Handling: Use try-catch for expected failures, let unexpected exceptions bubble

Adding New Features

Example: Adding a New Gateway Event Type

  1. Add Model (OpenClaw.Shared/Models.cs):

    public class MyNewEventData
    {
        public string Property { get; set; } = "";
    }
    
  2. Add Event (OpenClaw.Shared/OpenClawGatewayClient.cs):

    public event EventHandler<MyNewEventData>? MyNewEvent;
    
  3. Parse Event (OpenClawGatewayClient.cs, in ListenForMessagesAsync):

    if (eventType == "my_new_event")
    {
        var data = JsonSerializer.Deserialize<MyNewEventData>(json);
        MyNewEvent?.Invoke(this, data);
    }
    
  4. Handle in Tray App (OpenClaw.Tray.WinUI/App.xaml.cs):

    _gatewayClient.MyNewEvent += OnMyNewEvent;
    
    private void OnMyNewEvent(object? sender, MyNewEventData e)
    {
        _dispatcherQueue?.TryEnqueue(() =>
        {
            // Update UI
        });
    }
    
  5. Add Tests (tests/OpenClaw.Shared.Tests/):

    [Fact]
    public void MyNewEventData_DisplaysCorrectly()
    {
        var data = new MyNewEventData { Property = "test" };
        Assert.Equal("test", data.Property);
    }
    

Troubleshooting

Common Issues:

  1. Build Error: "Windows SDK not found"

    • Install Windows 10 SDK 19041 or later
    • Or build Shared library only: dotnet build src/OpenClaw.Shared
  2. Command Palette Extension Not Loading

    • Verify correct architecture (x64 on x64, arm64 on ARM64)
    • Check PowerToys version (latest recommended)
    • View logs: %LOCALAPPDATA%\Microsoft\PowerToys\CmdPal\Logs
    • Run "Reload" command in Command Palette after deploying
  3. WebView2 Error 0x8007000B on ARM64

    • Both Tray app AND Command Palette must be ARM64
    • Rebuild: dotnet build src/OpenClaw.Tray.WinUI -r win-arm64
  4. Tray Icon Not Appearing

    • Check Windows notification area settings
    • Verify app is running (Task Manager)
    • Check logs: %LOCALAPPDATA%\OpenClawTray\openclaw-tray.log
  5. Gateway Connection Fails

    • Verify gateway is running: curl http://localhost:18789/health
    • Check gateway URL in settings
    • Verify authentication token is correct
    • Check firewall settings

Getting Help

Developing & Testing the Onboarding Wizard

The onboarding wizard is a 6-screen flow built with OpenClaw's minimal FunctionalUI helper layer for declarative C# WinUI. The chat page uses a WebView2 overlay for visual consistency with the post-setup chat experience.

Building

The WinUI project requires platform-specific build targets. Use the build script:

./build.ps1 -Project WinUI   # Builds with correct -r win-x64 targets

Direct dotnet build without the script will fail with "WindowsAppSDKSelfContained requires a supported Windows architecture".

Environment Variables

Variable Purpose
OPENCLAW_FORCE_ONBOARDING=1 Show onboarding wizard even if a token already exists
OPENCLAW_SKIP_UPDATE_CHECK=1 Skip the update dialog (useful during testing)
OPENCLAW_LANGUAGE=fr-fr Override UI language (validated: en-us, fr-fr, nl-nl, zh-cn, zh-tw)
OPENCLAW_GATEWAY_PORT=19001 Override default gateway port for local dev
OPENCLAW_VISUAL_TEST=1 Enable automatic screenshot capture on page transitions
OPENCLAW_VISUAL_TEST_DIR=path Output directory for visual test screenshots

Testing the Wizard Locally

  1. Start a local gateway (e.g., in WSL): cd ~/openclaw && npx openclaw gateway
  2. Set env vars:
    $env:OPENCLAW_FORCE_ONBOARDING = "1"
    $env:OPENCLAW_SKIP_UPDATE_CHECK = "1"
    
  3. Build and run: ./build.ps1 -Project WinUI then launch the exe
  4. Navigate through all 6 screens to verify

Architecture

  • FunctionalUI: src/OpenClawTray.FunctionalUI/ — Minimal declarative WinUI helper layer used by onboarding
  • Pages: src/OpenClaw.Tray.WinUI/Onboarding/Pages/ — Functional UI components for each wizard screen
  • Services: src/OpenClaw.Tray.WinUI/Onboarding/Services/ — State management, setup code decoder, permission checker, health check, input validation
  • Widgets: src/OpenClaw.Tray.WinUI/Onboarding/Widgets/ — Shared UI components (cards, step indicators, feature rows)
  • Window: src/OpenClaw.Tray.WinUI/Onboarding/OnboardingWindow.cs — Host window with WebView2 overlay for chat
  • Helpers: src/OpenClaw.Tray.WinUI/Helpers/GatewayChatHelper.cs — Shared WebView2 chat URL builder

Made with 🦞 love by Scott Hanselman and the OpenClaw community