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>
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)
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>
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>
* 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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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.
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.
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>
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>
'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>
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>
* 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>
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
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>
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>
- 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>
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>
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