- Fix InvalidCastException in CreateForMonitor: pass IID_IInspectable
instead of typeof(GraphicsCaptureItem).GUID, which returns a C#/WinRT-
generated GUID unrecognized by the native COM method (E_NOINTERFACE).
- Replace PrepareStreamTranscodeAsync with PrepareMediaStreamSourceTranscodeAsync
+ MediaStreamSource feeding NV12 samples on demand, fixing "Transcode
failed: Unknown" on all three screen recording commands.
- Add 500 MB frame-buffer cap (MaxFrameBufferBytes) with early stop and
warning log to prevent OOM on long or high-fps recordings.
- Save encoded MP4 to %TEMP%\openclaw\ and return filePath in the response.
- Change ScreenRecordResult.Fps from float to int.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>
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>
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>
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>
Two new commands for session-based recording:
- screen.record.start: opens a recording session and returns a recordingId
- screen.record.stop: closes the session and returns the video
ActiveSession manages the capture loop with a CancellationToken and stores
frames safely under a lock. A ConcurrentDictionary keyed by recordingId
allows concurrent sessions.
9 new tests cover: start/stop without a handler, args and monitor alias,
recordingId in the start response, full stop payload, and exception paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NodeService instantiates ScreenRecordingService and subscribes OnScreenRecord
to ScreenCapability's RecordRequested event.
Tests cover the full surface of screen.record: missing handler error, correct
arg forwarding, defaults (durationMs=5000, fps=10, screenIndex=0), the
monitor→screenIndex alias, and exception handling in the handler.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WinRT-based implementation backing screen.record:
- D3D11 + Direct3D11CaptureFramePool for GPU-backed frame acquisition
- Software BGRA→NV12 conversion (BT.601 limited range) before encoding
- MediaTranscoder pipeline with hardware acceleration and SW fallback
- No external dependencies: pure P/Invoke (d3d11.dll, combase.dll)
Records the full monitor only. Per-window capture is not yet implemented.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New command in the shared capability layer:
- screen.record: fixed-duration capture; blocks until done and returns
the video as base64 MP4.
Args: durationMs (def. 5000), fps (def. 10), screenIndex/monitor (def. 0).
The monitor→screenIndex alias keeps consistency with screen.capture.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reuses same cache key as test job. Saves redundant NuGet downloads on build runners.
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
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>