* feat: winnode CLI for invoking node commands over local MCP
Mirrors `openclaw nodes invoke`'s flag surface but routes to the local
tray's MCP HTTP server (default http://127.0.0.1:8765/) instead of the
gateway. `--node` and `--idempotency-key` are accepted for paste-from-
gateway parity and ignored.
Ships skill.md alongside winnode.exe documenting every supported
command, argument schema, and the A2UI v0.8 JSONL grammar for agent use.
Tests: 62 cases, 100% line/branch on CliRunner via in-process unit tests
plus a loopback HttpListener fake that exercises the full HTTP path.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(test): gate MCP readiness on token-bearing client
InitializeAsync would return ready as soon as `GET /` returned 200, even
if `mcp-token.txt` had not been read yet. Against a tray binary built
before the auth-before-dispatch hardening (where `GET /` answers 200
without auth), this raced ahead and handed back a tokenless `Client` —
every subsequent POST then 401'd. Restructure the loop to require both
the token-on-disk and a 200 from a token-bearing GET before declaring
ready.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(winnode): auto-load MCP bearer token
The CLI now sends `Authorization: Bearer <token>` on every MCP request,
without the user having to plumb the token themselves. Resolution chain
mirrors the per-tool secret convention (gh, az, anthropic):
1. `--mcp-token <literal>` flag
2. `OPENCLAW_MCP_TOKEN` env var (literal)
3. `mcp-token.txt` under `$OPENCLAW_TRAY_DATA_DIR` if set, else
`%APPDATA%\OpenClawTray\` — the same location SettingsManager
points the tray at, so a sandboxed tray is found automatically.
When the token comes from disk, run `McpAuthToken.VerifyAcl` (the same
hygiene check `NodeService.StartMcpServer` runs at startup) and route
any owner/DACL warning to stderr so the user knows to rotate. `--verbose`
reports the resolved auth source without echoing the secret value.
Tests redirect via `OPENCLAW_TRAY_DATA_DIR` to a temp sandbox dir so they
don't pick up the developer machine's real tray token.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(winnode): apply 19 review findings (F-01..F-21)
Hardens the winnode CLI against the threat model in
C:/temp/winnode-cli-review-2026-04-30/01-findings.md. F-15 (port-0 nit)
was approved as no-action; F-17 was a positive observation.
- F-01/F-09: validate --mcp-url; refuse auto-loaded token off-loopback
- F-02: explicit SocketsHttpHandler with AllowAutoRedirect=false
- F-03: cap response body at 16 MiB with explicit overflow message
- F-04: warn unconditionally when --mcp-token is used (process-listing leak)
- F-05: warn unconditionally when --idempotency-key is supplied
- F-06: TokenLooksValid ASCII-printable check; ignore corrupt tokens
- F-07: don't echo full token-file path in --verbose
- F-08: canonicalize OPENCLAW_TRAY_DATA_DIR; reject symlink redirect
- F-10: RunAsyncTests is now IDisposable (cleans up sandbox dir)
- F-11: SkillMdDriftTests + REGENERATE-ME header in skill.md;
McpToolBridge.KnownCommands exposes the canonical command set;
skill.md re-synced with live capability surface
- F-12: --params @<path> loads JSON object from disk
- F-13: Token_file_with_wide_acl_emits_warn (Windows-only, gracefully
skips when SetAccessControl is denied by hardened CI)
- F-14: BuildToolsCallBody returns (byte[], int) consumed by
ByteArrayContent without a string round-trip
- F-16+F-21: SanitizeForStderr strips control chars, redacts ≥32-char
base64url runs, caps at 4 KiB, default-quiet first-line-only,
full sanitized body under --verbose
- F-18: --invoke-timeout capped at 600000 ms; long arithmetic on the
+5000 buffer; out-of-range exits 2
- F-19: --mcp-port and OPENCLAW_MCP_PORT bounded [1, 65535]; env-var
out-of-range falls back to default with a verbose warning
- F-20: distinguish missing/empty/unreadable/loaded token-file states;
unreadable exits 1 with a diagnostic before any HTTP traffic
Tests: 23 added (115/115 pass). All other suites stay green
(Shared 1046/1066, Tray 245/245, Integration 18/18, UI 62/62).
WinNode CLI line coverage: 91.6% (434/474 in Program.cs).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Enable WindowsAppSDKSelfContained for registration-free WinRT activation
- Build script auto-detects architecture (win-arm64 / win-x64)
- WebChatWindow shows error details instead of silently falling back
- Remove conditional self-contained (always needed for WebView2)
The issue: WebView2 COM activation fails in unpackaged apps because
WinRT registration isn't available. Self-contained mode bundles the
'Undocked RegFree WinRT' which enables COM activation without MSIX.
- build.ps1 now checks for WebView2 Runtime
- README lists all prerequisites for WinUI build
- WebView2 check is informational (app falls back to browser)
- build.ps1 checks for .NET 9, .NET 10, Windows SDK
- Provides clear guidance on what's missing
- Supports building individual projects or all
- Fix README: correct dotnet run command (needs full .csproj path)
- Expanded Quick Start section with multiple build options
Fixes#10