Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
67a62e6
refactor: add replaceable node runtime boundary
giodl73-repo Jul 30, 2026
5e3c651
refactor: share node capability dispatcher
giodl73-repo Jul 30, 2026
a92b5ca
fix: harden substitute runtime lifecycle
giodl73-repo Jul 30, 2026
13b4dee
feat: prove Rust sidecar adapter contract
giodl73-repo Jul 31, 2026
3b0eeba
fix: align Windows sidecar contract
giodl73-repo Jul 31, 2026
ec82b66
fix: enforce portable sidecar results
giodl73-repo Jul 31, 2026
735a696
fix: enforce sidecar input bounds
giodl73-repo Jul 31, 2026
6a92026
fix: release rejected sidecar admissions
giodl73-repo Jul 31, 2026
9d2f762
fix: bound all sidecar responses
giodl73-repo Jul 31, 2026
cf6357b
fix: align Windows sidecar value semantics
giodl73-repo Jul 31, 2026
fc5627c
fix: match serde JSON limits
giodl73-repo Jul 31, 2026
9285fcd
fix: encode sidecar JSON like serde
giodl73-repo Jul 31, 2026
45b9dba
fix: enforce sidecar handler deadlines
giodl73-repo Jul 31, 2026
c34ae4f
fix: normalize sidecar invocation parameters
giodl73-repo Jul 31, 2026
e0184d0
fix: preserve active sidecar cancellation
giodl73-repo Jul 31, 2026
6b7fd1f
fix: retain timed-out sidecar admissions
giodl73-repo Jul 31, 2026
6ba6433
fix: reserve system namespace case-insensitively
giodl73-repo Jul 31, 2026
314f964
fix: stabilize malformed capability errors
giodl73-repo Jul 31, 2026
923fcad
fix: preserve admitted numeric payloads
giodl73-repo Jul 31, 2026
4439d45
fix: reserve default timeout result grace
giodl73-repo Jul 31, 2026
a6f782a
fix: match Rust sidecar value semantics
giodl73-repo Jul 31, 2026
6cd11d8
fix: align sidecar result serialization
giodl73-repo Jul 31, 2026
00931de
fix: separate handler and result deadlines
giodl73-repo Jul 31, 2026
6073393
fix: normalize untyped sidecar numbers
giodl73-repo Jul 31, 2026
1d11687
fix: match serde result number encoding
giodl73-repo Jul 31, 2026
d4d8ef7
fix: bound canonical JSON serialization
giodl73-repo Jul 31, 2026
b6d78eb
fix: normalize JsonDocument sidecar results
giodl73-repo Jul 31, 2026
9abed58
fix: bound JSON canonicalization work
giodl73-repo Jul 31, 2026
fe790a6
fix: canonicalize typed decimals and parsed nodes
giodl73-repo Jul 31, 2026
b551e30
fix: preflight lazy JSON node containers
giodl73-repo Jul 31, 2026
a847e6b
fix: continue node preflight after non-finite values
giodl73-repo Jul 31, 2026
ef338a3
fix: avoid double-charging JSON preflight
giodl73-repo Jul 31, 2026
711fe09
test: preserve handshake authorization in runtime seam
giodl73-repo Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ These are the canonical homes. Do not reintroduce private copies elsewhere.
| Capability UI metadata | `NodeCapabilityUiCatalog` (planned) | planned |
| Capability registration/gating | `NodeCapabilityRegistrationPolicy` (planned) | planned |
| Local MCP exposure policy | `McpCapabilityPolicy` (planned) | planned |
| Node runtime client boundary | `INodeRuntimeClient` + `INodeRuntimeClientFactory` | authoritative |
| Windows node capability execution | `NodeCapabilityDispatcher` | authoritative |
| Rust sidecar protocol and capability adaptation | `WindowsSidecarSupervisor` + `WindowsSidecarCapabilityAdapter` | authoritative, not selectable |
| Gateway connect envelope | `ConnectEnvelopeBuilder` (planned) | planned |
| Gateway request tracking | `PendingRequestRegistry` (planned) | planned |

