Commit Graph

201 Commits

Author SHA1 Message Date
Scott Hanselman
1960a436e0
fix: address WinUI3 code review findings (#203)
Some checks failed
Build and Test / test (push) Has been cancelled
Build and Test / build (win-arm64) (push) Has been cancelled
Build and Test / build (win-x64) (push) Has been cancelled
Build and Test / build-msix (ARM64, win-arm64) (push) Has been cancelled
Build and Test / build-msix (x64, win-x64) (push) Has been cancelled
Build and Test / build-extension (arm64) (push) Has been cancelled
Build and Test / build-extension (x64) (push) Has been cancelled
Build and Test / release (push) Has been cancelled
- Add AutomationProperties.AutomationId to all interactive controls
- Add AutomationProperties.Name to 4 icon-only WebChat toolbar buttons
- Localize all SetupWizard strings via LocalizationHelper + .resw
- Replace hardcoded colors with ThemeResource brushes
- Replace raw FontSize with typography styles where appropriate

Prepares codebase for automated UI testing (winapp ui) and
proper Dark/Light/HighContrast theme support.
2026-04-22 22:28:53 -07:00
Scott Hanselman
5ecaa8427b fix: widen WebChat toolbar buttons to prevent icon clipping
Increase button width from 36 to 40px with explicit padding,
bump FontIcon size from 14 to 16, add right padding to toolbar.
2026-04-22 18:29:50 -07:00
Scott Hanselman
f235f3f999
feat: add setup wizard with pairing-aware connection test (#201)
Add SetupWizardWindow with 3-step onboarding flow:
- Step 1: Paste setup code (auto-decodes URL+token) or manual entry
  with connection test that understands pairing-required as success
- Step 2: Optional node mode with device ID and approve instructions
- Step 3: Done - saves settings and reconnects

Integration:
- Replace WelcomeDialog with wizard on first run (empty token)
- Add Setup Guide menu item to tray menu
- Add openclaw://setup deep link
- Guard node service against empty token (no crash)
- Contextual error messages for token mismatch, origin rejection,
  rate limiting, and pairing required

Addresses #199
2026-04-22 18:22:28 -07:00
Scott Hanselman
9134be49e1 test: strengthen audit coverage and enable CI integration checks
Some checks failed
Build and Test / test (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Build and Test / build (win-arm64) (push) Has been cancelled
Build and Test / build (win-x64) (push) Has been cancelled
Build and Test / build-msix (ARM64, win-arm64) (push) Has been cancelled
Build and Test / build-msix (x64, win-x64) (push) Has been cancelled
Build and Test / build-extension (arm64) (push) Has been cancelled
Build and Test / build-extension (x64) (push) Has been cancelled
Build and Test / release (push) Has been cancelled
Tighten weak assertions identified during the three-model test-suite audit and enable the Shared integration test lane in CI.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-19 22:55:52 -07:00
github-actions[bot]
0852ffee2e
perf: eliminate LINQ allocations in command execution path (#185)
Replace LINQ Select/Any with direct loops in three hot-path sites:

- ShellQuoting.FormatExecCommand: argv.Select(FormatSingleArg) →
  preallocated string[] + for loop; avoids enumerator state-machine alloc
- LocalCommandRunner.BuildProcessArgs: request.Args.Select(...) →
  preallocated string[] + for loop; same fix; drop unused System.Linq import
- ExecApprovalPolicy.Evaluate: rule.Shells.Any(...) → foreach with early
  break; avoids closure + enumerator alloc on every command approval check

All three sites are in the system.run execution path and can be called
repeatedly. The for-loop variants are allocation-free for the iteration
itself and improve throughput under load.

586 Shared + 122 Tray tests pass.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:43 -04:00
github-actions[bot]
fd384574f5
fix: use integer truncation in FormatAge to prevent rounding past display boundaries (#183)
Math.Round with C# default banker's rounding (MidpointRounding.ToEven) can
produce counterintuitive results at display-threshold boundaries:

- 59.5 minutes -> Math.Round(59.5) = 60 -> displayed as '60m ago'
  instead of '59m ago' (or the correct transition to '1h ago')
- 47.5 hours -> Math.Round(47.5) = 48 -> displayed as '48h ago'
  instead of '47h ago' (near the 48h/days boundary)

Using integer truncation ((int)delta.TotalX) matches the idiomatic
convention for age display: show the floor of the elapsed time, which
is consistent, predictable, and never exceeds the guard condition.

Adds three regression tests covering:
- 59.5-minute boundary (was '60m ago', now '59m ago')
- 47.5-hour boundary (was '48h ago', now '47h ago')
- Exactly 60 seconds (correctly '1m ago')

Test status: Shared.Tests 589 passed, 20 skipped; Tray.Tests 122 passed.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:39 -04:00
github-actions[bot]
e9f18f78c9
improve: use SearchValues<char> in ShellQuoting.NeedsQuoting for SIMD scan (#182)
Replace per-character foreach+switch with System.Buffers.SearchValues<char>,
which uses SSE2/AVX2 vectorized IndexOfAny to detect shell metacharacters
in a single SIMD pass instead of a character-by-character loop.

- Add static SearchValues<char> s_shellMetachars (same 25-char set as former
- Replace NeedsQuoting foreach body with arg.AsSpan().IndexOfAny(s_shellMetachars) >= 0
- Remove private IsShellMetachar helper (no longer needed)
- Add using System.Buffers (SearchValues<T> namespace)

SearchValues<char> is available since .NET 8; this project targets net10.0.
All existing ShellQuotingTests pass unchanged.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:35 -04:00
github-actions[bot]
eeac8d5c7c
perf: replace HashSet/ToLowerInvariant with FrozenSet/FrozenDictionary in ChannelHealth; eliminate List<string> in GatewayUsageInfo.DisplayText (#175)
ChannelHealth:
- s_healthyStatuses/s_intermediateStatuses: HashSet<string> → FrozenSet<string>
  (O(1) lookup, no per-call allocation; consistent with existing FrozenDictionary
  pattern in NotificationCategorizer and OpenClawGatewayClient)
- DisplayText: Status.ToLowerInvariant() switch → FrozenDictionary<string,string>
  s_statusLabels lookup (eliminates the lowercase string allocation on every
  channel health update)

GatewayUsageInfo.DisplayText:
- List<string>/string.Join → nullable string?[4] accumulator + string.Join
  overload with offset+count (single array allocation; no List wrapper)

Tests (+8):
- ChannelHealth.DisplayText_CaseInsensitiveLabelLookup (6 cases: RUNNING,
  Connected, READY, NOT CONFIGURED, Connecting, STOPPED)
- GatewayUsageInfo.DisplayText_PreservesPartOrder_TokensBeforeCostBeforeRequests
- GatewayUsageInfo.DisplayText_ModelOnlyWithTokens_SeparatedBySeparator

Result: 594 Shared passed, 20 skipped; 122 Tray passed (was 586+122)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:31 -04:00
github-actions[bot]
c9255f9d94
perf: eliminate List<string> allocation in BuildProviderSummary (#173)
BuildProviderSummary is called on every gateway usage-status update.
The previous implementation allocated a List<string> (up to 3 entries)
and called string.Join, producing a heap-allocated list wrapper plus
join enumeration in addition to the final string.

With at most 2 provider slots + an optional overflow suffix, the
combinations fit into two nullable string variables and a switch
expression, producing only the final string allocation.

Also adds 9 unit tests covering all branch paths:
empty providers, single-provider with usage/error/no-windows,
two providers, three providers (overflow), missing display name,
all-empty providers, and overflow with one valid provider.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:27 -04:00
github-actions[bot]
5dade750e3
perf: eliminate List<T> allocations in SessionInfo.DisplayText and GetSessionListInternal (#172)
SessionInfo.DisplayText: replace List<string> + string.Join with direct
string interpolation. With at most 3 segments (prefix, channel,
activity/status), the allocation-free conditional composition avoids a
List heap alloc plus the Join enumeration on every tray UI update.

GetSessionListInternal: replace new List(_sessions.Values).ToArray()
with a single direct array allocation + Values.CopyTo + Array.Sort with
a static comparer. Drops one heap allocation per session list query.

Both changes are under the covered test surface (586 Shared + 122 Tray).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:22 -04:00
github-actions[bot]
5ee75410d8
perf: eliminate List<string> allocation in SessionInfo.RichDisplayText (#176)
RichDisplayText is called for every active session on every tray menu
rebuild, tooltip update, and activity stream refresh. The previous
implementation allocated a List<string> (up to 9 entries), populated
it via nine conditional Add() calls, then called string.Join to produce
the final string.

Replace with a stack-discipline string?[9] array accumulator + the
string.Join(string, string?[], int, int) overload that takes an offset
and count, producing a single array allocation with no List wrapper.

Tests (+4):
- RichDisplayText_PreservesPartOrder_ChannelModelCtxThinkVerboseSystemAbortedActivity
  (all 9 slots populated; verifies channel<model<ctx<think<verbose<system<aborted<activity)
- RichDisplayText_ActivityTakesPrecedenceOverStatus
- RichDisplayText_IncludesContextSummary_WhenBothTokensSet
- RichDisplayText_OmitsContextSummary_WhenContextTokensZero

Result: 590 Shared passed, 20 skipped; 122 Tray passed (was 586+122)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:18 -04:00
github-actions[bot]
897a743b13
improve: eliminate List(T) allocation in GatewayNodeInfo.DetailText; +2 edge-case tests (#178)
Replace the 5-slot List<string> accumulator in GatewayNodeInfo.DetailText
with a fixed-size string?[5] array joined via string.Join(sep, array, 0, count).
This avoids a heap allocation on every node-list render, consistent with the
same pattern applied to GatewayUsageInfo.DisplayText and SessionInfo.RichDisplayText.

Also add two missing tests:
- DetailText_IgnoresWhitespaceOnlyModeAndPlatform
- DetailText_IgnoresZeroCounts

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:13 -04:00
github-actions[bot]
cf027fb269
test: add ParseChannelHealth status-derivation and property-parsing coverage (#174)
ParseChannelHealth has six distinct status derivation paths based on the
combination of running/configured/linked/probeOk/hasError flags, plus
separate property-parsing for error/authAge/type/IsLinked fields. Only
the 'status field takes priority' path was previously tested.

Adds 13 new tests covering:

Status derivation:
- running=true → 'running'
- lastError present (non-null) → 'error'
- lastError=null excluded from hasError → falls through to 'ready'
- configured && probe.ok=true → 'ready'
- configured && linked=true → 'ready' + IsLinked=true
- nothing set → 'not configured'
- error priority over running flag

Property parsing:
- error, authAge, type fields populated correctly
- linked=false → IsLinked=false
- probe.ok=false + configured=true → 'ready' (second ready clause)

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:09 -04:00
github-actions[bot]
fbc193b22f
eng: fix three compiler/analyzer warnings in test suite (#177)
- CS8767 (ExecApprovalPolicyTests): ExecTestLogger.Error parameter 'ex'
  was declared as non-nullable 'Exception' but IOpenClawLogger.Error
  uses 'Exception? ex = null'; align signature to match interface
- CS8604 (OpenClawGatewayClientTests): SetPrivateField takes 'object'
  (non-nullable) but SetOperatorDeviceId passes 'string?'; change
  parameter to 'object?' to match realistic usage
- xUnit2013 (CapabilityTests): Assert.Equal(0, collection.Count()) is
  an anti-pattern flagged by the xUnit analyser; use Assert.Empty

Test status: 586 Shared + 122 Tray pass, 0 warnings

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-20 01:36:05 -04:00
Scott Hanselman
139ad8e09e chore: upgrade agentic workflows to v0.68.7
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-19 21:51:19 -07:00
Scott Hanselman
e772b5e6e9 security: harden system.run approval checks
Closes #184 by blocking dangerous environment overrides and by re-evaluating nested shell-wrapper payloads and chained commands against the exec approval policy.

This extends the partial env-only approach discussed in PR #186 so the Windows node closes both vectors called out in the issue.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-19 21:33:02 -07:00
Scott Hanselman
e46dfe7830 chore: upgrade and recompile agentic workflows
Some checks failed
Build and Test / test (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Build and Test / build (win-arm64) (push) Has been cancelled
Build and Test / build (win-x64) (push) Has been cancelled
Build and Test / build-msix (ARM64, win-arm64) (push) Has been cancelled
Build and Test / build-msix (x64, win-x64) (push) Has been cancelled
Build and Test / build-extension (arm64) (push) Has been cancelled
Build and Test / build-extension (x64) (push) Has been cancelled
Build and Test / release (push) Has been cancelled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 19:26:41 -07:00
github-actions[bot]
be94c4bdfe
improve: use FrozenDictionary for NotificationCategorizer lookup tables (#171)
ChannelMap, IntentMap, and CategoryTitles are static read-only lookup
tables that are never mutated after startup. Switching from plain
Dictionary to FrozenDictionary is consistent with the pattern already
used in OpenClawGatewayClient (s_toolKindMap) and App.xaml.cs
(s_notifTypeMap), and gives optimal read-time performance with
guaranteed immutability.

All 606 Shared.Tests (586 pass, 20 skip) and 122 Tray.Tests pass.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 18:32:47 -07:00
Scott Hanselman
b1523956fe
ci: add NuGet cache to build-extension job (#170)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 18:09:45 -07:00
Scott Hanselman
7a95b190f1
test: cover websocket auto-reconnect regression
Add regression coverage locking in the reconnect loop behavior on current master and increasing confidence around issue #149.
2026-04-13 17:55:48 -07:00
github-actions[bot]
9a35c78715
perf: use ArrayPool in WebSocketClientBase.SendRawAsync
Use pooled UTF-8 buffers in SendRawAsync to reduce allocations in the hot send path while preserving behavior.
2026-04-13 14:00:40 -07:00
github-actions[bot]
c852c72e13
refactor: remove write-only _nodes dictionary
Remove unused write-only _nodes state from OpenClawGatewayClient to simplify internals with no behavior change.
2026-04-13 13:59:42 -07:00
Scott Hanselman
2d98d4fd1c chore: upgrade repo-assist workflow to v0.68.1
Some checks failed
Build and Test / test (push) Has been cancelled
Build and Test / build (win-arm64) (push) Has been cancelled
Build and Test / build (win-x64) (push) Has been cancelled
Build and Test / build-msix (ARM64, win-arm64) (push) Has been cancelled
Build and Test / build-msix (x64, win-x64) (push) Has been cancelled
Build and Test / build-extension (arm64) (push) Has been cancelled
Build and Test / build-extension (x64) (push) Has been cancelled
Build and Test / release (push) Has been cancelled
- Better git checkout (fetch all branches, full history)
- Status messages for workflow progress
- Protected files fallback to issue
- Rainbow footer with workflow run links
- Min integrity: none for full issue/PR access
2026-04-11 08:38:43 -07:00
github-actions[bot]
87e5cd8c75
improve: add GetStringArrayArg and expand node tests
Some checks failed
Build and Test / test (push) Has been cancelled
Build and Test / build (win-arm64) (push) Has been cancelled
Build and Test / build (win-x64) (push) Has been cancelled
Build and Test / build-msix (ARM64, win-arm64) (push) Has been cancelled
Build and Test / build-msix (x64, win-x64) (push) Has been cancelled
Build and Test / build-extension (arm64) (push) Has been cancelled
Build and Test / build-extension (x64) (push) Has been cancelled
Build and Test / release (push) Has been cancelled
Adds a reusable string-array parsing helper for node capabilities, keeps system.which whitespace handling intact, and expands Shared coverage for helper parsing and WindowsNodeClient behavior.
2026-04-09 17:43:43 -07:00
github-actions[bot]
8944d59744
eng: add tests/Directory.Build.props to DRY up shared test config
Deduplicates the shared test project configuration into tests/Directory.Build.props without changing test behavior or package versions.
2026-04-09 17:39:59 -07:00
Scott Hanselman
674b0e5a83
fix: handle node pairing approval events safely
Semantically ports the useful pairing-event support from #144 onto current master while preserving the safer hello-ok fallback behavior and keeping the #150 logging cleanup intact.
2026-04-09 14:45:04 -07:00
Scott Hanselman
dfec59dd61
ci: collect coverage and upload test results in CI (#160)
Enable XPlat Code Coverage and TRX logs for both test projects in the CI test job, and upload the combined TestResults artifact on every run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 15:29:55 -07:00
T
5e39f26be3
feat(tray): add Dutch (nl-NL) localization (#155)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 14:30:42 -07:00
github-actions[bot]
1b46b755cc
improve: remove debug scaffolding and double-serialization from WindowsNodeClient (#150)
- Remove verbose debug block in SendNodeConnectAsync that logged sensitive
  data (auth token prefix, Ed25519 signature, full connect payload) on every
  connect attempt; this ran in all builds, not just debug.
- Eliminate redundant BuildDebugPayload() call (was called twice: once for
  logging, once inside SignPayload) — only the SignPayload call remains.
- Remove duplicate JSON serialization: msg was serialized twice (once with
  WriteIndented for the debug log, once compact to actually send); collapse
  to a single compact serialization and remove the now-unused s_indentedOptions
  field.
- Remove HandleResponse debug log that allocated a full payload string just
  to truncate it for a Debug()-level message.

No functional change; connection, signing, and registration behaviour are
identical. Test status: 525 Shared passed, 99 Tray passed.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 14:22:46 -07:00
github-actions[bot]
5b57ebdb72
test: expand WindowsNodeClient coverage with 10 new unit tests (#157)
Adds tests for previously uncovered scenarios:
- HandleResponse with stored device token fires Paired (not Pending)
- HandleResponse with ok:false raises ConnectionStatus.Error
- HandleResponse with missing payload does not throw
- ShortDeviceId truncates to 16-char prefix of FullDeviceId
- IsPaired returns false when no device token stored
- IsPaired returns true after device token stored
- RegisterCapability adds to Capabilities list and Registration
- RegisterCapability deduplicates categories in NodeRegistration
- IsPendingApproval is false initially
- FullDeviceId is non-empty after construction

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 13:09:29 -07:00
github-actions[bot]
5c76d47844
refactor: extract FormatAge into ModelFormatting, removing duplicate logic (#154)
GatewayNodeInfo and SessionInfo both implemented identical age-formatting
logic (just now / Xm ago / Xh ago / Xd ago). Consolidate into a single
ModelFormatting.FormatAge(DateTime) helper.

- Add ModelFormatting.FormatAge(DateTime timestampUtc)
- SessionInfo.AgeText now delegates to ModelFormatting.FormatAge
- GatewayNodeInfo.FormatAge now delegates to ModelFormatting.FormatAge
- No behaviour change; all existing tests pass (525 Shared, 99 Tray)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 13:09:23 -07:00
github-actions[bot]
2be59b920e
improve: SettingsData.FromJson null safety + GatewayUrlHelper static array (#148)
Two small coding improvements:

1. SettingsData.FromJson(string? json) — null/empty guard
   - Change parameter from non-nullable 'string' to 'string?'
   - Return null immediately for null or empty input rather than
     forwarding to JsonSerializer.Deserialize which would throw
     ArgumentNullException (not caught by the existing JsonException
     handler)
   - Adds two regression tests: null and empty string both return null

2. GatewayUrlHelper.RemoveUserInfo — static readonly char array
   - Replace the inline 'new[] { '/', '?', '#' }' array literal
     in RemoveUserInfo with a 'private static readonly' field
   - The method is called on every URL normalisation and display
     sanitisation; the previous form allocated a new char array on
     each call, which is now eliminated

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 13:09:17 -07:00
github-actions[bot]
b202642137
fix: ClassifyByKeywords recognises CI/CD patterns (ci/ prefix) (#147)
'CI/CD pipeline completed' was not classified as build because
ClassifyByKeywords only checked for 'ci ' (trailing space), missing
the 'CI/CD' style where a slash follows 'CI'.

Adding the 'ci/' check catches:
  - 'CI/CD pipeline completed'
  - 'CI/CD workflow triggered'
  - Any pattern where 'CI' is immediately followed by a slash

False-positive analysis: common English words containing 'ci' (recipe,
social, special, facial...) are followed by letters, not '/', so the
new check introduces no false matches.

Regression tests: two new InlineData entries in KeywordFallback_BackwardCompatible.

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 13:09:11 -07:00
github-actions[bot]
dbfe57d8cf
test: add BuildMissingScopeFixCommands and BuildPairingApprovalFixCommands tests (#145)
Add 15 unit tests covering the two public error-message builder methods on
OpenClawGatewayClient that previously had zero test coverage.

BuildMissingScopeFixCommands (10 tests):
- Null/empty/whitespace scope defaults to 'operator.write'
- Specific scope is preserved in output
- Empty _grantedOperatorScopes shows '(none reported by gateway)' placeholder
- Populated scopes are listed correctly
- _operatorDeviceId present vs absent (placeholder shown when missing)
- node.* scopes trigger 'node token' warning (case-insensitive match)
- Operator-only scopes produce no node-token warning

BuildPairingApprovalFixCommands (5 tests):
- _operatorDeviceId used when set
- Falls back to DeviceIdentity.DeviceId when _operatorDeviceId is null
- Empty scopes shows '(none reported by gateway yet)' placeholder
- Populated scopes are listed correctly
- Output always contains pairing approval instructions

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 13:09:05 -07:00
github-actions[bot]
4c3466ac6c
test: add MenuSizingHelper unit tests + ShellQuoting edge cases (#142)
Add dedicated MenuSizingHelperTests.cs covering all edge cases
for CalculateWindowHeight and ConvertPixelsToViewUnits:
- zero/negative pixel inputs
- zero DPI fallback to 96
- DPI scaling (96, 120, 144, 192)
- minimum height enforcement and clamping
- negative/zero work area handling
- minimum larger than work area

Extend ShellQuotingTests with previously untested metacharacters
(], }, )), newline/carriage-return chars, and FormatExecCommand
with single and empty argument arrays.

Tray tests: 99 → 120 passed
Shared tests: 525 → 531 passed (551 total, 20 skipped)

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 13:08:59 -07:00
AlexAlves87
69332397a2
fix: use invariant culture in numeric display formatting (#158)
* fix: use invariant culture in token and cost display formatting

FormatLargeNumber and CostUsd were using the current thread culture,
producing "2,5M" or "$0,25" on non-English locales instead of the
expected "2.5M" / "$0.25".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add regression for invariant culture numeric formatting

Covers non-English culture formatting so token and cost display stays
as 2.5M / .25 rather than locale-specific 2,5M / ,25.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: AlexAlves87 <alexalves87@github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Scott Hanselman <scott@hanselman.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-08 12:52:32 -07:00
github-actions[bot]
bd0a7b0d67
perf: replace List.Insert(0) with LinkedList.AddFirst in ActivityStreamService
O(1) prepend instead of O(n) for activity stream items. Significant win with MaxItems=200.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 09:57:45 -07:00
github-actions[bot]
31b54af3b8
perf: make ExecApprovalPolicy regex cache thread-safe (ConcurrentDictionary)
Replaces Dictionary with static ConcurrentDictionary.GetOrAdd() for thread-safe glob-to-regex caching. Removes unnecessary cache invalidation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 09:57:40 -07:00
Scott Hanselman
b7d15591cf
fix: signature fallback retries connect + SSH tunnel auto-restart (closes #131, #132)
- #131: Cache challenge nonce, re-send connect after signature mode fallback
- #132: Clean up on unexpected SSH exit, raise TunnelExited event, auto-restart with 3s delay

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 09:53:13 -07:00
Scott Hanselman
55960c88b5
ci: add NuGet cache to build and build-msix CI jobs (closes #135)
Reuses same cache key as test job. Saves redundant NuGet downloads on build runners.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-02 09:50:43 -07:00
Scott Hanselman
4ffd028a02 test: harden test assertions and fix GetIntArg overflow bug
Test hardening (Codex code-review findings across 8 test files):

- OpenClawGatewayClientTests: constructor tests now verify URL normalization instead of just NotNull; renamed misleading GetSessionList_SortsMainSessionFirst to _ReturnsEmpty_ForFreshClient

- ExecApprovalPolicyTests: ExecApprovalsGet now deserializes and asserts enabled/defaultAction/rules fields; WithoutPolicy test verifies enabled=false

- CapabilityTests: screen list test validates screens[] array fields (name/primary/bounds); camera list validates cameras[] entries (DeviceId/Name/IsDefault); error-path tests assert 'not available' error message

- NodeCapabilitiesTests: ExecuteAsync verifies payload echoes command; CanSet verifies payload value; added Int32 overflow test

- DeviceIdentityTests: signature length tightened from >40 to ==86; public key test decodes base64url and asserts 32-byte Ed25519 length

- GatewayUrlHelperTests: added malformed percent-encoding negative tests; tightened ValidationMessage assertion

- ReadmeValidationTests: validates ALL matching allowCommands JSON blocks, not just first

- ModelsTests: replaced overly permissive OR assertion with platform-aware exact checks

Production fix:

- NodeCapabilities.GetIntArg: catch FormatException on Int32 overflow and return default value instead of throwing
2026-04-01 13:55:25 -07:00
Scott Hanselman
0be021398c Merge PR #129: modernize string operations in GatewayUrlHelper and SettingsData 2026-04-01 10:00:39 -07:00
github-actions[bot]
84425718aa eng: enhance .editorconfig with modern C# idiom preferences
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>
2026-04-01 13:02:38 +00:00
github-actions[bot]
7b212f3824 refactor: modernize string operations in GatewayUrlHelper and SettingsData
- 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>
2026-04-01 13:02:34 +00:00
Scott Hanselman
344461d30d
fix: honour PreferStructuredCategories in notification pipeline
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>
2026-04-01 00:05:50 -07:00
Sytone
1d836390e9
feat: SSH tunnel gateway, device identity, reconnect hardening
Adds SSH local port-forward support for secure remote gateway access, Ed25519 device identity for operator auth, enhanced Quick Send with error remediation, reconnect resilience, and OpenClaw.Cli validator tool.

Includes security fix: SSH user/host input validation to prevent command injection.

615 tests pass (516 shared + 99 tray).

Contributed by @sytone
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-01 00:03:43 -07:00
Scott Hanselman
4e7225ba8c fix: add missing System.Collections.Frozen using in App.xaml.cs
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>
2026-03-31 23:50:49 -07:00
github-actions[bot]
37a5f9453d
docs: add SETUP.md and POWERTOYS.md guides
Comprehensive Windows Node setup guide and PowerToys Command Palette extension documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:44:18 -07:00
github-actions[bot]
cfe9e1f75b
refactor: eliminate ToLowerInvariant allocations in ChannelHealth
HashSet lookups with OrdinalIgnoreCase instead of pattern matching on lowered strings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:44:06 -07:00
github-actions[bot]
7aba505320
perf: skip StringBuilder in single-frame WebSocket receive
Fast-path single-frame messages to avoid StringBuilder allocation. Caches payload.ToString() in node logging.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:44:00 -07:00