Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
108 changes: 46 additions & 62 deletions src/OpenClaw.Shared/Capabilities/SystemCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,44 +163,50 @@ private NodeInvokeResponse HandleWhich(NodeInvokeRequest request)
private static string FormatExecCommand(string[] argv) => ShellQuoting.FormatExecCommand(argv);

/// <summary>
/// Pre-flight for system.run: echoes back the execution plan without running anything.
/// The gateway uses this to build its approval context before the actual run.
/// Parses a JSON "command" property as either a string array or a plain string.
/// Returns the argv array (command as first element) or null if missing/invalid.
/// </summary>
private NodeInvokeResponse HandleRunPrepare(NodeInvokeRequest request)
private static string[]? TryParseArgv(System.Text.Json.JsonElement requestArgs)
{
string? command = null;
string[]? argv = null;
string? rawCommand = null;
string? cwd = null;

if (request.Args.ValueKind != System.Text.Json.JsonValueKind.Undefined &&
request.Args.TryGetProperty("command", out var cmdEl))
if (requestArgs.ValueKind == System.Text.Json.JsonValueKind.Undefined ||
!requestArgs.TryGetProperty("command", out var cmdEl))
return null;

if (cmdEl.ValueKind == System.Text.Json.JsonValueKind.Array)
{
if (cmdEl.ValueKind == System.Text.Json.JsonValueKind.Array)
var list = new List<string>();
foreach (var item in cmdEl.EnumerateArray())
{
var list = new List<string>();
foreach (var item in cmdEl.EnumerateArray())
{
if (item.ValueKind == System.Text.Json.JsonValueKind.String)
list.Add(item.GetString() ?? "");
}
argv = list.ToArray();
command = argv.Length > 0 ? argv[0] : null;
}
else if (cmdEl.ValueKind == System.Text.Json.JsonValueKind.String)
{
command = cmdEl.GetString();
argv = command != null ? new[] { command } : null;
if (item.ValueKind == System.Text.Json.JsonValueKind.String)
list.Add(item.GetString() ?? "");
}
return list.Count > 0 ? list.ToArray() : null;
}

if (string.IsNullOrWhiteSpace(command) || argv == null || argv.Length == 0)
if (cmdEl.ValueKind == System.Text.Json.JsonValueKind.String)
{
var command = cmdEl.GetString();
return command != null ? new[] { command } : null;
}

return null;
}