Expand All @@ -80,6 +83,8 @@ These are the canonical homes. Do not reintroduce private copies elsewhere.
| `src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs` | `ConnectionPagePlan` (pure), `ConnectionPageViewModel`, `GatewayDirectConnectService`, gateway row models |
| `src/OpenClaw.Tray.WinUI/Pages/SettingsPage.xaml.cs` | settings read/persist → `SettingsPageViewModel` + `ISettingsStore`; keep gateway-uninstall, uptime timer, saved-indicator, and app-info in the view |
| `src/OpenClaw.Tray.WinUI/Services/NodeService.cs` | `McpServerHost`, `CanvasWindowManager`, `MediaCapabilityHost`, `RecordingConsentService`, `NodeCapabilityRegistry` |
| `src/OpenClaw.Connection/NodeConnector.cs` | runtime implementations behind `INodeRuntimeClientFactory`; keep lifecycle arbitration in the connector |
| `src/OpenClaw.Shared/WindowsNodeClient.cs` | wire parsing and response framing only; keep capability routing, bounds, cancellation, and telemetry in `NodeCapabilityDispatcher` |
| `src/OpenClaw.Shared/OpenClawGatewayClient.cs` | `PendingRequestRegistry`, `ConnectEnvelopeBuilder`, `GatewayMessageRouter`, per-domain API facades |
| `src/OpenClaw.Shared/Models.cs` | per-domain model files + `*Mapper` classes |
| `src/OpenClaw.Shared/Capabilities/SystemCapability.cs` | `ExecApprovalService` |
Expand Down Expand Up @@ -138,6 +143,9 @@ leading and trailing pipe. Columns, in order:
| ui-dispatcher | authoritative | src/OpenClaw.Tray.WinUI/App.xaml.cs | UI-thread marshaling abstraction for presentation code | IUiDispatcher | App and existing WinUI code may call DispatcherQueue directly until the view-model migration | presentation view models depend on IUiDispatcher not a concrete DispatcherQueue | UiDispatcherContractTests.PageViewModel_ReceivesRegisteredDispatcher | behavioral | - |
| navigation-scope | authoritative | src/OpenClaw.Tray.WinUI/Windows/HubWindow.xaml.cs | page view-model activation/deactivation and disposal lifetime | NavigationScopeManager | HubWindow keeps frame navigation back-stack and rail selection | transient page view models are activated on navigation and deactivated then disposed on navigate-away | NavigationScopeManagerTests.NavigatingAway_DeactivatesAndDisposesPreviousViewModel | behavioral | - |
| composition-root | authoritative | src/OpenClaw.Tray.WinUI/App.xaml.cs | presentation-layer service construction and wiring | AppServiceRegistration | App remains the composition root and owns non-DI service lifetimes | one validated root ServiceProvider; App-owned singletons registered as instances are never disposed by the container | AppServiceRegistrationTests.Dispose_DoesNotDisposeAppOwnedInstanceSingletons | behavioral | - |
| node-runtime-client-boundary | authoritative | NodeConnector + NodeService | concrete WindowsNodeClient coupling across node lifecycle and capability registration | INodeRuntimeClient + INodeRuntimeClientFactory | WindowsNodeClient remains the default in-process implementation | a substitute runtime receives configuration and capability-host setup before its connect handshake | NodeConnectorTests.ConnectAsync_UsesInjectedRuntimeClientBeforeHandshake | behavioral | - |
| node-capability-dispatcher | authoritative | src/OpenClaw.Shared/WindowsNodeClient.cs | capability indexing, bounded execution, duplicate tracking, cancellation, telemetry, and completion events | NodeCapabilityDispatcher | WindowsNodeClient retains Gateway envelope parsing and response framing; future adapters remain ineligible for selection until dispatcher conformance passes | the current C# runtime executes Windows-owned capabilities through one bounded dispatcher with first-registration-wins routing and structured completion | NodeCapabilityDispatcherTests.DispatchAsync_UsesFirstRegisteredCapabilityAndPreservesEventSender | behavioral | when every selectable runtime adapter proves it does not execute INodeCapability directly |
| rust-sidecar-capability-adapter | authoritative | future Rust runtime adapter planning | authenticated sidecar framing, handshake, immutable manifest admission, and native capability dispatch | WindowsSidecarSupervisor + WindowsSidecarCapabilityAdapter | process launch, protected credential bootstrap, runtime lifecycle projection, audit, rollout, rollback, and the default C# runtime remain outside this proof adapter | every admitted ordinary sidecar invocation is byte-compatible with the Rust corpus and executes only through NodeCapabilityDispatcher | WindowsSidecarCapabilityAdapterTests.Supervisor_DrivesAuthenticatedFramesIntoWindowsDispatcher | behavioral | when a selectable INodeRuntimeClient adapter subsumes this implementation with real Rust process and Gateway proof |
| node-summary-text | authoritative | src/OpenClaw.Tray.WinUI/App.xaml.cs | node-summary clipboard text formatting | NodeSummaryText | App keeps the clipboard side effect (building the DataPackage and setting clipboard content) | copied node-summary text is projected only by NodeSummaryText.Build (online/offline state, display-name fallback, short id, detail text, newline join) | NodeSummaryTextTests.Build_MultipleNodes_OneLinePerNodeJoinedByNewline | behavioral | - |
| reactor-chat-timeline | authoritative | src/OpenClaw.Tray.WinUI/Chat/OpenClawChatTimeline.cs | production chat message virtualization, row realization, and imperative scroll follow | ReactorChatTimeline through OpenClawReactorChatRoot and ReactorHostControl | OpenClawChatTimeline remains a legacy focused-test surface while its runtime route is migrated | the default chat route mounts one direct ReactorHostControl per XAML chat target; Reactor owns stable-key ItemsView and ItemContainer realization without a custom native list, collection reconciler, or scroll-layout mutation | review-only: user explicitly deferred new tests for this migration; required build and existing shared/tray suites still run | review-only | when Reactor timeline proof coverage replaces the legacy focused UI host coverage |
| chat-tool-activity-renderer | authoritative | src/OpenClaw.Tray.WinUI/Chat/ReactorChatTimeline.cs | production standalone tool-call and grouped activity presentation, summaries, disclosures, and detail rendering | ChatToolActivityPresentation + ToolCallCardRenderer | ReactorChatTimeline projects rows and delegates realization only | consecutive invocation grouping preserves source chronology; stable group identity comes from session, generation, and first tool entry; selectable output remains capped at 240px | ChatToolActivityPresentationTests.Project_GroupsOnlyConsecutiveSpansOfAtLeastTwoTools | behavioral | - |
Expand Down
18 changes: 11 additions & 7 deletions docs/CONNECTION_ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ OpenClaw.Tray.WinUI (net10.0-windows) - UI app, tray icon, pages, windows

