eng: handle repo-assist easy wins

Add root NuGet package source mapping and Dependabot config, allow .NET SDK feature-band roll-forward, and apply low-risk allocation cleanup from repo-assist suggestions.

Closes #208

Closes #214

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Scott Hanselman 2026-04-26 19:46:27 -07:00
parent c1296be7fd
commit d83e81d451
8 changed files with 79 additions and 45 deletions

13
.github/dependabot.yml vendored Normal file
View File

@ -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"

12
NuGet.Config Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>

View File

@ -1,6 +1,6 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestPatch"
"rollForward": "latestFeature"
}
}

View File

@ -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<string>(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);

View File

@ -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;

View File

@ -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<char> 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)

View File

@ -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)

View File

@ -41,18 +41,18 @@ public class NotificationCategorizer
["error"] = ("⚠️ Error", "error"),
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
private static readonly FrozenDictionary<string, string> CategoryTitles =
new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
private static readonly FrozenDictionary<string, (string title, string type)> CategoryTypeMap =
new Dictionary<string, (string title, string type)>(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);
/// <summary>
@ -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);
}
}
}