openclaw/docs/platforms/windows-companion-build-guide.md
TAPUZE 7f33621721 feat(windows): Add Windows companion app (Phase 0-2)
- System tray icon with connection status
- Exec approval dialogs with queue and timeout
- Settings persistence to JSON (%LOCALAPPDATA%\Clawdbot)
- Auto-start on login via Windows Registry
- Notification sounds for approvals and connection events
- WebView2 Control UI embedding
- 23 unit tests passing
- Build scripts and Inno Setup installer template
- Community build guide documentation

Phase 0: Protocol models, WebSocket client, logging
Phase 1: Tray icon, exec approval dialogs, Control UI
Phase 2: Settings, auto-start, sounds, settings UI
2026-01-27 11:12:39 +02:00

15 KiB

title summary read_when
Building the Windows Companion App Complete guide to building the Clawdbot Windows companion from scratch
Contributing to Windows companion development
Understanding Windows companion architecture
Extending the Windows app

Building the Clawdbot Windows Companion App

This guide documents the complete process of building the Windows companion app for Clawdbot. It's written for developers who want to understand how it was built, contribute improvements, or use it as a reference for building similar apps.

Overview

The Windows companion app is a native .NET 9 WPF application that:

  • Runs in the system tray with connection status indicators
  • Connects to the Clawdbot Gateway via WebSocket (Protocol v3)
  • Shows exec approval dialogs when AI agents request to run commands
  • Embeds the Control UI via WebView2
  • Persists user settings to disk
  • Supports auto-start on Windows login
  • Plays notification sounds for important events

Prerequisites

Before building, ensure you have:

  • Windows 10 (1903+) or Windows 11
  • .NET 9.0 SDK - Download
  • Visual Studio 2022 (recommended) or VS Code with C# extension
  • WebView2 Runtime - Ships with Windows 11, or download for Windows 10

Verify your setup:

dotnet --version
# Should show 9.0.x

Architecture

The solution follows a clean 3-project structure:

apps/windows/
├── ClawdbotWindows.sln
├── src/
│   ├── Clawdbot.Windows/           # WPF App (UI layer)
│   │   ├── App.xaml(.cs)           # Entry point, DI setup
│   │   ├── MainWindow.xaml(.cs)    # WebView2 Control UI
│   │   ├── ExecApprovalDialog.xaml(.cs)  # Command approval UI
│   │   ├── SettingsWindow.xaml(.cs)      # Settings UI
│   │   ├── SystemTrayIcon.cs       # Tray icon + context menu
│   │   └── Assets/                 # Icons
│   │
│   ├── Clawdbot.Windows.Core/      # Business logic (no UI)
│   │   ├── GatewayChannel.cs       # WebSocket client
│   │   ├── ExecApprovalService.cs  # Approval event handling
│   │   ├── ExecApprovalModels.cs   # Request/response types
│   │   ├── AppSettings.cs          # JSON settings persistence
│   │   ├── AutoStartHelper.cs      # Windows Registry auto-start
│   │   ├── NotificationSounds.cs   # System sound playback
│   │   ├── AppLogger.cs            # File logging
│   │   └── WebView2Helper.cs       # Runtime detection
│   │
│   └── Clawdbot.Windows.Protocol/  # Gateway protocol models
│       └── GatewayModels.cs        # Protocol v3 types
│
└── tests/
    └── Clawdbot.Windows.Tests/     # xUnit tests
        ├── Phase0ValidationTests.cs
        ├── ExecApprovalTests.cs
        └── SettingsTests.cs

Why This Structure?

  1. Protocol - Isolated models that match the Gateway's TypeScript types exactly
  2. Core - Testable business logic with no WPF dependencies
  3. Windows - WPF-specific UI that consumes Core services

This mirrors the macOS app's ClawdbotKit/Clawdbot split.

Build Process

Step 1: Clone and Navigate

git clone https://github.com/clawdbot/clawdbot.git
cd clawdbot/apps/windows

Step 2: Restore and Build

# Debug build (faster, includes debug menu)
dotnet build

# Release build (optimized)
dotnet build --configuration Release

Step 3: Run Tests

dotnet test

Expected output: 23 passing, 7 skipped (skipped tests require a running Gateway)

Step 4: Run the App

# From apps/windows directory
dotnet run --project src\Clawdbot.Windows

# Or run the built executable
.\src\Clawdbot.Windows\bin\Release\net9.0-windows\Clawdbot.exe

The app starts minimized to the system tray. Look for the Clawdbot icon in your system tray (you may need to click the "^" arrow to see hidden icons).

Development Phases

This section documents exactly how the app was built, phase by phase.

Phase 0: Foundation (Gateway Connection)

Goal: Establish WebSocket connection to Gateway, validate protocol compatibility.