**OpenClaw.Shared** owns the low-level gateway clients (`OpenClawGatewayClient`, `WindowsNodeClient`, `WebSocketClientBase`), device identity/signing (`DeviceIdentity`), protocol models, and the `IOperatorGatewayClient` interface.

`WindowsNodeClient` also owns gateway invocation lifetime at the transport
boundary. Active invokes are registered by invoke ID in a focused cancellation
registry, linked to the node connection lifetime, and cancelled individually by
the gateway `node.invoke.cancel` event. Active invocations atomically transition
to cancelled or completed when capability execution returns; whichever
transition wins determines the protocol outcome. Capability implementations
remain responsible for cooperative cancellation of their own underlying work.
`WindowsNodeClient` owns Gateway envelope decoding and response framing, then
delegates Windows capability execution to `NodeCapabilityDispatcher`. The
dispatcher registers active invokes by invoke ID, links them to the node
connection lifetime, and applies individual `node.invoke.cancel` requests.
Active invocations atomically transition to cancelled or completed when
capability execution returns; whichever transition wins determines the protocol
outcome. Capability implementations remain responsible for cooperative
cancellation of their own underlying work. A future runtime adapter must route
decoded invokes through this same dispatcher rather than execute Windows
capabilities itself, and remains ineligible for runtime selection until an
adapter-level conformance test proves that route.