/// <summary>
/// Pre-flight for system.run: echoes back the execution plan without running anything.
/// The gateway uses this to build its approval context before the actual run.
/// </summary>
private NodeInvokeResponse HandleRunPrepare(NodeInvokeRequest request)
{
var argv = TryParseArgv(request.Args);
if (argv == null || argv.Length == 0 || string.IsNullOrWhiteSpace(argv[0]))
{
return Error("Missing command parameter");
}

rawCommand = GetStringArg(request.Args, "rawCommand");
cwd = GetStringArg(request.Args, "cwd");
var command = argv[0];
var rawCommand = GetStringArg(request.Args, "rawCommand");
var cwd = GetStringArg(request.Args, "cwd");
var agentId = GetStringArg(request.Args, "agentId");
var sessionKey = GetStringArg(request.Args, "sessionKey");

Expand Down Expand Up @@ -229,44 +235,22 @@ private async Task<NodeInvokeResponse> HandleRunAsync(NodeInvokeRequest request)

// Per OpenClaw spec, "command" is an argv array (e.g. ["echo","Hello"]).
// Also accept a plain string for backward compatibility.
string? command = null;
string[]? args = null;
var argv = TryParseArgv(request.Args);
string? command = argv?[0];
string[]? args = argv?.Length > 1 ? argv.Skip(1).ToArray() : null;

if (request.Args.ValueKind != System.Text.Json.JsonValueKind.Undefined &&
request.Args.TryGetProperty("command", out var cmdEl))
// When command is a string, also check for separate "args" array
if (argv?.Length == 1 && request.Args.TryGetProperty("args", out var argsEl) &&
argsEl.ValueKind == System.Text.Json.JsonValueKind.Array)
{
if (cmdEl.ValueKind == System.Text.Json.JsonValueKind.Array)
{
var argv = new List<string>();
foreach (var item in cmdEl.EnumerateArray())
{
if (item.ValueKind == System.Text.Json.JsonValueKind.String)
argv.Add(item.GetString() ?? "");
}
if (argv.Count > 0)
{
command = argv[0];
args = argv.Count > 1 ? argv.Skip(1).ToArray() : null;
}
}
else if (cmdEl.ValueKind == System.Text.Json.JsonValueKind.String)
var list = new List<string>();
foreach (var item in argsEl.EnumerateArray())
{
command = cmdEl.GetString();

// When command is a string, also check for separate "args" array
if (request.Args.TryGetProperty("args", out var argsEl) &&
argsEl.ValueKind == System.Text.Json.JsonValueKind.Array)
{
var list = new List<string>();
foreach (var item in argsEl.EnumerateArray())
{
if (item.ValueKind == System.Text.Json.JsonValueKind.String)
list.Add(item.GetString() ?? "");
}
if (list.Count > 0)
args = list.ToArray();
}
if (item.ValueKind == System.Text.Json.JsonValueKind.String)
list.Add(item.GetString() ?? "");
}
if (list.Count > 0)
args = list.ToArray();
}

if (string.IsNullOrWhiteSpace(command))
Expand Down Expand Up @@ -402,7 +386,7 @@ private NodeInvokeResponse HandleExecApprovalsSet(NodeInvokeRequest request)
if (ruleEl.TryGetProperty("description", out var descEl) && descEl.ValueKind == System.Text.Json.JsonValueKind.String)
rule.Description = descEl.GetString();

if (ruleEl.TryGetProperty("enabled", out var enEl) && enEl.ValueKind == System.Text.Json.JsonValueKind.True || enEl.ValueKind == System.Text.Json.JsonValueKind.False)
if (ruleEl.TryGetProperty("enabled", out var enEl) && (enEl.ValueKind == System.Text.Json.JsonValueKind.True || enEl.ValueKind == System.Text.Json.JsonValueKind.False))
rule.Enabled = enEl.GetBoolean();

if (ruleEl.TryGetProperty("shells", out var shellsEl) && shellsEl.ValueKind == System.Text.Json.JsonValueKind.Array)
Expand Down
81 changes: 41 additions & 40 deletions src/OpenClaw.Tray.WinUI/App.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@
StartDeepLinkServer();

// Register global hotkey if enabled
if (_settings.GlobalHotkeyEnabled)

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / test

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build-msix (win-x64)

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build (win-arm64)

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build (win-arm64)

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build (win-arm64)

Dereference of a possibly null reference.

Check warning on line 284 in src/OpenClaw.Tray.WinUI/App.xaml.cs

View workflow job for this annotation

GitHub Actions / build-msix (win-arm64)

Dereference of a possibly null reference.
{
_globalHotkey = new GlobalHotkeyService();
_globalHotkey.HotkeyPressed += OnGlobalHotkeyPressed;
Expand Down Expand Up @@ -568,10 +568,9 @@
global::Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(dataPackage);

// Show toast confirming copy
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_DeviceIdCopied"))
.AddText(string.Format(LocalizationHelper.GetString("Toast_DeviceIdCopiedDetail"), _nodeService.ShortDeviceId))
.Show();
.AddText(string.Format(LocalizationHelper.GetString("Toast_DeviceIdCopiedDetail"), _nodeService.ShortDeviceId)));
}
catch (Exception ex)
{
Expand All @@ -597,10 +596,9 @@
dataPackage.SetText(summary);
global::Windows.ApplicationModel.DataTransfer.Clipboard.SetContent(dataPackage);

new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_NodeSummaryCopied"))
.AddText(string.Format(LocalizationHelper.GetString("Toast_NodeSummaryCopiedDetail"), _lastNodes.Length))
.Show();
.AddText(string.Format(LocalizationHelper.GetString("Toast_NodeSummaryCopiedDetail"), _lastNodes.Length)));
}
catch (Exception ex)
{
Expand Down Expand Up @@ -654,10 +652,9 @@

if (!sent)
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_SessionActionFailed"))
.AddText(LocalizationHelper.GetString("Toast_SessionActionFailedDetail"))
.Show();
.AddText(LocalizationHelper.GetString("Toast_SessionActionFailedDetail")));
return;
}

