Skip to content

Commit fe6758e

Browse files
CopilotCopilot
andcommitted
Node mode UI: surface MCP-only/connecting states and repair gateway-node gating
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 6c56a83 commit fe6758e

13 files changed

Lines changed: 567 additions & 69 deletions

File tree

src/OpenClaw.Tray.WinUI/App.xaml.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ public partial class App : Application, OpenClawTray.Services.IAppCommands
6767
internal VoiceService? VoiceService => _nodeService?.VoiceService ?? _standaloneVoiceService;
6868
/// <summary>The full device ID of the local node service (if running).</summary>
6969
internal string? NodeFullDeviceId => _nodeService?.FullDeviceId;
70+
/// <summary>Live node service instance used by settings surfaces for MCP status.</summary>
71+
internal NodeService? ActiveNodeService => _nodeService;
7072

7173
/// <summary>
7274
/// Session key that the chat surface should select on its next mount.
@@ -644,7 +646,7 @@ _dispatcherQueue is null
644646
credentialResolver, clientFactory, _gatewayRegistry, appLogger,
645647
identityStore: new DeviceIdentityFileStore(appLogger),
646648
nodeConnector: nodeConnector,
647-
isNodeEnabled: ShouldInitializeNodeService,
649+
isNodeEnabled: IsGatewayNodeEnabled,
648650
diagnostics: diagnostics,
649651
tunnelManager: _sshTunnelService);
650652
_connectionManager.OperatorClientChanged += OnOperatorClientChanged;
@@ -1541,7 +1543,7 @@ record = SyncGatewayBrowserProxyForward(record);
15411543
if (credential == null)
15421544
{
15431545
var nodeCredential = ResolveStartupNodeCredential(record, resolver, identityDir);
1544-
if (nodeCredential != null && ShouldInitializeNodeService())
1546+
if (nodeCredential != null && IsGatewayNodeEnabled())
15451547
{
15461548
Logger.Info(
15471549
$"Connecting node-only gateway during {context}: {record.Url} ({nodeCredential.Source})");
@@ -1562,6 +1564,8 @@ record = SyncGatewayBrowserProxyForward(record);
15621564
ObserveBackgroundFault(
15631565
_connectionManager.ConnectAsync(record.Id),
15641566
$"[App] Startup gateway connect failed during {context}");
1567+
if (!IsGatewayNodeEnabled())
1568+
TryStartLocalMcpOnlyNode();
15651569
return true;
15661570
}
15671571

@@ -1891,6 +1895,12 @@ private bool ShouldInitializeNodeService()
18911895
return _settings?.EnableNodeMode == true || _settings?.EnableMcpServer == true;
18921896
}
18931897

1898+
/// <summary>True when this PC should connect as a gateway node.</summary>
1899+
private bool IsGatewayNodeEnabled()
1900+
{
1901+
return _settings?.EnableNodeMode == true;
1902+
}
1903+
18941904
/// <summary>
18951905
/// Ensures a WSL keepalive process is running for the local gateway distro
18961906
/// so the WSL2 VM stays up even after the tray exits.

src/OpenClaw.Tray.WinUI/Pages/ConnectionPage.xaml.cs

