Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d25951a
fix(chat): surface incompatible-gateway state when handshake lacks se…
github-actions[bot] May 21, 2026
53300ec
fix(chat): tighten composer padding symmetry and cap tool burst card …
kenehong May 21, 2026
53d6a3d
ui(chat): unify margins, radius, drop tool footer, outline tool cards
kenehong May 21, 2026
43ed8a3
ui(chat): align assistant continuation with tool cards + add top brea…
kenehong May 21, 2026
31c8dbe
ui(chat): strip phantom avatar gap, unify tool row padding/height, al…
kenehong May 21, 2026
65ba516
ui(chat): align continuation footer, soften tool hover, light tool tint
kenehong May 21, 2026
003afae
fix(chat): align timestamps with bubble inner text
kenehong May 21, 2026
ab97783
fix(chat): align user timestamp with bubble text (no avatar case)
kenehong May 21, 2026
85deafa
fix(chat): align Plain/FooterReframe tool bursts with assistant bubble
kenehong May 21, 2026
d70387f
feat(chat): default ToolBurstStyle to TaskList for collapsible bursts
kenehong May 21, 2026
7d6a8b1
Chat UI: tool card indent + token/ctx footer priority
kenehong May 21, 2026
1287011
Chat UI: tool cards below assistant/thinking, right-align with bubble
kenehong May 21, 2026
6301d78
Chat UI: align assistant bubble + tool card right edges
kenehong May 21, 2026
ccec251
Chat UI: revert assistant bubble to HAlign=Left (keep avatar/timestam…
kenehong May 21, 2026
00b4c59
Chat UI: revert tool card to HAlign=Left (avoid WinUI stretch-centers…
kenehong May 21, 2026
8add87d
Chat UI: anchor tool card left while stretching to MaxWidth via Auto/…
kenehong May 21, 2026
fa6f7ad
Chat UI: bind tool card width to assistant bubble's ActualWidth
kenehong May 21, 2026
c91f0bf
Chat UI: auto-collapse tool burst once all steps finish
kenehong May 21, 2026
725c390
Chat UI: align collapsed tool header with step rows, drop tool timestamp
kenehong May 21, 2026
5058dcc
chore(chat-ui): move assistant timestamp below tool burst
kenehong May 21, 2026
e8a4c2e
Revert "chore(chat-ui): move assistant timestamp below tool burst"
kenehong May 21, 2026
4c522a3
feat(chat-ui): nest single/collapsed tool burst inside assistant bubble
kenehong May 21, 2026
821171b
chore(chat-ui): widen nested tool card top gap to match bottom optically
kenehong May 21, 2026
612c26f
chore(chat-ui): drop tool card indent when no assistant bubble yet
kenehong May 21, 2026
7dd6bad
Address XAML review feedback for chat tool card
kenehong May 21, 2026
9b2ccbf
Promote 'preset1' values to baseline chat defaults
kenehong May 21, 2026
dc599d2
Align expanded tool section glyph with header chevron position
kenehong May 21, 2026
2eca2ce
Polish chat composer + fix reducer dedup across tool boundaries
kenehong May 21, 2026
a422330
Fix tool card Done pill clipping under narrow viewport
kenehong May 21, 2026
9f09e9b
Inset nested tool card from assistant bubble's corner arc
kenehong May 21, 2026
7737549
Add user bubble tone toggle (Accent / Secondary)
kenehong May 21, 2026
31c593f
Merge openclaw/master into feat/chatui-nested-tool
kenehong May 21, 2026
cfe3751
mxc: replace node bridge with direct wxc-exec.exe spawn
bkudiess May 21, 2026
4948a2d
mxc: drop dead OrcaCore namespace, flatten Direct/, remove unused hel…
bkudiess May 21, 2026
f142e25
fix(#494): fall back to host execution when sandbox unavailable
bkudiess May 21, 2026
9478e95
feat: tool metadata cache, tool call toggle, stale-closure fix, and p…
christineyan4 May 21, 2026
f3d3fd2
feat(tray): consolidate app version + add user feedback to Check for …
RBrid May 21, 2026
a89a045
mxc: drop OpenClaw-invented env/tool lists from the config builder
bkudiess May 21, 2026
d5639e7
mxc: restore env scrub using canonical openclaw security policy + UX …
bkudiess May 21, 2026
0adbc84
docs(mxc): document provenance + macOS-parity for env scrub and PATH …
bkudiess May 21, 2026
190a53f
refactor: remove dead recording handler and extract two static helper…
AlexAlves87 May 21, 2026
12416d2
feat: add ExecApprovalsCoordinator and ICanPresentEvaluator (#471)
AlexAlves87 May 21, 2026
e0224e3
test(tray): add unit tests for NodeCapabilityGating.GetLocalNodeCapab…
github-actions[bot] May 21, 2026
8446ea5
Add easy setup diagnostics logging (#497)
indierawk2k2 May 21, 2026
9de3ae0
feat(tray): consolidate app version and update feedback (#481)
shanselman May 21, 2026
6946b72
fix(uninstall): clean up empty WSL parent after VHD removal
github-actions[bot] May 21, 2026
8914780
fix(uninstall): allow preserved non-empty WSL parent
shanselman May 21, 2026
0f6e8fa
Resolve PR484 chat UI conflicts
shanselman May 21, 2026
eeab616
test(shared): add DeepLinkParser unit tests
github-actions[bot] May 21, 2026
650a058
fix(mcp): add missing Windows node tool descriptions
github-actions[bot] May 21, 2026
ea0c95a
mxc: revert added env scrub from BuildEnv (redundant with ExecEnvSani…
bkudiess May 21, 2026
d62a8f2
Resolve PR482 localization validation
shanselman May 21, 2026
7900589
docs(mxc): clean up stale comments
bkudiess May 21, 2026
0e3b970
loc: add x:Uid for chat and instances strings
github-actions[bot] May 21, 2026
927be39
mxc: delete UnavailableSandboxExecutor (dead since #494 fallback)
bkudiess May 21, 2026
35c54c9
mxc: small simplifications across the directory
bkudiess May 21, 2026
64186df
feat(mxc): replace node bridge with direct wxc-exec
shanselman May 22, 2026
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
6 changes: 6 additions & 0 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,12 @@ File-based logging with automatic rotation:
- Rotation: When log exceeds 5MB, old log → `openclaw-tray.log.old`
- Thread-safe: Uses lock for concurrent writes

**Easy-button setup diagnostics:**
- Human summary: `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\easy-setup-latest.txt`
- Machine-readable latest trace: `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\easy-setup-latest.jsonl`
- Per-run traces: `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\setup-*.jsonl`
- Contents are redacted and cover setup phases, WSL commands, pairing, gateway checks, repair, and remove lifecycle steps.

**Log Levels:**
- `INFO` - Normal operation (connections, events)
- `WARN` - Recoverable issues (reconnects, timeouts)
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,8 @@ openclaw-windows-node/
Settings are stored in:
- Settings: `%APPDATA%\OpenClawTray\settings.json`
- Logs: `%LOCALAPPDATA%\OpenClawTray\openclaw-tray.log`
- Easy-button setup summary: `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\easy-setup-latest.txt`
- Easy-button setup JSONL: `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\easy-setup-latest.jsonl`

Default gateway: `ws://localhost:18789`

Expand Down
3 changes: 3 additions & 0 deletions docs/SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ Download and install WebView2 from [Microsoft](https://developer.microsoft.com/m
- Make sure the OpenClaw gateway process is running.
- Check Windows Firewall — if your gateway runs on a different machine, allow inbound traffic on port 18789.
- See the log at `%LOCALAPPDATA%\OpenClawTray\openclaw-tray.log` for connection errors.
- For easy-button setup, repair, or remove failures, start with `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\easy-setup-latest.txt`; Copilot CLI/debugging tools can use `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\easy-setup-latest.jsonl`.

### "Not yet paired" message on reconnect

Expand All @@ -155,13 +156,15 @@ See [issue #81](https://github.com/openclaw/openclaw-windows-node/issues/81) for
- Make sure you paste the **entire** setup code — it's a single base64url-encoded string.
- Check for accidental leading/trailing whitespace.
- The code must be from a compatible gateway version. Try entering the gateway URL and token manually instead.
- If the easy-button setup flow generated the code, check `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\easy-setup-latest.txt` for the failing phase and next action.

### Connection test fails

- Verify the gateway URL is correct (e.g., `ws://localhost:18789` for local, or the full URL for remote).
- Check that your token is valid and hasn't expired.
- If the gateway is on another machine, ensure Windows Firewall allows traffic on the gateway port.
- See the log at `%LOCALAPPDATA%\OpenClawTray\openclaw-tray.log` for detailed error messages.
- Easy-button setup diagnostics keep per-run JSONL traces at `%LOCALAPPDATA%\OpenClawTray\Logs\Setup\setup-*.jsonl` and update `easy-setup-latest.txt`/`.jsonl` after each run.

### Wizard shows "offline"

Expand Down
26 changes: 26 additions & 0 deletions docs/VERSIONING.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,32 @@ By removing the hardcoded `FileVersion` and `AssemblyVersion` properties, they n
2. **Let GitVersion and CI control the version** - the csproj's `<Version>` is just a fallback for local development builds
3. **Test version detection** - after building, check the EXE properties to ensure FileVersion matches expectations
4. **Use semantic versioning** - tags should follow `v{major}.{minor}.{patch}` format (e.g., `v0.4.0`)
5. **Use `OpenClaw.Shared.AppVersionInfo` for any user-visible or wire-exposed version string** - never re-roll
`typeof(...).Assembly.GetName().Version` or hardcode literals like `"v0.1.0"`. `AppVersionInfo` is the single
source of truth driven by `<Version>`, used by the About page, Update dialog, support-context dump,
`device.info` capability, MCP `serverVersion` handshake, and the update-check diagnostics.

## Runtime Version Resolution (AppVersionInfo)

`src/OpenClaw.Shared/AppVersionInfo.cs` exposes:

- `AppVersionInfo.Version` → bare string, e.g. `"0.4.7"`
- `AppVersionInfo.DisplayVersion` → `"v"` prefix, e.g. `"v0.4.7"`

It resolves the version by:

1. Looking for the `OpenClaw.Tray.WinUI` assembly in the current `AppDomain` (so `dotnet test` and CLI siblings
still report the tray's version rather than the testhost / dotnet host).
2. Falling back to `Assembly.GetEntryAssembly()`, then to the Shared assembly.
3. Reading `AssemblyInformationalVersionAttribute` (preferred) or `AssemblyVersion`.
4. Stripping SourceLink build metadata (`+abc123`) **and** the SemVer pre-release suffix (`-beta.1`) so the
displayed value matches what Updatum compares (Updatum reads the numeric `AssemblyVersion` only).

For tests that need a deterministic value regardless of host process, set the `internal` test hook:

```csharp
AppVersionInfo.TestOverride = "9.9.9";
```

## References

Expand Down
13 changes: 13 additions & 0 deletions docs/WINDOWS_NODE_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ When the node connects, it advertises these capabilities:
- If you see "Camera access blocked", enable camera access for desktop apps in Windows Privacy settings
- Packaged MSIX builds will show the system consent prompt automatically

### MXC sandbox filesystem grants
- `system.run` with sandboxing enabled uses MXC AppContainer filesystem grants. The cwd is granted **read-only** automatically when it is not already covered by an explicit grant. If a command needs to write in its cwd, grant that folder read-write in Sandbox settings.
- As a temporary MXC compatibility workaround, OpenClaw adds read-only grants for the drive roots used by sandbox grants and shell startup. `cmd.exe` currently stats the drive root during startup; this workaround should be removed after MXC no longer requires it.
- MXC filesystem filtering requires NTFS-backed paths. ReFS volumes do not have the required filter-driver behavior, so grants on ReFS paths can fail with `Access is denied` even when the policy includes the path.
- MXC integration tests self-skip on GitHub Actions because MXC/AppContainer filesystem behavior depends on local Windows sandbox support. Run MXC tests from an NTFS-backed checkout/output folder on a local Windows machine after building the tray app so `wxc-exec.exe` has been copied into `OpenClaw.Tray.WinUI\bin\...\tools\mxc\<arch>\`:

```powershell
.\build.ps1
$env:OPENCLAW_RUN_INTEGRATION='1'
$env:OPENCLAW_WXC_EXEC='D:\github\moltbot-windows-hub\src\OpenClaw.Tray.WinUI\bin\Debug\net10.0-windows10.0.22621.0\win-x64\tools\mxc\x64\wxc-exec.exe'
dotnet test .\tests\OpenClaw.Shared.Tests\OpenClaw.Shared.Tests.csproj --filter "FullyQualifiedName~Mxc"
```

## Remaining Work (Roadmap)

1. ~~**system.run + exec approvals**~~ ✅ Implemented
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "openclaw-windows-node-mxc",
"version": "0.0.0",
"private": true,
"description": "Node bridge dependencies for the OpenClaw tray's MXC sandbox integration. The C# tray spawns node.exe with scripts under tools/mxc/ that consume the @microsoft/mxc-sdk to drive wxc-exec.exe.",
"description": "MXC sandbox dependency used by the OpenClaw tray build to copy wxc-exec.exe into the app output.",
"dependencies": {
"@microsoft/mxc-sdk": "^0.1.8"
}
Expand Down
6 changes: 5 additions & 1 deletion scripts/validate-wsl-gateway-uninstall.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ $settingsPath = Join-Path $appData "OpenClawTray\settings.json"
$logsDir = Join-Path $localAppData "OpenClawTray\Logs"
$execPolicyPath = Join-Path $localAppData "OpenClawTray\exec-policy.json"
$vhdDirPath = Join-Path $localAppData "OpenClawTray\wsl\$DistroName"
$wslParentDirPath = Join-Path $localAppData "OpenClawTray\wsl"

$autoStartRegKey = "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
$autoStartAppName = "OpenClawTray"
Expand Down Expand Up @@ -374,6 +375,7 @@ function Get-StateSnapshot {
settings_exists = (Test-Path -LiteralPath $settingsPath)
exec_policy_exists = (Test-Path -LiteralPath $execPolicyPath)
vhd_dir_exists = (Test-Path -LiteralPath $vhdDirPath)
wsl_parent_dir_exists = (Test-Path -LiteralPath $wslParentDirPath)
}
processes_openclaw = @()
}
Expand Down Expand Up @@ -477,6 +479,7 @@ function Get-Postconditions {
mcp_token_preserved = $mcpTokenPreserved
keepalives_absent = $keepalivesAbsent
vhd_dir_absent = (-not (Test-Path -LiteralPath $vhdDirPath))
wsl_parent_dir_absent = (-not (Test-Path -LiteralPath $wslParentDirPath))
}
}

Expand All @@ -488,7 +491,8 @@ function Get-Verdict {

# Required postconditions (device_key_file_preserved and mcp_token_preserved are advisory).
$required = @('wsl_distro_absent', 'autostart_cleared', 'setup_state_absent',
'device_token_cleared', 'keepalives_absent', 'vhd_dir_absent')
'device_token_cleared', 'keepalives_absent', 'vhd_dir_absent',
'wsl_parent_dir_absent')
$failedKeys = @($required | Where-Object { $Postconditions[$_] -ne $true })
$errCount = if ($null -eq $Errors) { 0 } else { @($Errors).Count }

Expand Down
32 changes: 23 additions & 9 deletions src/OpenClaw.Chat/ChatTimelineReducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,18 +191,32 @@ static ChatTimelineState UpsertAssistant(ChatTimelineState state, string text, b

if (replace && reconcilePrevious && state.Entries.Count > 0)
{
var lastIndex = state.Entries.Count - 1;
var last = state.Entries[lastIndex];
if (last.Kind == ChatTimelineItemKind.Assistant)
// Scan backward for the most recent Assistant entry — not just
// the very last one. The gateway can emit a final message
// event AFTER tool entries have been appended (text → tool →
// tool output → final text), in which case the immediate last
// entry is a ToolCall. Without this scan, the final message
// would create a brand-new assistant entry that duplicates
// the streaming text the user already saw before the tool ran.
for (var li = state.Entries.Count - 1; li >= 0; li--)
{
return state with
var candidate = state.Entries[li];
if (candidate.Kind == ChatTimelineItemKind.Assistant)
{
Entries = state.Entries.SetItem(lastIndex, last with
return state with
{
Text = text,
IsStreaming = streaming
})
};
Entries = state.Entries.SetItem(li, candidate with
{
Text = text,
IsStreaming = streaming
})
};
}
// Stop scanning once we hit a User entry — that's a turn
// boundary, the assistant entry above it belongs to a
// previous turn and must not be reconciled into.
if (candidate.Kind == ChatTimelineItemKind.User)
break;
}
}

Expand Down
101 changes: 101 additions & 0 deletions src/OpenClaw.Shared/AppVersionInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Reflection;

namespace OpenClaw.Shared;

/// <summary>
/// Single source of truth for the OpenClaw Companion app version that is
/// surfaced to users. Reads <see cref="AssemblyInformationalVersionAttribute"/>
/// (or falls back to <c>AssemblyVersion</c>) from the tray executable so every
/// UI/diagnostic/handshake site reports the same number driven by the csproj
/// <c>&lt;Version&gt;</c> property.
/// </summary>
/// <remarks>
/// Under <c>dotnet test</c> and inside CLI siblings, <see cref="Assembly.GetEntryAssembly"/>
/// is the host process (testhost / dotnet), not the tray exe — so we first
/// search the current <see cref="AppDomain"/> for the tray assembly by name.
/// Tests that need a deterministic value can set <see cref="TestOverride"/>.
/// </remarks>
public static class AppVersionInfo
{
private const string TrayAssemblyName = "OpenClaw.Tray.WinUI";

private static readonly string _version = ResolveVersion();

/// <summary>
/// Test-only override. When non-null, <see cref="Version"/> returns this
/// value instead of the reflected one, giving tests a deterministic
/// version string regardless of the host process.
/// </summary>
internal static string? TestOverride { get; set; }

/// <summary>Bare version string, e.g. <c>"0.4.7"</c>.</summary>
public static string Version => TestOverride ?? _version;

/// <summary>Version prefixed with <c>v</c>, e.g. <c>"v0.4.7"</c>.</summary>
public static string DisplayVersion => "v" + Version;

private static string ResolveVersion()
{
try
{
var assembly = FindTrayAssembly()
?? Assembly.GetEntryAssembly()
?? typeof(AppVersionInfo).Assembly;

var informational = assembly
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()
?.InformationalVersion;
if (!string.IsNullOrWhiteSpace(informational))
{
return NormalizeSemVer(informational);
}

var name = assembly.GetName().Version;
if (name != null)
{
// System.Version uses -1 for unspecified components; coerce to 0.
var build = Math.Max(0, name.Build);
var revision = Math.Max(0, name.Revision);
return revision == 0
? $"{name.Major}.{name.Minor}.{build}"
: $"{name.Major}.{name.Minor}.{build}.{revision}";
}

return "0.0.0";
}
catch
{
// Never let the type initializer fail — a TypeInitializationException
// would poison every future access from every caller.
return "0.0.0";
}
}

private static Assembly? FindTrayAssembly()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
if (string.Equals(asm.GetName().Name, TrayAssemblyName, StringComparison.Ordinal))
return asm;
}
return null;
}

private static string NormalizeSemVer(string s)
{
// Strip SourceLink build metadata, e.g. "0.4.7+abc123" -> "0.4.7".
var plus = s.IndexOf('+');
if (plus >= 0) s = s.Substring(0, plus);

// Strip the SemVer pre-release suffix, e.g. "0.4.7-beta.1" -> "0.4.7".
// This keeps the UI string aligned with Updatum, which compares the
// numeric AssemblyVersion only. Revisit if pre-release labels should
// ever be surfaced to users.
var dash = s.IndexOf('-');
if (dash >= 0) s = s.Substring(0, dash);

return s;
}
}

8 changes: 2 additions & 6 deletions src/OpenClaw.Shared/Capabilities/DeviceCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System.IO;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading.Tasks;

Expand Down Expand Up @@ -53,10 +52,7 @@ private NodeInvokeResponse HandleInfo()
{
Logger.Info("device.info");

var assembly = typeof(DeviceCapability).Assembly;
var version = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion
?? assembly.GetName().Version?.ToString()
?? "unknown";
var version = AppVersionInfo.Version;

return Success(new
{
Expand All @@ -65,7 +61,7 @@ private NodeInvokeResponse HandleInfo()
systemName = OperatingSystem.IsWindows() ? "Windows" : RuntimeInformation.OSDescription,
systemVersion = RuntimeInformation.OSDescription,
appVersion = version,
appBuild = assembly.GetName().Version?.ToString() ?? version,
appBuild = version,
locale = CultureInfo.CurrentCulture.Name
});
}
Expand Down
12 changes: 11 additions & 1 deletion src/OpenClaw.Shared/ExecApprovals/ExecApprovalV2Result.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ public enum ExecApprovalV2Code
AllowlistMiss,
UserDenied,
ValidationFailed,
ResolutionFailed
ResolutionFailed,
InternalError, // invariant violations and unexpected internal bugs detected at runtime
Allow, // coordinator approved; caller may execute the command
}

/// <summary>
Expand Down Expand Up @@ -50,5 +52,13 @@ public static ExecApprovalV2Result ValidationFailed(string reason)
public static ExecApprovalV2Result ResolutionFailed(string reason)
=> new(ExecApprovalV2Code.ResolutionFailed, reason);

public static ExecApprovalV2Result InternalError(string reason)
=> new(ExecApprovalV2Code.InternalError, reason);

public static ExecApprovalV2Result Allow()
=> new(ExecApprovalV2Code.Allow, "approved");

public bool IsAllow => Code == ExecApprovalV2Code.Allow;

public override string ToString() => $"{Code}: {Reason}";
}
Loading
Loading