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);
}
}
}