Skip to content

Commit 06b63ac

Browse files
authored
Add App refactor guardrails and setup validation
Remove duplicate WSL keepalive ownership from App, add source contract coverage, and document/automate developer setup with fresh-worktree validation safeguards.
1 parent 1fb28ee commit 06b63ac

7 files changed

Lines changed: 365 additions & 235 deletions

File tree

AGENTS.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ If a command fails:
2222
Notes:
2323

2424
- If a build/test is blocked by an environmental lock (for example running executable locking output assemblies), stop/close the locking process and rerun.
25+
- 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.
2526
- **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.
2627
- In linked git worktrees, set `OPENCLAW_REPO_ROOT` to the worktree path before running tests that discover the repository root, for example:
2728
- `$env:OPENCLAW_REPO_ROOT='D:\github\openclaw-windows-node.<worktree-name>'`
@@ -75,6 +76,17 @@ Start with these docs before changing connection, pairing, node, MCP, or tray UX
7576
- `docs/WINDOWS_NODE_TESTING.md` - Windows node capabilities, manual smokes, and gateway-dependent behavior.
7677
- `docs/ONBOARDING_WIZARD.md` - first-run setup flow, setup-code/bootstrap pairing, and test isolation.
7778

79+
## Architecture Guardrails for Large Refactors
80+
81+
`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:
82+
83+
- 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.
84+
- 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.
85+
- 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.
86+
- 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.
87+
- 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.
88+
- 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.
89+
7890
Important current facts:
7991

8092
- 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`.

DEVELOPMENT.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ A comprehensive guide for building, running, and contributing to the OpenClaw Wi
1818

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

26+
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.
27+
2428
### For Testing
2529

2630
- **A running OpenClaw gateway instance** - The gateway provides the backend for chat, sessions, and notifications when validating gateway-mediated flows

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,25 @@ Direct downloads from the latest OpenClaw release:
3939
### Prerequisites
4040
- Windows 10 (20H2+) or Windows 11
4141
- .NET 10.0 SDK - https://dotnet.microsoft.com/download/dotnet/10.0
42+
- Node.js LTS with npm (for WinUI build assets)
4243
- Windows 10 SDK (for WinUI build) - install via Visual Studio or standalone
4344
- WebView2 Runtime - pre-installed on modern Windows, or get from https://developer.microsoft.com/microsoft-edge/webview2
4445

46+
### Developer / Agent Setup
47+
48+
Use the setup script to install or verify local Windows build prerequisites:
49+
50+
```powershell
51+
# Install missing prerequisites with winget, trust the checkout, and verify setup
52+
.\scripts\setup-dev.ps1
53+
54+
# Check only; do not install packages or change git safe.directory
55+
.\scripts\setup-dev.ps1 -CheckOnly
56+
57+
# Setup and run the required build/test validation
58+
.\scripts\setup-dev.ps1 -RunValidation
59+
```
60+
4561
### Build
4662

4763
Use the build script to check prerequisites and build:

build.ps1

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,23 @@ if (-not $nodeVersion) {
228228
# Check Windows SDK (for WinUI)
229229
$windowsSdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Include"
230230
if (Test-Path $windowsSdkPath) {
231-
$sdkVersions = Get-ChildItem $windowsSdkPath -Directory | Select-Object -ExpandProperty Name | Sort-Object -Descending
232-
Write-Success "Windows SDK: $($sdkVersions[0])"
231+
$sdkVersions = @(
232+
Get-ChildItem $windowsSdkPath -Directory |
233+
Where-Object { $_.Name -match "^\d+\.\d+\.\d+\.\d+$" } |
234+
Sort-Object { [version]$_.Name } -Descending |
235+
Select-Object -ExpandProperty Name
236+
)
237+
238+
if ($sdkVersions.Count -gt 0) {
239+
Write-Success "Windows SDK: $($sdkVersions[0])"
240+
} else {
241+
Write-Warning "Windows 10 SDK not found (needed for WinUI build)"
242+
Write-Info "Install via Visual Studio Installer, standalone SDK, or: winget install --id Microsoft.WindowsSDK.10.0.26100 -e"
243+
$issues += "Windows 10 SDK not detected"
244+
}
233245
} else {
234246
Write-Warning "Windows 10 SDK not found (needed for WinUI build)"
235-
Write-Info "Install via Visual Studio Installer or standalone SDK"
247+
Write-Info "Install via Visual Studio Installer, standalone SDK, or: winget install --id Microsoft.WindowsSDK.10.0.26100 -e"
236248
$issues += "Windows 10 SDK not detected"
237249
}
238250

scripts/setup-dev.ps1

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
<#
2+
.SYNOPSIS
3+
Prepares a Windows checkout for OpenClaw developer and agent work.
4+
5+
.DESCRIPTION
6+
Installs missing local prerequisites with winget, refreshes the current
7+
process PATH, trusts the checkout for GitVersion, and runs the repository
8+
prerequisite check. Use -CheckOnly to report what is missing without
9+
installing anything.
10+
11+
.PARAMETER CheckOnly
12+
Check prerequisites without installing or changing git safe.directory.
13+
14+
.PARAMETER RunValidation
15+
After setup, run the full build plus the required shared and tray test
16+
projects used by AGENTS.md closeout validation.
17+
18+
.PARAMETER NoTrustRepository
19+
Do not add this checkout to git safe.directory.
20+
21+
.EXAMPLE
22+
.\scripts\setup-dev.ps1
23+
.\scripts\setup-dev.ps1 -CheckOnly
24+
.\scripts\setup-dev.ps1 -RunValidation
25+
#>
26+
27+
param(
28+
[switch]$CheckOnly,
29+
[switch]$RunValidation,
30+
[switch]$NoTrustRepository
31+
)
32+
33+
$ErrorActionPreference = "Stop"
34+
35+
$repoRoot = Split-Path -Parent $PSScriptRoot
36+
Set-Location $repoRoot
37+
38+
function Write-Header($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan }
39+
function Write-Success($text) { Write-Host "[OK] $text" -ForegroundColor Green }
40+
function Write-WarningMessage($text) { Write-Host "[WARN] $text" -ForegroundColor Yellow }
41+
function Write-ErrorMessage($text) { Write-Host "[ERROR] $text" -ForegroundColor Red }
42+
function Write-Info($text) { Write-Host " $text" -ForegroundColor Gray }
43+
44+
function Test-WindowsHost {
45+
$isWindowsVariable = Get-Variable -Name IsWindows -ErrorAction SilentlyContinue
46+
if ($isWindowsVariable) {
47+
return [bool]$isWindowsVariable.Value
48+
}
49+
50+
return [System.Environment]::OSVersion.Platform -eq [System.PlatformID]::Win32NT
51+
}
52+
53+
function Update-ProcessPath {
54+
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
55+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
56+
$env:Path = @($machinePath, $userPath) -join ";"
57+
}
58+
59+
function Test-CommandAvailable($name) {
60+
return $null -ne (Get-Command $name -ErrorAction SilentlyContinue)
61+
}
62+
63+
function Test-DotNet10Sdk {
64+
if (-not (Test-CommandAvailable "dotnet")) {
65+
return $false
66+
}
67+
68+
$sdks = & dotnet --list-sdks 2>$null
69+
return $LASTEXITCODE -eq 0 -and ($sdks | Where-Object { $_ -match "^10\." })
70+
}
71+
72+
function Test-NodeAndNpm {
73+
return (Test-CommandAvailable "node") -and (Test-CommandAvailable "npm")
74+
}
75+
76+
function Get-WindowsSdkVersion {
77+
$windowsSdkPath = "${env:ProgramFiles(x86)}\Windows Kits\10\Include"
78+
if (-not (Test-Path $windowsSdkPath)) {
79+
return $null
80+
}
81+
82+
$versions = @(
83+
Get-ChildItem $windowsSdkPath -Directory |
84+
Where-Object { $_.Name -match "^\d+\.\d+\.\d+\.\d+$" } |
85+
Sort-Object { [version]$_.Name } -Descending |
86+
Select-Object -ExpandProperty Name
87+
)
88+
89+
if ($versions.Count -eq 0) {
90+
return $null
91+
}
92+
93+
return $versions[0]
94+
}
95+
96+
function Get-WebView2RuntimeVersion {
97+
$keys = @(
98+
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}",
99+
"HKCU:\SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}"
100+
)
101+
102+
foreach ($key in $keys) {
103+
if (Test-Path $key) {
104+
$version = (Get-ItemProperty $key -ErrorAction SilentlyContinue).pv
105+
if ($version) {
106+
return $version
107+
}
108+
}
109+
}
110+
111+
return $null
112+
}
113+
114+
function Install-WingetPackage($id, $displayName) {
115+
if ($CheckOnly) {
116+
Write-WarningMessage "$displayName is missing."
117+
Write-Info "Install with: winget install --id $id -e"
118+
return
119+
}
120+
121+
if (-not (Test-CommandAvailable "winget")) {
122+
throw "winget is not available. Install App Installer from the Microsoft Store, then rerun this script."
123+
}
124+
125+
Write-Header "Installing $displayName"
126+
$arguments = @(
127+
"install",
128+
"--id", $id,
129+
"-e",
130+
"--accept-source-agreements",
131+
"--accept-package-agreements",
132+
"--disable-interactivity"
133+
)
134+
& winget @arguments
135+
if ($LASTEXITCODE -ne 0) {
136+
throw "winget failed to install $displayName ($id)."
137+
}
138+
139+
Update-ProcessPath
140+
}
141+
142+
function ConvertTo-GitSafeDirectoryPath($path) {
143+
return ([System.IO.Path]::GetFullPath($path).TrimEnd("\") -replace "\\", "/")
144+
}
145+
146+
function Test-GitSafeDirectoryContains($path) {
147+
if (-not (Test-CommandAvailable "git")) {
148+
return $false
149+
}
150+
151+
$expected = (ConvertTo-GitSafeDirectoryPath $path).ToLowerInvariant()
152+
$safeDirectories = & git config --global --get-all safe.directory 2>$null
153+
if ($LASTEXITCODE -ne 0 -or -not $safeDirectories) {
154+
return $false
155+
}
156+
157+
foreach ($safeDirectory in $safeDirectories) {
158+
if ($safeDirectory -eq "*") {
159+
return $true
160+
}
161+
162+
$normalized = ($safeDirectory.Trim().TrimEnd("\", "/") -replace "\\", "/").ToLowerInvariant()
163+
if ($normalized -eq $expected) {
164+
return $true
165+
}
166+
}
167+
168+
return $false
169+
}
170+
171+
function Ensure-RepositoryTrust {
172+
if ($NoTrustRepository -or $CheckOnly -or -not (Test-CommandAvailable "git")) {
173+
return
174+
}
175+
176+
if (-not (Test-Path (Join-Path $repoRoot ".git"))) {
177+
return
178+
}
179+
180+
if (Test-GitSafeDirectoryContains $repoRoot) {
181+
Write-Success "Repository already trusted for GitVersion."
182+
return
183+
}
184+
185+
$safeDirectory = ConvertTo-GitSafeDirectoryPath $repoRoot
186+
Write-Info "Adding git safe.directory entry: $safeDirectory"
187+
& git config --global --add safe.directory $safeDirectory
188+
if ($LASTEXITCODE -ne 0) {
189+
throw "Failed to add git safe.directory entry for $safeDirectory."
190+
}
191+
Write-Success "Repository trusted for GitVersion."
192+
}
193+
194+
function Require-Prerequisite($name, $isAvailable, $packageId) {
195+
if ($isAvailable) {
196+
Write-Success "$name detected."
197+
return
198+
}
199+
200+
Install-WingetPackage $packageId $name
201+
}
202+
203+
if (-not (Test-WindowsHost)) {
204+
throw "OpenClaw Windows development requires Windows."
205+
}
206+
207+
Write-Header "OpenClaw developer setup"
208+
if ($CheckOnly) {
209+
Write-Info "CheckOnly mode: no packages will be installed and git safe.directory will not be changed."
210+
}
211+
212+
Update-ProcessPath
213+
214+
Require-Prerequisite "Git" (Test-CommandAvailable "git") "Git.Git"
215+
Require-Prerequisite ".NET 10 SDK" (Test-DotNet10Sdk) "Microsoft.DotNet.SDK.10"
216+
Require-Prerequisite "Node.js LTS with npm" (Test-NodeAndNpm) "OpenJS.NodeJS.LTS"
217+
Require-Prerequisite "Windows SDK 10.0.26100" ([bool](Get-WindowsSdkVersion)) "Microsoft.WindowsSDK.10.0.26100"
218+
219+
$webView2Version = Get-WebView2RuntimeVersion
220+
if ($webView2Version) {
221+
Write-Success "WebView2 Runtime detected ($webView2Version)."
222+
} else {
223+
Install-WingetPackage "Microsoft.EdgeWebView2Runtime" "WebView2 Runtime"
224+
}
225+
226+
Update-ProcessPath
227+
Ensure-RepositoryTrust
228+
229+
$missing = @()
230+
if (-not (Test-CommandAvailable "git")) { $missing += "Git" }
231+
if (-not (Test-DotNet10Sdk)) { $missing += ".NET 10 SDK" }
232+
if (-not (Test-NodeAndNpm)) { $missing += "Node.js LTS with npm" }
233+
if (-not (Get-WindowsSdkVersion)) { $missing += "Windows SDK 10.0.26100" }
234+
if (-not (Get-WebView2RuntimeVersion)) { $missing += "WebView2 Runtime" }
235+
236+
if ($missing.Count -gt 0) {
237+
Write-ErrorMessage "Setup is incomplete:"
238+
foreach ($item in $missing) {
239+
Write-Info "- $item"
240+
}
241+
242+
if (-not $CheckOnly) {
243+
Write-Info "If packages were just installed, open a new terminal and rerun .\scripts\setup-dev.ps1 -CheckOnly."
244+
}
245+
exit 1
246+
}
247+
248+
Write-Header "Repository prerequisite check"
249+
& "$repoRoot\build.ps1" -CheckOnly
250+
if ($LASTEXITCODE -ne 0) {
251+
exit $LASTEXITCODE
252+
}
253+
254+
if ($RunValidation) {
255+
Write-Header "Required validation"
256+
$env:OPENCLAW_REPO_ROOT = $repoRoot
257+
258+
& "$repoRoot\build.ps1"
259+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
260+
261+
dotnet build "$repoRoot\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj"
262+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
263+
264+
dotnet build "$repoRoot\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj"
265+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
266+
267+
dotnet test "$repoRoot\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj" --no-restore
268+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
269+
270+
dotnet test "$repoRoot\tests\OpenClaw.Tray.Tests\OpenClaw.Tray.Tests.csproj" --no-restore
271+
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
272+
}
273+
274+
Write-Success "OpenClaw developer setup is ready."

0 commit comments

Comments
 (0)