**OpenClaw.Connection** owns all connection management: `GatewayConnectionManager`, `GatewayRegistry`, `CredentialResolver`, `ConnectionStateMachine`, `NodeConnector`, `SshTunnelService/Manager`, `SetupCodeDecoder`, and all connection interfaces/DTOs/enums. This project has zero WinUI dependencies and is independently testable.

Expand Down
81 changes: 81 additions & 0 deletions docs/RUST_NODE_RUNTIME_MIGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Rust node runtime migration seam

The Windows node is already the native Windows capability host. Its WinUI,
operator controls, MCP server, approval UX, and Windows-native command handlers
should remain in this repository. The proposed OpenClaw Rust runtime can replace
the duplicated Gateway transport and node lifecycle without replacing those
Windows surfaces.

This change introduces the first executable seam:

- `INodeRuntimeClient` is the contract consumed by the Windows capability host.
- `INodeRuntimeClientFactory` selects the runtime implementation.
- `WindowsNodeClient` remains the default, so production behavior is unchanged.
- `NodeConnector` still owns connection arbitration and ensures capabilities and
permissions are registered before the selected runtime begins its handshake.
- `NodeCapabilityDispatcher` is the shared Windows-owned execution path for
command lookup, concurrency, cancellation, telemetry, and completion. The
C# client now uses it. A Rust adapter is not eligible for runtime selection
until adapter-level conformance proves it routes every decoded invocation
through this dispatcher instead of copying capability policy.

The next fork-only evidence slice now implements the Windows half of the shared
sidecar contract without selecting it in production:

- `AuthenticatedSidecarChannel` independently reproduces the Rust framing and
HMAC vector, enforces directional sequence and generation bounds, and retires
permanently after inbound validation failure.
- `SidecarSupervisorHandshake` independently reproduces the Rust offer and
accept vectors and lowers the active frame ceiling to the negotiated limit.
- `WindowsSidecarSupervisor` enforces handshake, one-time immutable
configuration, and post-configuration message order.
- `WindowsSidecarCapabilityAdapter` requires an unchanged admission before
dispatch, rejects wrong-node and undeclared work, and routes invocation and
cancellation only through `NodeCapabilityDispatcher`.
- The copied fixtures are byte-exact evidence from OpenClaw fork PRs #193,
#194, and #195 at the combined head `5bab2c9ecf6`. They are conformance
inputs, not a second wire authority.

This proof deliberately does not implement `INodeRuntimeClient` or runtime
selection yet. The current shared messages do not project the complete pairing,
issued-token, health, Gateway-self, reconnect-authorization, and node-event
surface required by that interface. A production adapter also still needs a
verified Rust artifact, process supervision, protected bootstrap and credential
handoff, concrete local IPC, resource bounds, audit correlation, and rollback.

The proof also records one generic compatibility gap: the current Rust
`CommandRuntime` rejects all `system.*` registrations. That is appropriate for
the standalone experimental host, but it prevents the official Windows node's
existing `system.run`, `system.which`, and `system.notify` capabilities from
using the sidecar. Runtime PR3 therefore needs an explicit OpenClaw-authorized
command-namespace mechanism before the Windows adapter can be selected. The
Windows proof fails closed instead of bypassing that restriction.

## Intended follow-up slices