Files Created

  1. GatewayModels.cs - Protocol types matching TypeScript schema

    public record ConnectParams(
        int MinProtocol,
        int MaxProtocol,
        Dictionary<string, JsonElement> Client,
        // ...
    );
    
    public record HelloOk(
        int Protocol,
        string? Error,
        string? Gateway
    );
    
  2. GatewayChannel.cs - WebSocket client with:

    • Connection state machine (Disconnected → Connecting → Connected)
    • Automatic reconnection with exponential backoff
    • Request/response correlation via message IDs
    • Event subscription (snapshot, exec approval, etc.)
  3. AppLogger.cs - File-based logging to %LOCALAPPDATA%\Clawdbot\logs\

  4. Phase0ValidationTests.cs - Integration tests for Gateway connection

Key Protocol Details

The Gateway uses Protocol v3 with JSON-RPC style messaging:

// Request (client → gateway)
{"id": "1", "method": "config.get", "params": {}}

// Response (gateway → client)
{"id": "1", "ok": {"config": {...}, "configHash": "abc123"}}

// Event (gateway → client, no id)
{"method": "snapshot", "params": {...}}

Phase 1: Core Features (Tray + Approvals)

Goal: System tray icon with exec approval dialogs.

Files Created

  1. SystemTrayIcon.cs - Using Hardcodet.NotifyIcon.Wpf

    • Context menu with status, connect/disconnect, settings, exit
    • Double-click opens main window
    • Tooltip shows connection status
  2. ExecApprovalDialog.xaml - WPF dialog matching macOS design

    • Command display in monospace font
    • Context details (working dir, host, agent, security level)
    • Countdown timer with progress bar
    • Three buttons: Allow Once, Always Allow, Don't Allow
  3. ExecApprovalService.cs - Queue management

    • Handles multiple pending approvals
    • Auto-denies on timeout
    • Dispatches to UI thread
  4. MainWindow.xaml - WebView2 embedding Control UI

Exec Approval Flow

Gateway → exec.approval event
    ↓
ExecApprovalService (queue)
    ↓
ExecApprovalDialog (UI)
    ↓
User clicks button
    ↓
GatewayChannel.SendExecApprovalResponse()
    ↓
Gateway

Phase 2: Production Ready

Goal: Settings persistence, auto-start, notification sounds.

Files Created

  1. AppSettings.cs - JSON settings

    public class AppSettings
    {
        public string GatewayUrl { get; set; } = "ws://127.0.0.1:18789/";
        public bool StartOnLogin { get; set; } = false;
        public bool MinimizeToTray { get; set; } = true;
        public bool PlaySounds { get; set; } = true;
        // ...
    }
    

    Settings stored at: %LOCALAPPDATA%\Clawdbot\settings.json

  2. AutoStartHelper.cs - Windows Registry integration

    // Registry key: HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
    public static void Enable()
    {
        var exePath = Process.GetCurrentProcess().MainModule?.FileName;
        using var key = Registry.CurrentUser.OpenSubKey(RunKeyPath, true);
        key?.SetValue("Clawdbot", $"\"{exePath}\"");
    }
    
  3. NotificationSounds.cs - Windows system sounds

    public static void PlayApprovalRequest() => SystemSounds.Exclamation.Play();
    public static void PlayConnected() => SystemSounds.Asterisk.Play();
    
  4. SettingsWindow.xaml - Settings UI with:

    • Gateway URL textbox
    • Checkboxes for all settings
    • Save/Cancel/Reset buttons
    • Version and path info display

Integration Points

In App.xaml.cs:

// Load settings on startup
_settings = AppSettings.Load();

// Create channel with settings URL
_channel = new GatewayChannel(_settings.GatewayUrl);

// Play sounds on events
_channel.StateChanged += (_, state) => {
    if (_settings.PlaySounds)
    {
        if (state == GatewayState.Connected)
            NotificationSounds.PlayConnected();
    }
};

Key Implementation Details

WebSocket Connection

The GatewayChannel manages the WebSocket lifecycle:

public async Task ConnectAsync()
{
    State = GatewayState.Connecting;
    
    _ws = new ClientWebSocket();
    await _ws.ConnectAsync(new Uri(_gatewayUrl), CancellationToken.None);
    
    // Send connect frame
    await SendAsync(new ConnectFrame { 
        Params = new ConnectParams { 
            MinProtocol = 3, 
            MaxProtocol = 3,
            // ...
        } 
    });
    
    // Wait for hello response
    var hello = await ReceiveHelloAsync();
    if (hello.Protocol != 3) throw new Exception("Protocol mismatch");
    
    State = GatewayState.Connected;
    
    // Start receive loop
    _ = ReceiveLoopAsync();
}

Exec Approval Queue

Multiple approval requests can arrive while a dialog is open:

private readonly Queue<ExecApprovalRequest> _pendingApprovals = new();
private ExecApprovalDialog? _currentDialog;

public void EnqueueApproval(ExecApprovalRequest request)
{
    _pendingApprovals.Enqueue(request);
    if (_currentDialog == null)
        ShowNextApproval();
}

private void ShowNextApproval()
{
    if (!_pendingApprovals.TryDequeue(out var request)) return;
    
    _currentDialog = new ExecApprovalDialog(request);
    _currentDialog.Closed += (_, _) => {
        _currentDialog = null;
        ShowNextApproval(); // Process next in queue
    };
    _currentDialog.Show();
}

Settings Persistence

