- README.md: Fix project table (OpenClaw.Tray → OpenClaw.Tray.WinUI), remove WinForms run command, add system.run.prepare and system.which to capability table and allowCommands JSON, remove '(investigating)' from canvas.a2ui commands - DEVELOPMENT.md: Remove OpenClaw.Tray/ from structure, add OpenClaw.Tray.Tests/, update test counts (88 → 571), fix CI section - build.ps1: Fix broken 'Tray' target to point at WinUI .csproj, remove WinForms from default build and run instructions - docs/VERSIONING.md: Remove reference to deleted OpenClaw.Tray.csproj - docs/TEST_COVERAGE.md: Full rewrite (88 → 571 tests, .NET 9 → 10) - docs/CODE_REVIEW.md: Update project names, test counts, .NET version - docs/WINDOWS_NODE_TESTING.md: Mark system.run as implemented, update capability descriptions - docs/WINDOWS_NODE_ARCHITECTURE.md: Add historical planning note, update current state table (Node mode now implemented) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
23 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
- Default gateway URL:
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
│
├── moltbot-windows-hub.slnx # Solution file (historical name)
├── README.md # User-facing documentation
└── DEVELOPMENT.md # This file
Note on Naming: The solution file is named
moltbot-windows-hub.slnxdue to the project's history (formerly known as Moltbot, formerly known as Clawdbot). The repository and current branding use "OpenClaw".
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/WebChatWindow.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
- Open
moltbot-windows-hub.slnxin Visual Studio 2022 - Set
OpenClaw.Tray.WinUIas the startup project - 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:
- Removes the currently installed extension
- Builds the extension for your platform
- Deploys it via
Add-AppxPackage -Register - 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:
- WebSocket connects to gateway URL (default:
ws://localhost:18789) - Client waits for
challengeevent from gateway - Client responds with authentication token
- Gateway sends
connectedevent confirming authentication - 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
StatusChangedevent
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:
- Structured (preferred): Events with explicit
type,category, ornotificationTypefields - Text-based (fallback): Keyword matching on notification content
Categories:
health- Blood sugar, glucose, CGM readingsurgent- Critical alerts requiring immediate attentionreminder- Calendar reminders, tasksstock- Stock price alertsemail- Email notificationscalendar- Calendar eventserror- Error messagesbuild- CI/CD build statusinfo- 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 WebChatWindow uses Microsoft Edge WebView2 for embedded web content:
Initialization:
- WebView2 control created in XAML
CoreWebView2environment initialized on window load- User data folder:
%LOCALAPPDATA%\OpenClawTray\WebView2 - 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 WebChat 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
_sessionsdictionary
Display Selection Algorithm:
- Active main session always takes priority
- Currently displayed session kept if still active (prevents flipping)
- Falls back to most recently active sub-session
- 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:
- ✅ 478 tests in
OpenClaw.Shared.Tests— models, gateway client, exec approvals, capabilities, URL helpers, notification categorization, shell quoting - ✅ 93 tests in
OpenClaw.Tray.Tests— menu display, menu positioning, settings round-trip, deep link parsing - ✅ 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:
- Launch the app:
dotnet run --project src/OpenClaw.Tray.WinUI - Right-click tray icon → Settings
- Enter a dummy gateway URL (e.g.,
ws://localhost:18789) - The app will show "Disconnected" status but you can:
- Test the tray menu structure
- Open Settings dialog and configure preferences
- Test auto-start functionality
- View logs
Command Palette:
- Deploy the extension:
.\tools\cmdpal-dev.ps1 deploy - Open PowerToys Command Palette (Win+Alt+Space)
- Type "OpenClaw"
- Commands will show but most require a connected gateway to function
Manual Test Scenarios
Tray Icon States
-
Disconnected (Gray):
- Start app without gateway running
- Verify icon is gray
- Verify tooltip shows "Disconnected"
-
Connecting (Amber):
- Configure valid gateway URL but don't start gateway yet
- Restart app
- Briefly observe amber icon during connection attempt
-
Connected (Green):
- Start gateway
- Verify icon turns green
- Verify tooltip shows "Connected"
-
Error (Red):
- Connect to gateway, then stop gateway
- Verify icon turns red after timeout
-
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
-
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
-
Notification History:
- Right-click tray → Notification History
- Verify past notifications are listed
- Test filtering by category
-
Notification Settings:
- Settings → Disable notifications
- Send a chat message
- Verify no toast appears (but history still records it)
WebChat Panel
-
Open WebChat:
- Right-click tray → Open Web Chat
- Verify window opens with WebView2 content
- Test sending a message
-
Window State Persistence:
- Move/resize WebChat window
- Close and reopen
- Verify position/size restored (future feature)
-
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
mainormasterbranch - Pull requests to
mainormaster - 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.Testsanddotnet 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):
-
Build & Sign:
- All artifacts built for x64 and ARM64
- Executables signed with Azure Trusted Signing certificate
-
Create Installers:
- Inno Setup creates Windows installers
- Includes both Tray app and Command Palette extension
- Separate installers for x64 and ARM64
-
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
- Installers:
- 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
-
Fork and Clone:
git clone https://github.com/YOUR_USERNAME/openclaw-windows-hub.git cd openclaw-windows-hub -
Create Feature Branch:
git checkout -b feature/my-new-feature -
Make Changes:
- Follow existing code style and patterns
- Add tests for new functionality
- Update documentation as needed
-
Test Locally:
dotnet build dotnet test dotnet run --project src/OpenClaw.Tray.WinUI -
Commit and Push:
git add . git commit -m "Add my new feature" git push origin feature/my-new-feature -
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
-
Add Model (
OpenClaw.Shared/Models.cs):public class MyNewEventData { public string Property { get; set; } = ""; } -
Add Event (
OpenClaw.Shared/OpenClawGatewayClient.cs):public event EventHandler<MyNewEventData>? MyNewEvent; -
Parse Event (
OpenClawGatewayClient.cs, inListenForMessagesAsync):if (eventType == "my_new_event") { var data = JsonSerializer.Deserialize<MyNewEventData>(json); MyNewEvent?.Invoke(this, data); } -
Handle in Tray App (
OpenClaw.Tray.WinUI/App.xaml.cs):_gatewayClient.MyNewEvent += OnMyNewEvent; private void OnMyNewEvent(object? sender, MyNewEventData e) { _dispatcherQueue?.TryEnqueue(() => { // Update UI }); } -
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:
-
Build Error: "Windows SDK not found"
- Install Windows 10 SDK 19041 or later
- Or build Shared library only:
dotnet build src/OpenClaw.Shared
-
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
-
WebView2 Error 0x8007000B on ARM64
- Both Tray app AND Command Palette must be ARM64
- Rebuild:
dotnet build src/OpenClaw.Tray.WinUI -r win-arm64
-
Tray Icon Not Appearing
- Check Windows notification area settings
- Verify app is running (Task Manager)
- Check logs:
%LOCALAPPDATA%\OpenClawTray\openclaw-tray.log
-
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
- Verify gateway is running:
Getting Help
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: OpenClaw Docs
Made with 🦞 love by Scott Hanselman and the OpenClaw community