Expand All @@ -671,10 +668,9 @@
Logger.Warn($"Session action error ({action}): {ex.Message}");
try
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_SessionActionFailed"))
.AddText(ex.Message)
.Show();
.AddText(ex.Message));
}
catch { }
}
Expand Down Expand Up @@ -1157,10 +1153,9 @@
{
try
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_NodeModeActive"))
.AddText(LocalizationHelper.GetString("Toast_NodeModeActiveDetail"))
.Show();
.AddText(LocalizationHelper.GetString("Toast_NodeModeActiveDetail")));
}
catch { /* ignore */ }
}
Expand All @@ -1176,18 +1171,16 @@
{
AddRecentActivity("Node pairing pending", category: "node", dashboardPath: "nodes", nodeId: args.DeviceId);
// Show toast with approval instructions
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_PairingPending"))
.AddText(string.Format(LocalizationHelper.GetString("Toast_PairingPendingDetail"), args.DeviceId.Substring(0, 16)))
.Show();
.AddText(string.Format(LocalizationHelper.GetString("Toast_PairingPendingDetail"), args.DeviceId.Substring(0, 16))));
}
else if (args.Status == OpenClaw.Shared.PairingStatus.Paired)
{
AddRecentActivity("Node paired", category: "node", dashboardPath: "nodes", nodeId: args.DeviceId);
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_NodePaired"))
.AddText(LocalizationHelper.GetString("Toast_NodePairedDetail"))
.Show();
.AddText(LocalizationHelper.GetString("Toast_NodePairedDetail")));
}
}
catch { /* ignore */ }
Expand All @@ -1200,10 +1193,9 @@
// Agent requested a notification via node.invoke system.notify
try
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(args.Title)
.AddText(args.Body)
.Show();
.AddText(args.Body));
}
catch (Exception ex)
{
Expand Down Expand Up @@ -1376,10 +1368,9 @@
dashboardPath: !string.IsNullOrWhiteSpace(result.Key) ? $"sessions/{result.Key}" : "sessions",
sessionKey: result.Key);

new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(title)
.AddText(message)
.Show();
.AddText(message));
}
catch (Exception ex)
{
Expand Down Expand Up @@ -1434,7 +1425,7 @@
.AddArgument("action", "open_chat"));
}

builder.Show();
ShowToast(builder);
}
catch (Exception ex)
{
Expand Down Expand Up @@ -1498,10 +1489,9 @@
{
if (userInitiated)
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_HealthCheck"))
.AddText(LocalizationHelper.GetString("Toast_HealthCheckNotConnected"))
.Show();
.AddText(LocalizationHelper.GetString("Toast_HealthCheckNotConnected")));
}
return;
}
Expand All @@ -1512,21 +1502,19 @@
await _gatewayClient.CheckHealthAsync();
if (userInitiated)
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_HealthCheck"))
.AddText(LocalizationHelper.GetString("Toast_HealthCheckSent"))
.Show();
.AddText(LocalizationHelper.GetString("Toast_HealthCheckSent")));
}
}
catch (Exception ex)
{
Logger.Warn($"Health check failed: {ex.Message}");
if (userInitiated)
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_HealthCheckFailed"))
.AddText(ex.Message)
.Show();
.AddText(ex.Message));
}
}
}
Expand Down Expand Up @@ -1749,13 +1737,12 @@

try
{
new ToastContentBuilder()
ShowToast(new ToastContentBuilder()
.AddText(LocalizationHelper.GetString("Toast_ActivityStreamTip"))
.AddText(LocalizationHelper.GetString("Toast_ActivityStreamTipDetail"))
.AddButton(new ToastButton()
.SetContent(LocalizationHelper.GetString("Toast_ActivityStreamTipButton"))
.AddArgument("action", "open_activity"))
.Show();
.AddArgument("action", "open_activity")));
}
catch (Exception ex)
{
Expand All @@ -1765,6 +1752,20 @@

#endregion

private void ShowToast(ToastContentBuilder builder)
{
var sound = _settings?.NotificationSound;
if (string.Equals(sound, "None", StringComparison.OrdinalIgnoreCase))
{
builder.AddAudio(new ToastAudio { Silent = true });
}
else if (string.Equals(sound, "Subtle", StringComparison.OrdinalIgnoreCase))
{
builder.AddAudio(new Uri("ms-winsoundevent:Notification.IM"), silent: false);
}
builder.Show();
}

#region Actions

private void OpenDashboard(string? path = null)
Expand Down
Loading
Loading