Settings auto-save and provide defaults for missing properties:

public static AppSettings Load()
{
    var path = GetSettingsFilePath();
    if (!File.Exists(path)) return new AppSettings();
    
    var json = File.ReadAllText(path);
    return JsonSerializer.Deserialize<AppSettings>(json) ?? new();
}

public void Save()
{
    var path = GetSettingsFilePath();
    Directory.CreateDirectory(Path.GetDirectoryName(path)!);
    
    var json = JsonSerializer.Serialize(this, new JsonSerializerOptions { 
        WriteIndented = true 
    });
    File.WriteAllText(path, json);
}

Testing

Unit Tests (No Gateway Required)

dotnet test --filter "Category!=Integration"

Tests protocol models, settings persistence, approval models.

Integration Tests (Gateway Required)

Start the Gateway first:

# In WSL2 or Linux terminal
clawdbot gateway run --bind loopback --port 18789

Then run all tests:

dotnet test

Manual Testing Checklist

  • App starts and shows tray icon
  • Tray icon tooltip shows "Disconnected" initially
  • Right-click tray shows context menu
  • Settings window opens and saves correctly
  • Auto-start toggle modifies registry
  • Double-click tray opens main window
  • With Gateway running, icon shows "Connected"
  • Exec approval dialog appears for test commands (Debug menu)

Configuration

Settings File

Location: %LOCALAPPDATA%\Clawdbot\settings.json

{
  "gatewayUrl": "ws://127.0.0.1:18789/",
  "startOnLogin": false,
  "minimizeToTray": true,
  "playSounds": true,
  "showConnectionNotifications": true,
  "reconnectIntervalSeconds": 5
}

Logs

Location: %LOCALAPPDATA%\Clawdbot\logs\clawdbot-YYYY-MM-DD.log

View live:

Get-Content "$env:LOCALAPPDATA\Clawdbot\logs\clawdbot-$(Get-Date -Format 'yyyy-MM-dd').log" -Wait

Next Steps for Contributors

The following features are planned but not yet implemented. Pick one and contribute!

Phase 3: Distribution (Priority: High)

  1. MSIX Installer

    • Create Package.appxmanifest
    • Configure signing certificate
    • Add to Windows Store or sideload
    • See: MSIX Packaging Guide
  2. MSI Installer (Alternative)

    • Use WiX Toolset v4
    • Create Product.wxs with component structure
    • See: WiX Quick Start
  3. Auto-Update Mechanism

    • Check GitHub releases API for new versions
    • Download and apply updates
    • Consider: Squirrel.Windows or custom solution

Phase 4: Feature Parity with macOS

  1. Voice Wake Integration

    • Windows Speech Recognition API
    • "Hey Clawdbot" wake word detection
    • Microphone permission handling
  2. Talk Mode

    • Audio capture and streaming
    • Text-to-speech for responses
    • Push-to-talk hotkey
  3. Canvas Commands

    • Screenshot capture (screen.*)
    • Clipboard integration
    • File drag-and-drop
  4. Global Hotkeys

    • Register system-wide keyboard shortcuts
    • Quick actions without opening app

Phase 5: Polish

  1. Notifications

    • Windows Toast notifications
    • Action buttons in notifications
    • Notification history
  2. Theming

    • Dark/light mode following Windows setting
    • Custom accent colors
    • High contrast support
  3. Accessibility

    • Screen reader support
    • Keyboard navigation
    • High DPI scaling

Code Generator Enhancement

The project could benefit from a C# code generator similar to the Swift one:

scripts/protocol-gen-csharp.ts → GatewayModels.cs

This would auto-generate Protocol models from TypeBox schemas, keeping Windows in sync with Gateway changes.

Troubleshooting

"Cannot connect to Gateway"

  1. Ensure Gateway is running: clawdbot gateway run --bind loopback --port 18789
  2. Check Gateway URL in settings matches
  3. Verify no firewall blocking port 18789
  4. Check logs at %LOCALAPPDATA%\Clawdbot\logs\

"WebView2 not available"

  1. Windows 11: Should be pre-installed
  2. Windows 10: Download from Microsoft
  3. Check: WebView2Helper.IsWebView2Available() returns true

"App doesn't start"

  1. Check for existing process: Get-Process Clawdbot -ErrorAction SilentlyContinue
  2. Kill if stuck: Stop-Process -Name Clawdbot -Force
  3. Check logs for errors

"Tray icon not visible"

  1. Click the "^" arrow in system tray to see hidden icons
  2. Drag Clawdbot icon to always-visible area
  3. Windows Settings → Personalization → Taskbar → System tray icons

Contributing

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-feature
  3. Make changes in apps/windows/
  4. Run tests: dotnet test
  5. Submit a pull request

Code Style

  • Follow C# naming conventions (PascalCase for public members)
  • Use async/await for all I/O operations
  • Add XML doc comments for public APIs
  • Keep files under 500 lines when practical

Pull Request Checklist

  • Tests pass locally
  • New features have tests
  • README updated if needed
  • No compiler warnings
  • Code formatted consistently

Resources

License

MIT - see LICENSE in the project root.