Commit Graph

71 Commits

Author SHA1 Message Date
Scott Hanselman
99f803e5ba feat: add tray command center diagnostics
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 23:11:00 -07:00
Scott Hanselman
f121395b06 feat: add safe device parity commands
Add canvas.a2ui.pushJSONL as a Mac-compatible alias for the existing A2UI JSONL push handler.

Add device.info and device.status using the shared OpenClawKit payload shape, with Windows metadata/status sources plus tests and docs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 22:43:16 -07:00
Scott Hanselman
dba534250c feat: add screen recording parity
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 22:18:59 -07:00
AlexAlves87
24a4c15ca9 feat: add routing seam for system.run exec approvals
Introduce a stable handler seam behind system.run to allow incremental
exec approval work without touching the current legacy path.

When no handler is injected (default), system.run runs byte-for-byte
identical to today. The new seam is inert until explicitly activated.
Adds minimum observability at the routing boundary: correlation ID,
selected path, decision, and reason code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit a018dc054cbb85581baa37f92719bfcecee6d76f)
2026-04-26 21:07:43 -07:00
Scott Hanselman
902e1ee2b6 fix: sign bootstrap pairing requests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 21:07:43 -07:00
dependabot[bot]
72dbd3d5a1
Bump Microsoft.NET.Test.Sdk from 17.14.1 to 18.4.0 (#226)
---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.4.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-26 21:05:24 -07:00
dependabot[bot]
95c10a4369
Bump xunit.runner.visualstudio from 3.1.4 to 3.1.5
Dependabot patch update. Normal build/test checks passed; MSIX packaging failure is the existing SDK/MSBuild CI issue.
2026-04-26 21:03:59 -07:00
Scott Hanselman
0bd56ffb9a Merge PR #218: eng: handle repo-assist easy wins 2026-04-26 20:23:54 -07:00
Scott Hanselman
00670860ed feat: improve QR setup pairing
Preserve setup bootstrap tokens separately from gateway tokens, support QR image and clipboard setup imports, and improve pairing notification copy flow.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 20:14:18 -07:00
Scott Hanselman
c84bff3a5f test: add exec auth and sanitizer coverage
Add test-only coverage from Repo Assist PRs #207 and #212 for terminal auth failures, shell wrapper parsing, and environment sanitization.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 19:56:52 -07:00
Scott Hanselman
864154bc2d test: add system capability coverage
Add coverage for system.run.prepare, exec approval get/set, and system.run environment sanitization from Repo Assist PR #209.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-26 19:51:15 -07:00
Scott Hanselman
c1296be7fd
feat: add camera.clip, location.get, and canvas local file serving
Some checks are pending
Build and Test / test (push) Waiting to run
Build and Test / build (win-arm64) (push) Blocked by required conditions
Build and Test / build (win-x64) (push) Blocked by required conditions
Build and Test / build-msix (ARM64, win-arm64) (push) Blocked by required conditions
Build and Test / build-msix (x64, win-x64) (push) Blocked by required conditions
Build and Test / build-extension (arm64) (push) Blocked by required conditions
Build and Test / build-extension (x64) (push) Blocked by required conditions
Build and Test / release (push) Blocked by required conditions
Adds Windows node camera.clip, location.get, canvas local file serving/reload support, gateway command parity docs, and aligns screen capture with gateway-canonical screen.snapshot. Also makes camera.clip choose a supported Windows MediaCapture record stream and skips startup update prompts in debug builds for local node debugging.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-25 23:19:15 -07:00
Scott Hanselman
5a97268ec0
improve: FrozenDictionary command dispatch map in WindowsNodeClient (#205)
* improve: FrozenDictionary command dispatch map in WindowsNodeClient

Replace O(n) linear capability scans with O(1) FrozenDictionary lookup.

- Add _commandMap field (FrozenDictionary<string, INodeCapability>)
- Add BuildCommandMap() to (re)build the map after each RegisterCapability call
- Replace both FirstOrDefault dispatch calls with _commandMap.GetValueOrDefault
- First-registered capability wins on command collision (preserves original semantics)
- Add 3 tests: routing, unknown command, first-registered-wins collision

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

* improve: FrozenDictionary command dispatch map in WindowsNodeClient

Replace two O(n) FirstOrDefault(c => c.CanHandle(command)) scans
with O(1) FrozenDictionary lookup. TryAdd preserves first-registered-
wins semantics. Map rebuilt on each RegisterCapability() call
(startup only, before ConnectAsync).

Add event-path dispatch test to cover HandleNodeInvokeEventAsync
in addition to the request-path tests.

Based on Repo Assist PR #197, with additional test coverage.
Closes #197

---------

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-23 10:54:20 -07:00
github-actions[bot]
c1e8955841
improve: modernize HandleRunAsync syntax; add policy+args regression tests (#181)
- Use range slice argv[1..] instead of argv.Skip(1).ToArray() (LINQ-free)
- Add SystemRun_SeparateArgsProperty_PolicyEvaluatesFullCommandLine: regression
  guard ensuring policy evaluates the full 'rm -rf /' when args come from the
  separate JSON 'args' property rather than the command argv array
- Add SystemRun_ShellFilter_PolicySkipsRuleForWrongShell: verifies that shell-
  filtered rules (Shells=["pwsh"]) are not applied when a different shell (cmd)
  is requested

588 Shared + 20 skipped; Tray 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-23 10:26:23 -07:00
github-actions[bot]
0af3f95911
perf: pre-allocate arrays from JSON array length; replace LINQ sort with Array.Sort (#189)
Eliminates List<T>+ToArray() allocation in three JSON-parsing hot paths:

- TryGetHandshakeScopes: size string[] from GetArrayLength() upfront;
  avoid dynamic List<string> growth and second ToArray() copy.

- GetStringArrayArg (NodeCapabilities): same pattern; avoids List<string>
  on every capability command that receives an array argument.

- ParseNodeList: pre-size GatewayNodeInfo[] from GetArrayLength(); replace
  three-key LINQ sort chain with Array.Sort + static Comparison<T> delegate,
  eliminating the intermediate IOrderedEnumerable allocations.

Also adds 3 regression tests:
- ParseNodeListPayload_EmptyArray_ReturnsEmpty
- ParseNodeListPayload_SameOnlineStatus_SortsByLastSeenDescending
- ParseNodeListPayload_SkipsItemsWithNoNodeId

Test status: 633 Shared passed (+3), 20 skipped; 122 Tray 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-23 10:26:13 -07:00
github-actions[bot]
9f1ef7bc16
improve: modernize Substring→range syntax; consolidate TruncateLabel duplicate (#180)
Replace all Substring(0, n) calls in OpenClaw.Shared with the equivalent
C# 8+ range syntax (str[..n]) for readability and consistency with the
rest of the codebase.

Also remove the private TruncateLabel helper from OpenClawGatewayClient —
its logic was identical to the public MenuDisplayHelper.TruncateText.
The three call sites now delegate to MenuDisplayHelper.TruncateText(..., 60)
directly, and the reflection-based test helper is updated to match.

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-23 10:26:02 -07:00
github-actions[bot]
2c29677dc3
test: add HandleRequestError coverage for auth/scope/unknown-method paths (#202)
Add 13 new tests to OpenClawGatewayClientTests covering the
HandleRequestError private method via reflection:

Pairing required (3 tests):
- SetsPairingBlockFlag: verifies _pairingRequiredAwaitingApproval = true
- LogsWarning: verifies auto-reconnect-paused warning is emitted
- RaisesErrorStatus: verifies ConnectionStatus.Error is raised

Device signature invalid (2 tests):
- CyclesSignatureMode: verifies mode steps V3AuthToken -> V3EmptyToken
- LogsWarningWithMode: verifies rejection warning is logged

Missing operator.read scope (1 Theory x 4 cases):
- sessions.list, usage.status, usage.cost, node.list all set
  _operatorReadScopeUnavailable = true

Unknown method fallbacks (4 tests):
- usage.status, usage.cost, sessions.preview, node.list each set
  their respective _*Unsupported flags

Also adds GatewayClientTestHelper overloads:
- Constructor accepting IOpenClawLogger (for log capture)
- TrackPendingRequest, GetPairingRequiredFlag, GetSignatureTokenMode,
  GetOperatorReadScopeUnavailable, CaptureStatusChanges helpers

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-23 10:25:20 -07:00
github-actions[bot]
1c251dd9c7
eng: enable TreatWarningsAsErrors in test projects (#190)
Lock in the zero-warning baseline achieved by PR #177. Any future
compiler or analyser warning in the test suite now becomes a build
error, preventing warning accumulation over time.

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-23 10:25:11 -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]
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]
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]
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
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
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]
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
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]
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
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
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
github-actions[bot]
9087ed21eb
fix: prevent double PairingStatusChanged fire in hello-ok handler
Guards gotNewToken flag to prevent event double-firing during pairing flow. Adds regression tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:43:27 -07:00
Nich Overend
b511e509e0
fix: constrain tray menu height to work area, enable scroll (fixes #82)
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
2026-03-31 23:40:36 -07:00
github-actions[bot]
937c6cab5d
refactor: remove dead ClassifyNotification method
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>
2026-03-31 23:39:00 -07:00
Scott Hanselman
519374c306
fix: notification sound + TryParseArgv refactor (closes #91, #92, #71)
- Extract TryParseArgv helper, consolidating ~40 lines of duplicated argv parsing
- Fix operator precedence in HandleExecApprovalsSet
- Add ShowToast helper applying NotificationSound setting to all 14 toast call sites
- 5 new argv parsing tests

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 15:54:37 -07:00
Scott Hanselman
de515926e1
chore: quick-win triage — global.json, test tooling, localization tests
- Pin .NET SDK via global.json (10.0.100, latestPatch) (closes #123)
- Sync xunit.runner.visualstudio 3.1.4 + add coverlet.collector to Tray.Tests (closes #90)
- Add localization key parity + format placeholder validation tests (closes #70)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 15:54:34 -07:00
Scott Hanselman
76f7811a14
Refactor: Extract WebSocketClientBase (DRY #63)
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>
2026-03-18 09:59:00 -07:00
Scott Hanselman
27b1de3600
fix: security hardening from triple-model review (#62)
7 fixes from Opus/Codex/Gemini triple-model review with cross-validation:

Consensus (2+ models agreed):
- ExecApprovalPolicy bypass: full argv evaluated, not just argv[0]
- Thread safety: lock pattern for _sessions/_nodes dictionaries
- SendRawAsync/Dispose races: defensive patterns from WindowsNodeClient

Cross-validated (different model confirmed):
- Canvas jsonlPath restricted to temp directory (arbitrary file read)
- ChannelHealth 'active' status shows [ON] not [OFF]
- OnSettingsSaved mirrors startup if/else (no dual connections)
- TryEnqueue checked in OnCanvasEval/OnCanvasSnapshot (prevents hang)

5 new security tests. All 576 tests pass.
2026-03-17 21:55:52 -07:00
Scott Hanselman
3634f3d112 test: fix false-positive session sorting test and tautological menu assertion
- 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>
2026-03-17 19:21:35 -07:00
Scott Hanselman
4549b62af5
[Repo Assist] fix: refresh channel list when channels are added/removed on gateway
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>
2026-03-17 10:49:23 -07:00
Scott Hanselman
bc140e9cca
feat: add WinUI test project with extracted helpers (#45)
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
2026-03-16 21:40:55 -07:00