feat: add browser setup guidance entrypoint
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
cd67ae8485
commit
8bba19ca9d
@ -310,6 +310,7 @@ OpenClaw registers the `openclaw://` URL scheme for automation and integration:
|
||||
| `openclaw://config` | Open the config folder |
|
||||
| `openclaw://diagnostics` | Open the diagnostics JSONL folder |
|
||||
| `openclaw://support-context` | Copy redacted support context |
|
||||
| `openclaw://browser-setup` | Copy browser.proxy/browser-control setup guidance |
|
||||
| `openclaw://restart-ssh-tunnel` | Restart the tray-managed SSH tunnel when enabled |
|
||||
| `openclaw://send?message=Hello` | Open Quick Send with pre-filled text |
|
||||
| `openclaw://agent?message=Hello` | Send message directly to the connected gateway |
|
||||
@ -336,6 +337,7 @@ PowerToys Command Palette extension for quick OpenClaw access.
|
||||
- **⚙️ Settings** - Open the OpenClaw Tray Settings dialog
|
||||
- **📄 Open Log File / 📁 Logs / 🗂️ Config / 🧪 Diagnostics** - Open support files and folders
|
||||
- **📋 Copy Support Context** - Copy redacted Command Center metadata
|
||||
- **🌐 Copy Browser Setup** - Copy browser.proxy and node-host setup guidance
|
||||
- **🔁 Restart SSH Tunnel** - Restart the tray-managed SSH tunnel when enabled
|
||||
|
||||
### Installation
|
||||
|
||||
@ -52,6 +52,7 @@ Open Command Palette (`Win+Alt+Space`), type **"OpenClaw"** — you should see t
|
||||
| **🗂️ Open Config Folder** | Opens the OpenClaw Tray configuration folder |
|
||||
| **🧪 Open Diagnostics Folder** | Opens the diagnostics JSONL folder |
|
||||
| **📋 Copy Support Context** | Copies redacted Command Center support metadata |
|
||||
| **🌐 Copy Browser Setup** | Copies browser.proxy and node-host setup guidance |
|
||||
| **🔁 Restart SSH Tunnel** | Restarts the tray-managed SSH tunnel when enabled |
|
||||
|
||||
## Usage
|
||||
@ -119,4 +120,5 @@ Get-AppxPackage -Name '*OpenClaw*' | Remove-AppxPackage
|
||||
| Open Config Folder | `openclaw://config` |
|
||||
| Open Diagnostics Folder | `openclaw://diagnostics` |
|
||||
| Copy Support Context | `openclaw://support-context` |
|
||||
| Copy Browser Setup | `openclaw://browser-setup` |
|
||||
| Restart SSH Tunnel | `openclaw://restart-ssh-tunnel` |
|
||||
|
||||
@ -90,6 +90,7 @@ OpenClaw Tray responds to `openclaw://` deep links, which can be invoked from a
|
||||
| `openclaw://config` | Open the config folder |
|
||||
| `openclaw://diagnostics` | Open the diagnostics JSONL folder |
|
||||
| `openclaw://support-context` | Copy redacted support context |
|
||||
| `openclaw://browser-setup` | Copy browser.proxy/browser-control setup guidance |
|
||||
| `openclaw://restart-ssh-tunnel` | Restart the tray-managed SSH tunnel when enabled |
|
||||
| `openclaw://agent?message=Hello` | Send a message directly to the connected gateway |
|
||||
|
||||
|
||||
@ -109,6 +109,11 @@ internal sealed partial class OpenClawPage : ListPage
|
||||
Title = "📋 Copy Support Context",
|
||||
Subtitle = "Copy redacted Command Center support metadata"
|
||||
},
|
||||
new ListItem(new OpenUrlCommand("openclaw://browser-setup"))
|
||||
{
|
||||
Title = "🌐 Copy Browser Setup",
|
||||
Subtitle = "Copy browser.proxy and node-host setup guidance"
|
||||
},
|
||||
new ListItem(new OpenUrlCommand("openclaw://restart-ssh-tunnel"))
|
||||
{
|
||||
Title = "🔁 Restart SSH Tunnel",
|
||||
|
||||
@ -2257,24 +2257,13 @@ public partial class App : Application
|
||||
Category = "browser",
|
||||
Title = "Browser proxy host not detected",
|
||||
Detail = "browser.proxy needs a compatible browser-control host listening on the gateway port + 2.",
|
||||
RepairAction = "Copy browser-control host guidance",
|
||||
CopyText = BuildBrowserProxyHostGuidance(port.Port)
|
||||
RepairAction = "Copy browser setup guidance",
|
||||
CopyText = StatusDetailWindow.BuildBrowserSetupGuidance(port.Port, topology, tunnel)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildBrowserProxyHostGuidance(int browserProxyPort)
|
||||
{
|
||||
var portText = browserProxyPort is >= 1 and <= 65535 ? browserProxyPort.ToString() : "<gateway-port+2>";
|
||||
return string.Join(Environment.NewLine, [
|
||||
$"Start a compatible OpenClaw browser-control host on 127.0.0.1:{portText}.",
|
||||
"It must run on the same machine as the gateway, or be forwarded to Windows with SSH tunnel mode.",
|
||||
"Use auth compatible with the Windows node: the saved Settings gateway token, browser-control token, or browser-control password.",
|
||||
"After it is listening, retry browser.proxy from the gateway."
|
||||
]);
|
||||
}
|
||||
|
||||
private static string BuildBrowserProxySshForwardHint(int browserProxyPort, TunnelCommandCenterInfo? tunnel)
|
||||
{
|
||||
if (browserProxyPort is < 1 or > 65535)
|
||||
@ -2665,6 +2654,21 @@ public partial class App : Application
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyBrowserSetupGuidance()
|
||||
{
|
||||
try
|
||||
{
|
||||
var package = new DataPackage();
|
||||
package.SetText(StatusDetailWindow.BuildBrowserSetupGuidance(BuildCommandCenterState()));
|
||||
Clipboard.SetContent(package);
|
||||
Logger.Info("Copied browser setup guidance from deep link");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warn($"Failed to copy browser setup guidance from deep link: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGlobalHotkeyPressed(object? sender, EventArgs e)
|
||||
{
|
||||
// Hotkey events are raised from a dedicated Win32 message-loop thread.
|
||||
@ -2872,6 +2876,7 @@ public partial class App : Application
|
||||
OpenConfigFolder = OpenConfigFolder,
|
||||
OpenDiagnosticsFolder = OpenDiagnosticsFolder,
|
||||
CopySupportContext = CopySupportContext,
|
||||
CopyBrowserSetupGuidance = CopyBrowserSetupGuidance,
|
||||
RestartSshTunnel = RestartSshTunnel,
|
||||
OpenChat = ShowWebChat,
|
||||
OpenCommandCenter = ShowStatusDetail,
|
||||
|
||||
@ -107,6 +107,12 @@ public static class DeepLinkHandler
|
||||
actions.CopySupportContext?.Invoke();
|
||||
break;
|
||||
|
||||
case "browser-setup":
|
||||
case "browser-guidance":
|
||||
case "browser-proxy-setup":
|
||||
actions.CopyBrowserSetupGuidance?.Invoke();
|
||||
break;
|
||||
|
||||
case "ssh-restart":
|
||||
case "restart-ssh":
|
||||
case "restart-ssh-tunnel":
|
||||
@ -188,6 +194,7 @@ public class DeepLinkActions
|
||||
public Action? OpenConfigFolder { get; set; }
|
||||
public Action? OpenDiagnosticsFolder { get; set; }
|
||||
public Action? CopySupportContext { get; set; }
|
||||
public Action? CopyBrowserSetupGuidance { get; set; }
|
||||
public Action? RestartSshTunnel { get; set; }
|
||||
public Action? OpenChat { get; set; }
|
||||
public Action? OpenCommandCenter { get; set; }
|
||||
|
||||
@ -94,6 +94,7 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="OverviewChannelsText" Grid.Row="0" Grid.Column="0" Text="Channels: 0"/>
|
||||
<TextBlock x:Name="OverviewSessionsText" Grid.Row="0" Grid.Column="1" Text="Sessions: 0"/>
|
||||
@ -113,6 +114,7 @@
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Open Logs"
|
||||
@ -138,8 +140,13 @@
|
||||
AutomationProperties.AutomationId="CommandCenterRestartSshTunnelButton"
|
||||
Visibility="Collapsed"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" Spacing="8">
|
||||
<Button Content="Copy Browser Setup"
|
||||
Click="OnCopyBrowserSetup"
|
||||
AutomationProperties.AutomationId="CommandCenterCopyBrowserSetupButton"/>
|
||||
</StackPanel>
|
||||
<TextBlock x:Name="UpdateStatusText"
|
||||
Grid.Row="2"
|
||||
Grid.Row="3"
|
||||
AutomationProperties.AutomationId="CommandCenterUpdateStatusText"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
|
||||
@ -377,6 +377,11 @@ public sealed partial class StatusDetailWindow : WindowEx
|
||||
CopyText(BuildPortDiagnosticsSummary(_state.PortDiagnostics), "[CommandCenter] Copied port diagnostics");
|
||||
}
|
||||
|
||||
private void OnCopyBrowserSetup(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CopyText(BuildBrowserSetupGuidance(_state), "[CommandCenter] Copied browser setup guidance");
|
||||
}
|
||||
|
||||
private void OnOpenLogsFolder(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenFolder(Path.GetDirectoryName(Logger.LogFilePath), "logs");
|
||||
@ -514,6 +519,104 @@ public sealed partial class StatusDetailWindow : WindowEx
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
internal static string BuildBrowserSetupGuidance(GatewayCommandCenterState state)
|
||||
{
|
||||
var browserProxyPort = state.PortDiagnostics
|
||||
.FirstOrDefault(p => p.Purpose.Equals("Browser proxy host", StringComparison.OrdinalIgnoreCase))
|
||||
?.Port ?? 0;
|
||||
|
||||
return BuildBrowserSetupGuidance(browserProxyPort, state.Topology, state.Tunnel);
|
||||
}
|
||||
|
||||
internal static string BuildBrowserSetupGuidance(
|
||||
int browserProxyPort,
|
||||
GatewayTopologyInfo? topology,
|
||||
TunnelCommandCenterInfo? tunnel)
|
||||
{
|
||||
var portText = browserProxyPort is >= 1 and <= 65535
|
||||
? browserProxyPort.ToString(CultureInfo.InvariantCulture)
|
||||
: "<gateway-port+2>";
|
||||
var gatewayHost = string.IsNullOrWhiteSpace(topology?.Host) ? "<gateway-host>" : topology.Host;
|
||||
var gatewayPort = ResolveGatewayPort(topology?.GatewayUrl);
|
||||
var gatewayPortText = gatewayPort is >= 1 and <= 65535
|
||||
? gatewayPort.Value.ToString(CultureInfo.InvariantCulture)
|
||||
: "<gateway-port>";
|
||||
|
||||
var lines = new List<string>
|
||||
{
|
||||
"OpenClaw browser proxy setup",
|
||||
$"Expected local browser-control endpoint: http://127.0.0.1:{portText}/",
|
||||
"",
|
||||
"If the Gateway and browser are on this Windows machine:",
|
||||
"1. Ensure the upstream browser plugin is enabled in the Gateway config.",
|
||||
"2. Verify the browser control plane:",
|
||||
" openclaw browser --browser-profile openclaw doctor",
|
||||
" openclaw browser --browser-profile openclaw start",
|
||||
" openclaw browser --browser-profile openclaw tabs",
|
||||
"",
|
||||
"If the browser is on this Windows machine but the Gateway is remote:",
|
||||
"1. Run a browser-capable OpenClaw node host on this machine:",
|
||||
$" openclaw node run --host {gatewayHost} --port {gatewayPortText}",
|
||||
"2. Or install it as a user service:",
|
||||
$" openclaw node install --host {gatewayHost} --port {gatewayPortText}",
|
||||
" openclaw node start",
|
||||
"3. Keep nodeHost.browserProxy.enabled=true, and configure nodeHost.browserProxy.allowProfiles only if you want to restrict profile access.",
|
||||
"",
|
||||
"Gateway policy and auth checks:",
|
||||
"- The Gateway allowlist must permit browser.proxy for this node.",
|
||||
"- Browser-control auth must match the saved Gateway token/password in Settings.",
|
||||
"- Do not paste QR bootstrap tokens into the normal Gateway Token field."
|
||||
};
|
||||
|
||||
if (topology?.UsesSshTunnel == true)
|
||||
{
|
||||
lines.Add("");
|
||||
lines.Add("SSH tunnel mode:");
|
||||
lines.Add("- Prefer the tray-managed SSH tunnel with Browser proxy bridge enabled; it forwards local-port+2 to remote-port+2 automatically.");
|
||||
lines.Add($"- Manual forward shape: {BuildBrowserProxySshForwardHint(browserProxyPort, tunnel)}");
|
||||
}
|
||||
|
||||
return string.Join(Environment.NewLine, lines);
|
||||
}
|
||||
|
||||
private static string BuildBrowserProxySshForwardHint(int browserProxyPort, TunnelCommandCenterInfo? tunnel)
|
||||
{
|
||||
if (browserProxyPort is < 1 or > 65535)
|
||||
return "ssh -N -L <local-browser-port>:127.0.0.1:<remote-browser-port> <user>@<host>";
|
||||
|
||||
var target = string.IsNullOrWhiteSpace(tunnel?.User) || string.IsNullOrWhiteSpace(tunnel.Host)
|
||||
? "<user>@<host>"
|
||||
: $"{tunnel.User}@{tunnel.Host}";
|
||||
var remoteBrowserPort = TryParseEndpointPort(tunnel?.BrowserProxyRemoteEndpoint) ?? browserProxyPort;
|
||||
return $"ssh -N -L {browserProxyPort}:127.0.0.1:{remoteBrowserPort} {target}";
|
||||
}
|
||||
|
||||
private static int? TryParseEndpointPort(string? endpoint)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(endpoint))
|
||||
return null;
|
||||
|
||||
if (Uri.TryCreate($"tcp://{endpoint}", UriKind.Absolute, out var uri) &&
|
||||
uri.Port is >= 1 and <= 65535)
|
||||
{
|
||||
return uri.Port;
|
||||
}
|
||||
|
||||
var portDelimiter = endpoint.LastIndexOf(':');
|
||||
return portDelimiter >= 0 &&
|
||||
int.TryParse(endpoint[(portDelimiter + 1)..], NumberStyles.None, CultureInfo.InvariantCulture, out var port) &&
|
||||
port is >= 1 and <= 65535
|
||||
? port
|
||||
: null;
|
||||
}
|
||||
|
||||
private static int? ResolveGatewayPort(string? gatewayUrl)
|
||||
{
|
||||
return Uri.TryCreate(gatewayUrl, UriKind.Absolute, out var uri) && uri.Port is >= 1 and <= 65535
|
||||
? uri.Port
|
||||
: null;
|
||||
}
|
||||
|
||||
private static string RedactSupportPath(string? path)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(path))
|
||||
|
||||
@ -96,6 +96,7 @@ public class DeepLinkParserTests
|
||||
[InlineData("openclaw://config", "config")]
|
||||
[InlineData("openclaw://diagnostics", "diagnostics")]
|
||||
[InlineData("openclaw://support-context", "support-context")]
|
||||
[InlineData("openclaw://browser-setup", "browser-setup")]
|
||||
[InlineData("openclaw://restart-ssh-tunnel", "restart-ssh-tunnel")]
|
||||
public void ParseDeepLink_TrayUtilityEntrypoints(string uri, string expectedPath)
|
||||
{
|
||||
@ -228,6 +229,7 @@ public class DeepLinkParserTests
|
||||
[InlineData("openclaw://config", nameof(DeepLinkActions.OpenConfigFolder))]
|
||||
[InlineData("openclaw://diagnostics", nameof(DeepLinkActions.OpenDiagnosticsFolder))]
|
||||
[InlineData("openclaw://support-context", nameof(DeepLinkActions.CopySupportContext))]
|
||||
[InlineData("openclaw://browser-setup", nameof(DeepLinkActions.CopyBrowserSetupGuidance))]
|
||||
[InlineData("openclaw://restart-ssh-tunnel", nameof(DeepLinkActions.RestartSshTunnel))]
|
||||
public void Handle_InvokesExpectedAction(string uri, string expectedAction)
|
||||
{
|
||||
@ -244,6 +246,7 @@ public class DeepLinkParserTests
|
||||
OpenConfigFolder = () => invoked = nameof(DeepLinkActions.OpenConfigFolder),
|
||||
OpenDiagnosticsFolder = () => invoked = nameof(DeepLinkActions.OpenDiagnosticsFolder),
|
||||
CopySupportContext = () => invoked = nameof(DeepLinkActions.CopySupportContext),
|
||||
CopyBrowserSetupGuidance = () => invoked = nameof(DeepLinkActions.CopyBrowserSetupGuidance),
|
||||
RestartSshTunnel = () => invoked = nameof(DeepLinkActions.RestartSshTunnel)
|
||||
};
|
||||
|
||||
|
||||
@ -151,6 +151,8 @@ public class TrayMenuWindowMarkupTests
|
||||
Assert.Contains("Open Diagnostics Folder", source);
|
||||
Assert.Contains(@"openclaw://support-context", source);
|
||||
Assert.Contains("Copy Support Context", source);
|
||||
Assert.Contains(@"openclaw://browser-setup", source);
|
||||
Assert.Contains("Copy Browser Setup", source);
|
||||
Assert.Contains(@"openclaw://restart-ssh-tunnel", source);
|
||||
Assert.Contains("Restart SSH Tunnel", source);
|
||||
}
|
||||
@ -227,6 +229,8 @@ public class TrayMenuWindowMarkupTests
|
||||
Assert.Contains("OpenDiagnosticsFolder?.Invoke", source);
|
||||
Assert.Contains(@"case ""support-context"":", source);
|
||||
Assert.Contains("CopySupportContext?.Invoke", source);
|
||||
Assert.Contains(@"case ""browser-setup"":", source);
|
||||
Assert.Contains("CopyBrowserSetupGuidance?.Invoke", source);
|
||||
Assert.Contains(@"case ""restart-ssh-tunnel"":", source);
|
||||
Assert.Contains("RestartSshTunnel?.Invoke", source);
|
||||
}
|
||||
@ -308,10 +312,11 @@ public class TrayMenuWindowMarkupTests
|
||||
Assert.Contains(@"AutomationProperties.AutomationId=""CommandCenterOpenConfigButton""", xaml);
|
||||
Assert.Contains(@"AutomationProperties.AutomationId=""CommandCenterOpenDiagnosticsButton""", xaml);
|
||||
Assert.Contains(@"AutomationProperties.AutomationId=""CommandCenterCopySupportContextButton""", xaml);
|
||||
Assert.Contains(@"AutomationProperties.AutomationId=""CommandCenterCopyBrowserSetupButton""", xaml);
|
||||
Assert.Contains(@"AutomationProperties.AutomationId=""CommandCenterRestartSshTunnelButton""", xaml);
|
||||
Assert.Contains(@"AutomationProperties.AutomationId=""CommandCenterUpdateStatusText""", xaml);
|
||||
Assert.Matches(
|
||||
new Regex(@"<Grid\.RowDefinitions>\s*<RowDefinition/>\s*<RowDefinition/>\s*<RowDefinition/>\s*</Grid\.RowDefinitions>\s*<StackPanel Grid\.Row=""0""", RegexOptions.Singleline),
|
||||
new Regex(@"<Grid\.RowDefinitions>\s*<RowDefinition/>\s*<RowDefinition/>\s*<RowDefinition/>\s*<RowDefinition/>\s*</Grid\.RowDefinitions>\s*<StackPanel Grid\.Row=""0""", RegexOptions.Singleline),
|
||||
xaml);
|
||||
}
|
||||
|
||||
@ -391,8 +396,12 @@ public class TrayMenuWindowMarkupTests
|
||||
Assert.Contains("<remote-gateway-port+2>", appSource);
|
||||
Assert.Contains("BuildBrowserProxyAuthWarnings(nodes)", appSource);
|
||||
Assert.Contains("Do not paste QR bootstrap tokens into the normal gateway token field.", appSource);
|
||||
Assert.Contains("BuildBrowserProxyHostGuidance(port.Port)", appSource);
|
||||
Assert.Contains("Start a compatible OpenClaw browser-control host", appSource);
|
||||
Assert.Contains("StatusDetailWindow.BuildBrowserSetupGuidance(port.Port, topology, tunnel)", appSource);
|
||||
Assert.Contains("Copy browser setup guidance", appSource);
|
||||
Assert.Contains("openclaw node run --host", source);
|
||||
Assert.Contains("openclaw browser --browser-profile openclaw doctor", source);
|
||||
Assert.Contains(@"topology.Host", source);
|
||||
Assert.DoesNotContain("RedactSupportValue(topology.Host)", source);
|
||||
var portDiagnosticsSourcePath = Path.Combine(
|
||||
GetRepositoryRoot(),
|
||||
"src",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user