Lines changed: 76 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,14 @@ or NodeCardState.OnNodeRateLimited
810810
Helpers.FluentIconCatalog.StatusOk,
811811
"SystemFillColorSuccessBrush",
812812
capCount == 1 ? LocalizationHelper.GetString("ConnectionPage_NodeActiveOneCapability") : string.Format(LocalizationHelper.GetString("ConnectionPage_NodeActiveCapabilities"), capCount)),
813+
NodeCardState.OnNodeConnecting => (
814+
Helpers.FluentIconCatalog.Sync,
815+
"SystemFillColorCautionBrush",
816+
LocalizationHelper.GetString("ConnectionPage_NodeStarting")),
817+
NodeCardState.OffMcpOnly => (
818+
Helpers.FluentIconCatalog.Terminal,
819+
"SystemFillColorAttentionBrush",
820+
LocalizationHelper.GetString("ConnectionPage_NodeMcpOnly")),
813821
NodeCardState.OnPermissionsIncomplete => (
814822
Helpers.FluentIconCatalog.StatusWarn,
815823
"SystemFillColorCautionBrush",
@@ -854,47 +862,72 @@ or NodeCardState.OnNodeRateLimited
854862
? ResolveBrush("SystemFillColorCriticalBrush")
855863
: ResolveBrush("TextFillColorPrimaryBrush");
856864

857-
// The gateway's node-list contract owns this boundary. Pending
858-
// declarations are visible for approval context but never counted or
859-
// labeled as approved/effective.
860-
bool showSurfaces = settings != null && plan.NodeCard != NodeCardState.Off
861-
&& plan.NodeCard != NodeCardState.Hidden;
862-
NodeCapabilityText.Visibility = showSurfaces ? Visibility.Visible : Visibility.Collapsed;
863-
NodeCommandText.Visibility = showSurfaces ? Visibility.Visible : Visibility.Collapsed;
864-
NodePermissionText.Visibility = showSurfaces ? Visibility.Visible : Visibility.Collapsed;
865-
if (showSurfaces)
866-
{
867-
NodeCapabilityText.Text = BuildNodeSurfaceListString(
868-
"ConnectionPage_NodeEffectiveCapabilities",
869-
plan.NodeEffectiveCapabilities);
870-
NodeCommandText.Text = BuildNodeSurfaceListString(
871-
"ConnectionPage_NodeEffectiveCommands",
872-
plan.NodeEffectiveCommands);
873-
NodePermissionText.Text = BuildNodePermissionListString(
874-
"ConnectionPage_NodeEffectivePermissions",
875-
plan.NodeEffectivePermissions);
876-
}
877-
878-
var showPendingDeclarations = showSurfaces &&
879-
(plan.NodeApprovalState is GatewayNodeApprovalState.PendingApproval or
880-
GatewayNodeApprovalState.PendingReapproval ||
881-
plan.NodePendingDeclaredCapabilities.Count > 0 ||
882-
plan.NodePendingDeclaredCommands.Count > 0 ||
883-
plan.NodePendingDeclaredPermissions.Count > 0);
884-
NodePendingDeclarationsPanel.Visibility = showPendingDeclarations
885-
? Visibility.Visible
886-
: Visibility.Collapsed;
887-
if (showPendingDeclarations)
865+
if (plan.NodeCard == NodeCardState.OffMcpOnly)
866+
{
867+
NodeCapabilityText.Visibility = Visibility.Visible;
868+
NodeCapabilityText.Text = LocalizationHelper.Format(
869+
"ConnectionPage_NodeMcpOnlyReachable", NodeService.McpServerUrl);
870+
NodeCommandText.Visibility = Visibility.Collapsed;
871+
NodePermissionText.Visibility = Visibility.Collapsed;
872+
NodePendingDeclarationsPanel.Visibility = Visibility.Collapsed;
873+
874+
var mcpError = CurrentApp.ActiveNodeService?.McpStartupError;
875+
if (!string.IsNullOrEmpty(mcpError))
876+
{
877+
NodeStatusIcon.Glyph = Helpers.FluentIconCatalog.StatusErr;
878+
NodeStatusIcon.Foreground = ResolveBrush("SystemFillColorCriticalBrush");
879+
NodeStatusText.Text = LocalizationHelper.GetString("ConnectionPage_NodeMcpError");
880+
NodeStatusText.Foreground = ResolveBrush("SystemFillColorCriticalBrush");
881+
NodeCapabilityText.Visibility = Visibility.Collapsed;
882+
NodeBodyText.Text = mcpError;
883+
NodeBodyText.Foreground = ResolveBrush("SystemFillColorCriticalBrush");
884+
NodeBodyText.Visibility = Visibility.Visible;
885+
}
886+
}
887+
else
888888
{
889-
NodePendingCapabilityText.Text = BuildNodeSurfaceListString(
890-
"ConnectionPage_NodePendingDeclaredCapabilities",
891-
plan.NodePendingDeclaredCapabilities);
892-
NodePendingCommandText.Text = BuildNodeSurfaceListString(
893-
"ConnectionPage_NodePendingDeclaredCommands",
894-
plan.NodePendingDeclaredCommands);
895-
NodePendingPermissionText.Text = BuildNodePermissionListString(
896-
"ConnectionPage_NodePendingDeclaredPermissions",
897-
plan.NodePendingDeclaredPermissions);
889+
// Pending declarations are visible for approval context but never
890+
// counted as the active node contract.
891+
bool showSurfaces = settings != null && plan.NodeCard != NodeCardState.Off
892+
&& plan.NodeCard != NodeCardState.Hidden
893+
&& plan.NodeCard != NodeCardState.OnNodeConnecting;
894+
NodeCapabilityText.Visibility = showSurfaces ? Visibility.Visible : Visibility.Collapsed;
895+
NodeCommandText.Visibility = showSurfaces ? Visibility.Visible : Visibility.Collapsed;
896+
NodePermissionText.Visibility = showSurfaces ? Visibility.Visible : Visibility.Collapsed;
897+
if (showSurfaces)
898+
{
899+
NodeCapabilityText.Text = BuildNodeSurfaceListString(
900+
"ConnectionPage_NodeEffectiveCapabilities",
901+
plan.NodeEffectiveCapabilities);
902+
NodeCommandText.Text = BuildNodeSurfaceListString(
903+
"ConnectionPage_NodeEffectiveCommands",
904+
plan.NodeEffectiveCommands);
905+
NodePermissionText.Text = BuildNodePermissionListString(
906+
"ConnectionPage_NodeEffectivePermissions",
907+
plan.NodeEffectivePermissions);
908+
}
909+
910+
var showPendingDeclarations = showSurfaces &&
911+
(plan.NodeApprovalState is GatewayNodeApprovalState.PendingApproval or
912+
GatewayNodeApprovalState.PendingReapproval ||
913+
plan.NodePendingDeclaredCapabilities.Count > 0 ||
914+
plan.NodePendingDeclaredCommands.Count > 0 ||
915+
plan.NodePendingDeclaredPermissions.Count > 0);
916+
NodePendingDeclarationsPanel.Visibility = showPendingDeclarations
917+
? Visibility.Visible
918+
: Visibility.Collapsed;
919+
if (showPendingDeclarations)
920+
{
921+
NodePendingCapabilityText.Text = BuildNodeSurfaceListString(
922+
"ConnectionPage_NodePendingDeclaredCapabilities",
923+
plan.NodePendingDeclaredCapabilities);
924+
NodePendingCommandText.Text = BuildNodeSurfaceListString(
925+
"ConnectionPage_NodePendingDeclaredCommands",
926+
plan.NodePendingDeclaredCommands);
927+
NodePendingPermissionText.Text = BuildNodePermissionListString(
928+
"ConnectionPage_NodePendingDeclaredPermissions",
929+
plan.NodePendingDeclaredPermissions);
930+
}
898931
}
899932

900933
// Sync toggle from current settings (suppress event)
@@ -1069,7 +1102,9 @@ private List<Border> BuildCapabilityChips(IReadOnlyList<string>? capabilities, N
10691102
{
10701103
var chips = new List<Border>();
10711104
if (capabilities == null || capabilities.Count == 0) return chips;
1072-
if (state == NodeCardState.Off || state == NodeCardState.Hidden) return chips;
1105+
if (state == NodeCardState.Off || state == NodeCardState.Hidden
1106+
|| state == NodeCardState.OffMcpOnly || state == NodeCardState.OnNodeConnecting)
1107+
return chips;
10731108

10741109
void Add(string label, bool enabled, bool warn = false, bool error = false)
10751110
{

src/OpenClaw.Tray.WinUI/Pages/ConnectionPagePlan.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ internal enum NodeCardState
8686
{
8787
Hidden,
8888
Off,
89+
/// <summary>Gateway node is off, local MCP server is enabled.</summary>
90+
OffMcpOnly,
8991
OnHealthy,
92+
/// <summary>Node role is connecting / starting up (not yet ready).</summary>
93+
OnNodeConnecting,
9094
OnPermissionsIncomplete,
9195
OnNodeApprovalRequired,
9296
OnNodeReapprovalRequired,
@@ -530,7 +534,8 @@ GatewayNodeApprovalState.PendingApproval or
530534
var nodeCardAllowsTrustOverride = plan.NodeCard is
531535
NodeCardState.OnHealthy or
532536
NodeCardState.OnPermissionsIncomplete or
533-
NodeCardState.OnNodePairingRequired ||
537+
NodeCardState.OnNodePairingRequired or
538+
NodeCardState.OnNodeConnecting ||
534539
nodeConnectingAllowsTrustOverride;
535540
// Authoritative node-list trust can override any non-device-pair card.
536541
// Snapshot fallback is narrower: Unknown stays on discovery-only pairing UI.
@@ -598,14 +603,16 @@ NodeCardState.OnPermissionsIncomplete or
598603
private static NodeCardState BuildNodeCardState(GatewayConnectionSnapshot snap, SettingsManager? settings)
599604
{
600605
if (settings == null) return NodeCardState.Hidden;
601-
if (!settings.EnableNodeMode) return NodeCardState.Off;
602606

603-
// Operator must be connected for the node card to be meaningful.
607+
if (!settings.EnableNodeMode)
608+
return settings.EnableMcpServer ? NodeCardState.OffMcpOnly : NodeCardState.Off;
609+
604610
if (snap.OperatorState != RoleConnectionState.Connected)
605611
return NodeCardState.Off;
606612

607613
return snap.NodeState switch
608614
{
615+
RoleConnectionState.Connecting => NodeCardState.OnNodeConnecting,
609616
RoleConnectionState.PairingRequired => NodeCardState.OnNodePairingRequired,
610617
RoleConnectionState.PairingRejected => NodeCardState.OnNodeRejected,
611618
RoleConnectionState.RateLimited => NodeCardState.OnNodeRateLimited,

src/OpenClaw.Tray.WinUI/Pages/PermissionsPage.xaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@
175175
<TextBlock x:Uid="PermissionsPage_McpHeader" Text="Local MCP Server"
176176
Style="{StaticResource BodyStrongTextBlockStyle}"/>
177177
<TextBlock x:Uid="PermissionsPage_McpDescription"
178-
Text="Expose capabilities over HTTP for CLI tools and local integrations."
178+
Text="Serves capabilities to local MCP clients (CLI tools, integrations) on this PC over HTTP."
179179
Style="{StaticResource CaptionTextBlockStyle}"
180180
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
181181
TextWrapping="Wrap"/>

0 commit comments

Comments
 (0)