1. ~~Define the versioned, authenticated local IPC messages against the shared
OpenClaw node lifecycle fixtures.~~ Implemented as fork conformance evidence.
2. Add an opt-in sidecar adapter that implements `INodeRuntimeClient` over that
versioned, authenticated local IPC protocol. The Rust process owns Gateway
connection, registration, invoke/result/progress/cancellation, reconnect, and
runtime lifecycle.
3. Run the C# and Rust implementations through the same registration and
invocation conformance fixtures, including cancellable blocked-connect and
adapter-to-dispatcher routing tests. Keep the existing C# runtime as the
default while the Rust path gathers real Gateway proof.
4. Switch the Windows node role to the Rust adapter behind an explicit rollout
gate. C# continues to execute Windows-native capabilities and return results
through the runtime contract.
5. Remove the duplicated C# Gateway node transport only after parity, rollout,
and rollback criteria are met.

## Non-goals of this slice

- It does not add or vendor a Rust binary.
- It does not change the production runtime selection.
- It does not claim complete `INodeRuntimeClient` lifecycle or pairing parity.
- It does not add commands or change native capability ownership.
- It does not move the operator or MCP roles into Rust.

This separation lets the Windows Companion and the Windows tray share an
OpenClaw-owned Rust runtime while preserving the app-specific Windows surfaces.
8 changes: 4 additions & 4 deletions src/OpenClaw.Connection/INodeConnector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace OpenClaw.Connection;

/// <summary>
/// Manages the node-side connection for a given gateway.
/// Owns the WindowsNodeClient lifecycle but delegates capability
/// Owns the node runtime lifecycle but delegates capability
/// setup to NodeService (which has WinUI dependencies).
/// </summary>
public interface INodeConnector : IDisposable
Expand All @@ -21,7 +21,7 @@ public interface INodeConnector : IDisposable
event EventHandler<DeviceTokenReceivedEventArgs> DeviceTokenReceived;

/// <summary>
/// Raised right after a new <see cref="WindowsNodeClient"/> is constructed
/// Raised right after a new <see cref="INodeRuntimeClient"/> is constructed
/// but BEFORE its <c>ConnectAsync()</c> call. Subscribers (typically
/// <c>NodeService</c>) must register the node's capabilities on the new
/// client synchronously so the outbound "connect" handshake includes
Expand Down Expand Up @@ -67,12 +67,12 @@ public interface INodeConnectorReconnectPolicy

public sealed class NodeClientCreatedEventArgs : EventArgs
{
public NodeClientCreatedEventArgs(WindowsNodeClient client, string? bearerToken)
public NodeClientCreatedEventArgs(INodeRuntimeClient client, string? bearerToken)
{
Client = client;
BearerToken = bearerToken;
}

public WindowsNodeClient Client { get; }
public INodeRuntimeClient Client { get; }
public string? BearerToken { get; }
}
33 changes: 33 additions & 0 deletions src/OpenClaw.Connection/INodeRuntimeClientFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using OpenClaw.Shared;

namespace OpenClaw.Connection;

/// <summary>
/// Creates the node runtime used by <see cref="NodeConnector"/>.
/// </summary>
public interface INodeRuntimeClientFactory
{
INodeRuntimeClient Create(
string gatewayUrl,
GatewayCredential credential,
string identityPath,
IOpenClawLogger logger);
}

/// <summary>
/// Preserves the existing in-process C# node runtime as the default.
/// </summary>
public sealed class WindowsNodeRuntimeClientFactory : INodeRuntimeClientFactory
{
public INodeRuntimeClient Create(
string gatewayUrl,
GatewayCredential credential,
string identityPath,
IOpenClawLogger logger) =>
new WindowsNodeClient(
gatewayUrl,
credential.IsBootstrapToken ? "" : credential.Token,
identityPath,
logger,
bootstrapToken: credential.IsBootstrapToken ? credential.Token : null);
}
Loading