Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ If a command fails:
Notes:

- If a build/test is blocked by an environmental lock (for example running executable locking output assemblies), stop/close the locking process and rerun.
- If validation is blocked by missing local Windows prerequisites, run `.\scripts\setup-dev.ps1` to install/verify developer and agent prerequisites, then rerun validation. Use `.\scripts\setup-dev.ps1 -CheckOnly` when you only need diagnostics.
- **First-run gotcha**: `dotnet test --no-restore` silently no-ops in a fresh worktree where the test `bin/` doesn't exist yet (reports "Build succeeded in 0.5s" then exits 0 with no tests run). For first-run validation, either omit `--no-restore` OR run `dotnet build` on the test project first. Subsequent reruns honor `--no-restore` correctly.
- In linked git worktrees, set `OPENCLAW_REPO_ROOT` to the worktree path before running tests that discover the repository root, for example:
- `$env:OPENCLAW_REPO_ROOT='D:\github\openclaw-windows-node.<worktree-name>'`
Expand Down Expand Up @@ -75,6 +76,17 @@ Start with these docs before changing connection, pairing, node, MCP, or tray UX
- `docs/WINDOWS_NODE_TESTING.md` - Windows node capabilities, manual smokes, and gateway-dependent behavior.
- `docs/ONBOARDING_WIZARD.md` - first-run setup flow, setup-code/bootstrap pairing, and test isolation.

## Architecture Guardrails for Large Refactors

`src\OpenClaw.Tray.WinUI\App.xaml.cs` and `src\OpenClaw.Tray.WinUI\Pages\ConnectionPage.xaml.cs` are active god-file reduction targets. When touching either file:

- Prefer completing a real ownership transfer over moving code to partial classes. A new partial file is not progress unless it introduces a narrower owner, pure projection, policy, service, or tested seam.
- Keep `App` as the composition root. Shrink it by delegating cohesive behavior to focused services, but do not relocate startup ordering into another god object.
- Keep `ConnectionPage.xaml.cs` as the WinUI applicator until a pure row/plan/workflow seam exists. Do not move named-control setters into a presenter that just wraps the page.
- Add characterization tests before moving startup, credential, pairing, node/MCP, tray action, or direct-connect rollback behavior. Source-text contract tests are acceptable for WinUI-only seams, but prefer pure unit tests for policies and projections.
- Keep PRs small and reviewable: one seam per PR, with a clear invariant protected by tests. Stop and re-plan if a PR moves hundreds of lines without behavior coverage.
- In PR descriptions and handoffs, name the old owner, new owner, preserved invariant, and validation run so future agents do not reintroduce duplicate paths or grow new god objects.

Important current facts:

- Gateway credentials are no longer stored in `SettingsData.Token` / `SettingsData.BootstrapToken`. `SettingsManager` may read legacy JSON fields only for one-time migration; new writes must go through `GatewayRegistry`.
Expand Down
4 changes: 4 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ A comprehensive guide for building, running, and contributing to the OpenClaw Wi

