Add missing code style rules that codify the idioms already used in the
codebase, making IDE suggestions consistent for all contributors:
- csharp_style_prefer_range_operator / prefer_index_operator: prefer
range indexers ([..n], [n..], [^1]) over Substring / Length-based
arithmetic (directly reinforces the refactoring in GatewayUrlHelper)
- csharp_style_prefer_switch_expression: prefer switch expressions
over if/else chains (already used throughout Models.cs etc.)
- csharp_style_prefer_pattern_matching / prefer_not_pattern
- csharp_prefer_simple_using_statement: prefer declaration-style
'using var x = ...' without a nested block
- dotnet_style_prefer_simplified_boolean_expressions
- dotnet_style_prefer_inferred_tuple_names /
prefer_inferred_anonymous_type_member_names
No functional changes. 521 shared + 99 tray tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- GatewayUrlHelper: replace Substring calls with C# range indexers ([..n],
[n..]) and use string.Concat(ReadOnlySpan<char>, ...) for RemoveUserInfo
to avoid an intermediate string allocation
- SettingsData.FromJson: narrow bare catch to catch (JsonException) so
unexpected non-JSON errors (e.g. OutOfMemoryException) are not silently
swallowed and masked
521 shared + 99 tray tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When PreferStructuredCategories is false, classification skips structured metadata (Intent, Channel) and goes straight to user rules + keyword fallback.
5 new tests. 521 shared tests pass.
Reimplements #104 on current master.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
PR #105 introduced ToFrozenDictionary but missed the using directive
for the WinUI project. Fixes build break on master.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
HashSet lookups with OrdinalIgnoreCase instead of pattern matching on lowered strings.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Static compiled regexes for SanitizeHtml, FrozenDictionary for notification type map.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clears _isPendingApproval on disconnect/error, resets gateway state properly in OnSettingsSaved.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
UserRules from settings were never passed to Classify() — custom notification rules were silently ignored.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes overheight menu that could extend beyond screen.
- Add MenuSizingHelper for DPI-aware work area clamping
- Use GetDpiForMonitor with proper fallback chain
- Set ScrollViewer to Visible vertical scrollbar
- 4 regression tests for clamping, DPI conversion, and markup
Contributed by @NichUK
Removes unused private ClassifyNotification from GatewayClient; updates tests to call NotificationCategorizer directly instead of via reflection.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extracts ~200 lines of duplicated WebSocket lifecycle code into shared abstract base class.
- Template method pattern: base owns lifecycle, subclasses override hooks
- ProcessMessageAsync always async (Task.CompletedTask for sync gateway)
- Private _webSocket, subclasses use SendRawAsync only
- 20 new base class tests, 596 total pass
- 399 lines removed, net ~170 less production code
Closes#63
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Azure Trusted Signing dlib (x64) fails with exit code 3 when
running under emulation on the ARM64 runner (windows-11-arm).
Fix: Skip signing in the build/build-msix jobs for ARM64 targets.
Instead, sign ARM64 executables and MSIX packages in the release job,
which runs on windows-latest (x64) where the signing dlib works.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- GetSessionList_SortsMainSessionFirst now populates 3 sessions via
ParseSessions and verifies the main session is sorted first, instead
of only asserting an empty list.
- Added ParseSessionsPayload helper to expose the private parser.
- TaskbarAtBottom_TypicalScenario assertion changed from the tautological
'y + MenuHeight <= 1040 || y >= 0' to the strict 'y + MenuHeight <= 1040'.
571/571 tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 93-test Tray test suite (added in PR #45) was never wired into CI. Adds build + run steps for OpenClaw.Tray.Tests so all 571 tests validate on every push/PR.
Closes#58
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Fixes two bugs that together caused issue #55:
1. Remove the healthList.Count > 0 guard so ChannelHealthUpdated fires even when all channels are removed.
2. Dispatch StatusDetailWindow.UpdateStatus when the window is already open.
3 new unit tests added. 478/478 shared tests pass.
Closes#55
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The TestLogger was silently swallowing all gateway client errors,
so connection failures showed a generic 'Connection failed' message.
Now captures the last error/warning and displays the actual gateway
message (e.g. 'origin not allowed') in the status label.
Also keeps verbose logging to Debug.WriteLine for dbgview-mcp
diagnostics.
Add structured logging to under-instrumented windows and services:
- StatusDetailWindow, SettingsWindow, ActivityStreamWindow,
NotificationHistoryWindow, QuickSendDialog, WelcomeDialog,
UpdateDialog, ActivityStreamService
- Replace raw Debug.WriteLine in CanvasWindow with Logger
- Use appropriate log levels (ERROR/WARN/INFO/DEBUG)
Closes#52
- Remove input.json.bak (stray backup file)
- Update README project structure (remove old OpenClaw.Tray, add
WinUI and test projects)
- Fix installer URLs to point at openclaw/openclaw-windows-node
instead of old shanselman/moltbot-windows-hub
WinUI tray has full feature parity plus Node Mode, Activity Stream,
session management, cost tracking, localization, and modern XAML UI.
- Remove src/OpenClaw.Tray/ (28 files, ~4900 lines)
- Remove from solution file
- Remove WinForms build step from CI
Installer already only references WinUI. Settings.json format is
unchanged — no migration needed.
Closes#44
Add null check on actions.SendMessage before invoking in the agent
deep link path. Removes null-forgiving operator that would throw
NullReferenceException inside Task.Run if the handler wasn't wired.
Adds warning log for diagnosability.
Fixes#47
Based on PR #48 by @Alix-007, adapted for current codebase.
Add WinUI .resw resource files for proper multi-language support:
- Strings/en-us/Resources.resw with all extractable UI strings
- Strings/zh-cn/Resources.resw with Chinese Simplified translations
- x:Uid attributes on XAML elements for automatic resource lookup
- LocalizationHelper for C# runtime string localization
- Windows auto-selects language based on OS locale
Any language can now be added by contributing a new .resw file.
Addresses #40
Extract testable pure logic from WinUI tray app into shared helpers:
- MenuDisplayHelper: status icons, text truncation, provider formatting
- DeepLinkParser: URI parsing for openclaw:// deep links
- MenuPositioner: tray popup positioning calculations
Create tests/OpenClaw.Tray.Tests with comprehensive coverage for
all extracted helpers plus settings serialization round-trips.
Closes#43
Use Tag attribute on WinUI ComboBoxItems to decouple display text
from persisted settings values. Load and save by Tag instead of
Content in SettingsWindow.xaml.cs.
Add documentation comment in WinForms SettingsDialog.cs making
the persistence contract explicit.
Backward compatible — existing settings.json values already match
the Tag keys. Worst case for unknown values: falls back to Default.
Fixes#41
ExecApprovalPolicy.Evaluate: normalizedShell was allocated inside the
foreach loop for every rule that carries a shell filter. Moving the
computation before the loop avoids the repeated allocation on the hot
command-evaluation path.
NotificationCategorizer.MatchesRule: the static Regex.IsMatch overload
that accepts a matchTimeout bypasses .NET's internal regex cache, so a
new Regex object was compiled on every incoming notification for each
regex-pattern user rule. Replace with a ConcurrentDictionary<string,
Regex?> that caches compiled instances (constructed with the 100 ms
timeout so ReDoS protection is preserved). Subsequent notifications
reuse the same compiled automaton.
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Extract duplicated metacharacter quoting logic from LocalCommandRunner
and SystemCapability into a shared ShellQuoting static class, following
the established GatewayUrlHelper pattern.
- ShellQuoting.NeedsQuoting: single source of truth for metachar detection
- ShellQuoting.QuoteForShell: shell-aware quoting (cmd vs PowerShell)
- ShellQuoting.FormatExecCommand: display formatting for gateway
- IsShellMetachar: idiomatic switch expression instead of duplicated
multi-case switch blocks
- 43 new unit tests covering all metacharacters, escaping edge cases,
null handling, and both shell modes
Co-authored-by: danedane <1402819+danedane@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Address review findings from PRs #29 and #32:
- QuoteArgIfNeeded: check all shell metacharacters (& | ; < > etc.),
not just whitespace/quotes — prevents command injection
- cmd.exe: use doubled-quote escaping (%"%") instead of backslash
which cmd doesn't recognize
- FormatExecCommand: align metacharacter checks with QuoteArgIfNeeded
so system.run.prepare shows the same command that actually executes
- StringBuilder: add lock around stdout/stderr event handlers and
ToString() reads to prevent concurrent-access corruption
Co-authored-by: danedane <1402819+danedane@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The gateway's nodes 'run' action requires system.run.prepare to build
an execution plan before the actual run. Without it, the only path is
the 'invoke' action which has a hardcoded 30s gateway timeout — too
short for long-running tools like Copilot CLI.
system.run.prepare echoes back the argv, cwd, rawCommand, agentId,
and sessionKey without executing anything, enabling the gateway to
use its configurable timeout path for the actual execution.