diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..e597184 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,13 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + day: "monday" diff --git a/NuGet.Config b/NuGet.Config new file mode 100644 index 0000000..4e800bf --- /dev/null +++ b/NuGet.Config @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/global.json b/global.json index b24aad6..512142d 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "10.0.100", - "rollForward": "latestPatch" + "rollForward": "latestFeature" } } diff --git a/src/OpenClaw.Shared/Capabilities/SystemCapability.cs b/src/OpenClaw.Shared/Capabilities/SystemCapability.cs index bbfca5f..60c7c33 100644 --- a/src/OpenClaw.Shared/Capabilities/SystemCapability.cs +++ b/src/OpenClaw.Shared/Capabilities/SystemCapability.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading.Tasks; namespace OpenClaw.Shared.Capabilities; @@ -122,8 +121,8 @@ public class SystemCapability : NodeCapabilityBase if (OperatingSystem.IsWindows()) { var pathext = Environment.GetEnvironmentVariable("PATHEXT") ?? ".EXE;.CMD;.BAT;.COM"; - extensions.AddRange(pathext.Split(';', StringSplitOptions.RemoveEmptyEntries) - .Select(e => e.ToLowerInvariant())); + foreach (var e in pathext.Split(';', StringSplitOptions.RemoveEmptyEntries)) + extensions.Add(e.ToLowerInvariant()); } else { @@ -266,7 +265,9 @@ public class SystemCapability : NodeCapabilityBase var envResult = ExecEnvSanitizer.Sanitize(env); if (envResult.Blocked.Length > 0) { - var blockedList = string.Join(", ", envResult.Blocked.OrderBy(n => n, StringComparer.OrdinalIgnoreCase)); + var blockedNames = (string[])envResult.Blocked.Clone(); + Array.Sort(blockedNames, StringComparer.OrdinalIgnoreCase); + var blockedList = string.Join(", ", blockedNames); Logger.Warn($"system.run DENIED: blocked environment overrides [{blockedList}]"); return Error($"Unsafe environment variable override blocked: {blockedList}"); } @@ -346,18 +347,26 @@ public class SystemCapability : NodeCapabilityBase } var data = _approvalPolicy.GetPolicyData(); - return Success(new + var rules = data.Rules; + var rulesSummary = new object[rules.Count]; + for (var i = 0; i < rules.Count; i++) { - enabled = true, - defaultAction = data.DefaultAction.ToString().ToLowerInvariant(), - rules = data.Rules.Select(r => new + var r = rules[i]; + rulesSummary[i] = new { pattern = r.Pattern, action = r.Action.ToString().ToLowerInvariant(), shells = r.Shells, description = r.Description, enabled = r.Enabled - }).ToArray() + }; + } + + return Success(new + { + enabled = true, + defaultAction = data.DefaultAction.ToString().ToLowerInvariant(), + rules = rulesSummary }); } @@ -403,10 +412,13 @@ public class SystemCapability : NodeCapabilityBase if (ruleEl.TryGetProperty("shells", out var shellsEl) && shellsEl.ValueKind == System.Text.Json.JsonValueKind.Array) { - rule.Shells = shellsEl.EnumerateArray() - .Where(s => s.ValueKind == System.Text.Json.JsonValueKind.String) - .Select(s => s.GetString() ?? "") - .ToArray(); + var shellsList = new List(shellsEl.GetArrayLength()); + foreach (var s in shellsEl.EnumerateArray()) + { + if (s.ValueKind == System.Text.Json.JsonValueKind.String) + shellsList.Add(s.GetString() ?? ""); + } + rule.Shells = shellsList.ToArray(); } rules.Add(rule); diff --git a/src/OpenClaw.Shared/ExecApprovalPolicy.cs b/src/OpenClaw.Shared/ExecApprovalPolicy.cs index edf37b7..d761adc 100644 --- a/src/OpenClaw.Shared/ExecApprovalPolicy.cs +++ b/src/OpenClaw.Shared/ExecApprovalPolicy.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; @@ -98,8 +97,7 @@ public class ExecApprovalPolicy }; } - // Compute once; only used if any rule has shell filters. - var normalizedShell = (shell ?? "powershell").ToLowerInvariant(); + var shellSpan = (shell ?? "powershell").AsSpan(); foreach (var rule in _rules) { @@ -111,7 +109,7 @@ public class ExecApprovalPolicy var shellMatched = false; foreach (var s in rule.Shells) { - if (s.Equals(normalizedShell, StringComparison.OrdinalIgnoreCase)) + if (s.AsSpan().Equals(shellSpan, StringComparison.OrdinalIgnoreCase)) { shellMatched = true; break; diff --git a/src/OpenClaw.Shared/GatewayUrlHelper.cs b/src/OpenClaw.Shared/GatewayUrlHelper.cs index 40bcd34..8315c76 100644 --- a/src/OpenClaw.Shared/GatewayUrlHelper.cs +++ b/src/OpenClaw.Shared/GatewayUrlHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; namespace OpenClaw.Shared; @@ -6,7 +7,8 @@ public static class GatewayUrlHelper { public const string ValidationMessage = "Gateway URL must be a valid URL (ws://, wss://, http://, or https://)."; - private static readonly char[] s_authorityTerminators = { '/', '?', '#' }; + private static readonly SearchValues s_authorityTerminators = + SearchValues.Create("/?#"); public static bool IsValidGatewayUrl(string? gatewayUrl) => TryNormalizeWebSocketUrl(gatewayUrl, out _); @@ -143,11 +145,8 @@ public static class GatewayUrlHelper } var authorityStart = schemeSeparator + 3; - var authorityEnd = url.IndexOfAny(s_authorityTerminators, authorityStart); - if (authorityEnd < 0) - { - authorityEnd = url.Length; - } + var relativeEnd = url.AsSpan(authorityStart).IndexOfAny(s_authorityTerminators); + var authorityEnd = relativeEnd < 0 ? url.Length : authorityStart + relativeEnd; var atIndex = url.IndexOf('@', authorityStart); if (atIndex < 0 || atIndex >= authorityEnd) diff --git a/src/OpenClaw.Shared/LocalCommandRunner.cs b/src/OpenClaw.Shared/LocalCommandRunner.cs index c1066cf..355bd34 100644 --- a/src/OpenClaw.Shared/LocalCommandRunner.cs +++ b/src/OpenClaw.Shared/LocalCommandRunner.cs @@ -164,9 +164,9 @@ public class LocalCommandRunner : ICommandRunner private static (string fileName, string arguments) BuildProcessArgs(CommandRequest request) { - var shell = (request.Shell ?? "powershell").ToLowerInvariant(); + var shell = request.Shell ?? "powershell"; var command = request.Command; - var isCmd = shell == "cmd"; + var isCmd = shell.Equals("cmd", StringComparison.OrdinalIgnoreCase); if (request.Args is { Length: > 0 }) { @@ -176,12 +176,11 @@ public class LocalCommandRunner : ICommandRunner command = command + " " + string.Join(" ", quoted); } - return shell switch - { - "cmd" => ("cmd.exe", $"/C {command}"), - "pwsh" => ("pwsh.exe", $"-NoProfile -NonInteractive -Command {command}"), - _ => ("powershell.exe", $"-NoProfile -NonInteractive -Command {command}") - }; + if (isCmd) + return ("cmd.exe", $"/C {command}"); + if (shell.Equals("pwsh", StringComparison.OrdinalIgnoreCase)) + return ("pwsh.exe", $"-NoProfile -NonInteractive -Command {command}"); + return ("powershell.exe", $"-NoProfile -NonInteractive -Command {command}"); } private void KillProcess(Process process) diff --git a/src/OpenClaw.Shared/NotificationCategorizer.cs b/src/OpenClaw.Shared/NotificationCategorizer.cs index 9b81c86..622ac55 100644 --- a/src/OpenClaw.Shared/NotificationCategorizer.cs +++ b/src/OpenClaw.Shared/NotificationCategorizer.cs @@ -41,18 +41,18 @@ public class NotificationCategorizer ["error"] = ("⚠️ Error", "error"), }.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); - private static readonly FrozenDictionary CategoryTitles = - new Dictionary(StringComparer.OrdinalIgnoreCase) + private static readonly FrozenDictionary CategoryTypeMap = + new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["health"] = "🩸 Blood Sugar Alert", - ["urgent"] = "🚨 Urgent Alert", - ["reminder"] = "⏰ Reminder", - ["stock"] = "📦 Stock Alert", - ["email"] = "📧 Email", - ["calendar"] = "📅 Calendar", - ["error"] = "⚠️ Error", - ["build"] = "🔨 Build", - ["info"] = "🤖 OpenClaw", + ["health"] = ("🩸 Blood Sugar Alert", "health"), + ["urgent"] = ("🚨 Urgent Alert", "urgent"), + ["reminder"] = ("⏰ Reminder", "reminder"), + ["stock"] = ("📦 Stock Alert", "stock"), + ["email"] = ("📧 Email", "email"), + ["calendar"] = ("📅 Calendar", "calendar"), + ["error"] = ("⚠️ Error", "error"), + ["build"] = ("🔨 Build", "build"), + ["info"] = ("🤖 OpenClaw", "info"), }.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase); /// @@ -83,9 +83,10 @@ public class NotificationCategorizer if (!rule.Enabled) continue; if (MatchesRule(searchText, rule)) { + if (CategoryTypeMap.TryGetValue(rule.Category, out var categoryResult)) + return categoryResult; var cat = rule.Category.ToLowerInvariant(); - var title = CategoryTitles.GetValueOrDefault(cat, "🤖 OpenClaw"); - return (title, cat); + return ("🤖 OpenClaw", cat); } } }