- **.NET 10 SDK** - [Download here](https://dotnet.microsoft.com/download)
- **Windows 10/11** - WinUI 3 and Windows App SDK require Windows 10 version 1903 or later
- **Node.js LTS with npm** - Required by the WinUI build to restore JavaScript build assets
- **Windows 10 SDK** - Required for WinUI builds
- **WebView2 Runtime** - Usually pre-installed on Windows 10+ ([Manual download](https://developer.microsoft.com/microsoft-edge/webview2/))
- **Visual Studio 2022** (optional) - For easier development and debugging with WinUI 3 designer support

Run `.\scripts\setup-dev.ps1` from the repository root to install or verify local prerequisites with winget. Agents can use `.\scripts\setup-dev.ps1 -RunValidation` to prepare the worktree and run the required closeout validation.

### For Testing

- **A running OpenClaw gateway instance** - The gateway provides the backend for chat, sessions, and notifications when validating gateway-mediated flows
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,25 @@ Direct downloads from the latest OpenClaw release:
### Prerequisites
- Windows 10 (20H2+) or Windows 11
- .NET 10.0 SDK - https://dotnet.microsoft.com/download/dotnet/10.0
- Node.js LTS with npm (for WinUI build assets)
- Windows 10 SDK (for WinUI build) - install via Visual Studio or standalone
- WebView2 Runtime - pre-installed on modern Windows, or get from https://developer.microsoft.com/microsoft-edge/webview2

### Developer / Agent Setup

Use the setup script to install or verify local Windows build prerequisites:

```powershell
# Install missing prerequisites with winget, trust the checkout, and verify setup
.\scripts\setup-dev.ps1

# Check only; do not install packages or change git safe.directory
.\scripts\setup-dev.ps1 -CheckOnly

# Setup and run the required build/test validation
.\scripts\setup-dev.ps1 -RunValidation
```

### Build

Use the build script to check prerequisites and build:
Expand Down
18 changes: 15 additions & 3 deletions build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,23 @@ if (-not $nodeVersion) {
# Check Windows SDK (for WinUI)
$windowsSdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Include"
if (Test-Path $windowsSdkPath) {
$sdkVersions = Get-ChildItem $windowsSdkPath -Directory | Select-Object -ExpandProperty Name | Sort-Object -Descending
Write-Success "Windows SDK: $($sdkVersions[0])"
$sdkVersions = @(
Get-ChildItem $windowsSdkPath -Directory |
Where-Object { $_.Name -match "^\d+\.\d+\.\d+\.\d+$" } |
Sort-Object { [version]$_.Name } -Descending |
Select-Object -ExpandProperty Name
)

if ($sdkVersions.Count -gt 0) {
Write-Success "Windows SDK: $($sdkVersions[0])"
} else {
Write-Warning "Windows 10 SDK not found (needed for WinUI build)"
Write-Info "Install via Visual Studio Installer, standalone SDK, or: winget install --id Microsoft.WindowsSDK.10.0.26100 -e"
$issues += "Windows 10 SDK not detected"
}
} else {
Write-Warning "Windows 10 SDK not found (needed for WinUI build)"
Write-Info "Install via Visual Studio Installer or standalone SDK"
Write-Info "Install via Visual Studio Installer, standalone SDK, or: winget install --id Microsoft.WindowsSDK.10.0.26100 -e"
$issues += "Windows 10 SDK not detected"
}

Expand Down
274 changes: 274 additions & 0 deletions scripts/setup-dev.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
<#
.SYNOPSIS
Prepares a Windows checkout for OpenClaw developer and agent work.

.DESCRIPTION
Installs missing local prerequisites with winget, refreshes the current
process PATH, trusts the checkout for GitVersion, and runs the repository
prerequisite check. Use -CheckOnly to report what is missing without
installing anything.

.PARAMETER CheckOnly
Check prerequisites without installing or changing git safe.directory.

.PARAMETER RunValidation
After setup, run the full build plus the required shared and tray test
projects used by AGENTS.md closeout validation.

.PARAMETER NoTrustRepository
Do not add this checkout to git safe.directory.

.EXAMPLE
.\scripts\setup-dev.ps1
.\scripts\setup-dev.ps1 -CheckOnly
.\scripts\setup-dev.ps1 -RunValidation
#>

param(
[switch]$CheckOnly,
[switch]$RunValidation,
[switch]$NoTrustRepository
)

$ErrorActionPreference = "Stop"

$repoRoot = Split-Path -Parent $PSScriptRoot
Set-Location $repoRoot

function Write-Header($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan }
function Write-Success($text) { Write-Host "[OK] $text" -ForegroundColor Green }
function Write-WarningMessage($text) { Write-Host "[WARN] $text" -ForegroundColor Yellow }
function Write-ErrorMessage($text) { Write-Host "[ERROR] $text" -ForegroundColor Red }
function Write-Info($text) { Write-Host " $text" -ForegroundColor Gray }

function Test-WindowsHost {
$isWindowsVariable = Get-Variable -Name IsWindows -ErrorAction SilentlyContinue
if ($isWindowsVariable) {
return [bool]$isWindowsVariable.Value
}

return [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT
}

function Update-ProcessPath {
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
$env:Path = @($machinePath, $userPath) -join ";"
}

function Test-CommandAvailable($name) {
return $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
}

function Test-DotNet10Sdk {
if (-not (Test-CommandAvailable "dotnet")) {
return $false
}

$sdks = & dotnet --list-sdks 2>$null
return $LASTEXITCODE -eq 0 -and ($sdks | Where-Object { $_ -match "^10\." })
}

function Test-NodeAndNpm {
return (Test-CommandAvailable "node") -and (Test-CommandAvailable "npm")
}

function Get-WindowsSdkVersion {
$windowsSdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Include"
if (-not (Test-Path $windowsSdkPath)) {
return $null
}

$versions = @(
Get-ChildItem $windowsSdkPath -Directory |
Where-Object { $_.Name -match "^\d+\.\d+\.\d+\.\d+$" } |
Sort-Object { [version]$_.Name } -Descending |
Select-Object -ExpandProperty Name
)

if ($versions.Count -eq 0) {
return $null
}

return $versions[0]
}

function Get-WebView2RuntimeVersion {
$keys = @(
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
"HKCU:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
)

foreach ($key in $keys) {
if (Test-Path $key) {
$version = (Get-ItemProperty $key -ErrorAction SilentlyContinue).pv
if ($version) {
return $version
}
}
}

return $null
}

function Install-WingetPackage($id, $displayName) {
if ($CheckOnly) {
Write-WarningMessage "$displayName is missing."
Write-Info "Install with: winget install --id $id -e"
return
}

if (-not (Test-CommandAvailable "winget")) {
throw "winget is not available. Install App Installer from the Microsoft Store, then rerun this script."
}

Write-Header "Installing $displayName"
$arguments = @(
"install",
"--id", $id,
"-e",
"--accept-source-agreements",
"--accept-package-agreements",
"--disable-interactivity"
)
& winget @arguments
if ($LASTEXITCODE -ne 0) {
throw "winget failed to install $displayName ($id)."
}

Update-ProcessPath
}

function ConvertTo-GitSafeDirectoryPath($path) {
return ([System.IO.Path]::GetFullPath($path).TrimEnd("\") -replace "\\", "/")
}

function Test-GitSafeDirectoryContains($path) {
if (-not (Test-CommandAvailable "git")) {
return $false
}

$expected = (ConvertTo-GitSafeDirectoryPath $path).ToLowerInvariant()
$safeDirectories = & git config --global --get-all safe.directory 2>$null
if ($LASTEXITCODE -ne 0 -or -not $safeDirectories) {
return $false
}

foreach ($safeDirectory in $safeDirectories) {
if ($safeDirectory -eq "*") {
return $true
}

$normalized = ($safeDirectory.Trim().TrimEnd("\", "/") -replace "\\", "/").ToLowerInvariant()
if ($normalized -eq $expected) {
return $true
}
}

return $false
}

function Ensure-RepositoryTrust {
if ($NoTrustRepository -or $CheckOnly -or -not (Test-CommandAvailable "git")) {
return
}

if (-not (Test-Path (Join-Path $repoRoot ".git"))) {
return
}

if (Test-GitSafeDirectoryContains $repoRoot) {
Write-Success "Repository already trusted for GitVersion."
return
}

$safeDirectory = ConvertTo-GitSafeDirectoryPath $repoRoot
Write-Info "Adding git safe.directory entry: $safeDirectory"
& git config --global --add safe.directory $safeDirectory
if ($LASTEXITCODE -ne 0) {
throw "Failed to add git safe.directory entry for $safeDirectory."
}
Write-Success "Repository trusted for GitVersion."
}

function Require-Prerequisite($name, $isAvailable, $packageId) {
if ($isAvailable) {
Write-Success "$name detected."
return
}

Install-WingetPackage $packageId $name
}

if (-not (Test-WindowsHost)) {
throw "OpenClaw Windows development requires Windows."
}

Write-Header "OpenClaw developer setup"
if ($CheckOnly) {
Write-Info "CheckOnly mode: no packages will be installed and git safe.directory will not be changed."
}

Update-ProcessPath

Require-Prerequisite "Git" (Test-CommandAvailable "git") "Git.Git"
Require-Prerequisite ".NET 10 SDK" (Test-DotNet10Sdk) "Microsoft.DotNet.SDK.10"
Require-Prerequisite "Node.js LTS with npm" (Test-NodeAndNpm) "OpenJS.NodeJS.LTS"
Require-Prerequisite "Windows SDK 10.0.26100" ([bool](Get-WindowsSdkVersion)) "Microsoft.WindowsSDK.10.0.26100"

$webView2Version = Get-WebView2RuntimeVersion
if ($webView2Version) {
Write-Success "WebView2 Runtime detected ($webView2Version)."
} else {
Install-WingetPackage "Microsoft.EdgeWebView2Runtime" "WebView2 Runtime"
}

Update-ProcessPath
Ensure-RepositoryTrust

$missing = @()
if (-not (Test-CommandAvailable "git")) { $missing += "Git" }
if (-not (Test-DotNet10Sdk)) { $missing += ".NET 10 SDK" }
if (-not (Test-NodeAndNpm)) { $missing += "Node.js LTS with npm" }
if (-not (Get-WindowsSdkVersion)) { $missing += "Windows SDK 10.0.26100" }
if (-not (Get-WebView2RuntimeVersion)) { $missing += "WebView2 Runtime" }

if ($missing.Count -gt 0) {
Write-ErrorMessage "Setup is incomplete:"
foreach ($item in $missing) {
Write-Info "- $item"
}

if (-not $CheckOnly) {
Write-Info "If packages were just installed, open a new terminal and rerun .\scripts\setup-dev.ps1 -CheckOnly."
}
exit 1
}

Write-Header "Repository prerequisite check"
& "$repoRoot\build.ps1" -CheckOnly
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}

if ($RunValidation) {
Write-Header "Required validation"
$env:OPENCLAW_REPO_ROOT = $repoRoot

& "$repoRoot\build.ps1"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

dotnet build "$repoRoot\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

dotnet build "$repoRoot\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj"
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

dotnet test "$repoRoot\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj" --no-restore
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

dotnet test "$repoRoot\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj" --no-restore
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
}

Write-Success "OpenClaw developer setup is ready."
Loading
Loading