Commit Graph

182 Commits

Author SHA1 Message Date
Scott Hanselman
c9c1aed602 chore: upgrade and recompile agentic workflows
Some checks failed
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-04-13 19:26:42 -07:00
AlexAlves87
f4dbc521df fix: repair screen recording capture and encoding pipeline
- 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>
2026-04-09 17:00:09 +02:00
AlexAlves87
21b0d315be Merge remote-tracking branch 'upstream/master' into feat/screen-record 2026-04-09 16:46:33 +02: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
AlexAlves87
7cce9fe01e feat: add screen.record.start and screen.record.stop
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>
2026-04-08 19:19:30 +02:00
AlexAlves87
e4e6abb01e feat: wire screen.record into NodeService and add capability tests
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>
2026-04-08 19:19:24 +02:00
AlexAlves87
45912512d0 feat: add ScreenRecordingService for fixed-duration monitor capture
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>
2026-04-08 19:19:19 +02:00
AlexAlves87
22e378f8a7 feat: add screen.record to ScreenCapability
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>
2026-04-08 19:19:16 +02: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
github-actions[bot]
cca7493dce
perf: eliminate string allocations in ClassifyByKeywords
Replace .ToLowerInvariant() with OrdinalIgnoreCase, FrozenDictionary for tool kind map.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:43:54 -07:00
github-actions[bot]
10804c6305
perf: cache HTML sanitize regex, replace ToLowerInvariant in notification type lookup
Static compiled regexes for SanitizeHtml, FrozenDictionary for notification type map.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:43:49 -07:00
github-actions[bot]
52aa813129
fix: reset node/gateway state on settings save
Clears _isPendingApproval on disconnect/error, resets gateway state properly in OnSettingsSaved.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:43:33 -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
github-actions[bot]
180acbe7c1
fix: pass UserRules to NotificationCategorizer (closes #94)
UserRules from settings were never passed to Classify() — custom notification rules were silently ignored.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:43:12 -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]
c39293cbaa
fix: add ReDoS timeout to ExecApprovalPolicy glob matching
Adds 100ms timeout to regex matching in exec approval policy to prevent ReDoS. Returns deny on timeout (safe default).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:39:29 -07:00
github-actions[bot]
178b0054a7
fix: add operator.write scope to gateway connect handshake
Adds missing operator.write permission scope to gateway connection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:39:23 -07:00
github-actions[bot]
f086473c40
refactor: extract ModelFormatting helper
Eliminates duplicate FormatTokenCount/FormatCount methods across ModelInfo and GatewayUsageInfo.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:39:19 -07:00
github-actions[bot]
a9531e751b
refactor: use result.Parameters in DeepLinkHandler
Replaces manual query string parsing with result.Parameters dictionary lookup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:39:05 -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
github-actions[bot]
52597f8dca
eng: add .editorconfig for consistent C# style
Adds .editorconfig with Allman braces, underscore camelCase fields, file-scoped namespaces.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:38:56 -07:00
github-actions[bot]
b21ca94fb1
fix: null _gatewayClient after dispose in OnSettingsSaved
Prevents use-after-dispose by setting _gatewayClient = null after Dispose().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-31 23:38:39 -07:00
Rundredoffi
dd487ab8a4
feat: add French (fr-fr) localization
Adds complete fr-fr (French) translation — all 163 resource keys.

Contributed by @rundredoffi in response to #61.
2026-03-31 17:34:57 -07:00
Tim0320
cf87a692d0
feat: add Traditional Chinese (zh-tw) localization
Adds complete zh-tw (Traditional Chinese) translation — all 163 resource keys.

Contributed by @Tim0320 in response to #61.
2026-03-31 17:34:54 -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
536d436989
perf: cache JsonSerializerOptions and compiled Regex in WindowsNodeClient
Cache hot-path allocations: static JsonSerializerOptions (WhenWritingNull, WriteIndented) and compiled Regex for command validation.
Eliminates per-message allocations in node.invoke handlers.

Supersedes Repo Assist #65.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-18 10:18:41 -07:00
Scott Hanselman
a9de0c77a5
ci: add NuGet package caching to test job
Adds actions/cache@v4 for ~/.nuget/packages keyed on csproj hashes.
Saves ~30-60s on subsequent CI runs.

Supersedes Repo Assist #66.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-18 10:18:37 -07:00