mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 23:02:02 +03:00
refactor(agents): share glob matcher
This commit is contained in:
@@ -0,0 +1,56 @@
|
|||||||
|
export type CompiledGlobPattern =
|
||||||
|
| { kind: "all" }
|
||||||
|
| { kind: "exact"; value: string }
|
||||||
|
| { kind: "regex"; value: RegExp };
|
||||||
|
|
||||||
|
function escapeRegex(value: string) {
|
||||||
|
// Standard "escape string for regex literal" pattern.
|
||||||
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileGlobPattern(params: {
|
||||||
|
raw: string;
|
||||||
|
normalize: (value: string) => string;
|
||||||
|
}): CompiledGlobPattern {
|
||||||
|
const normalized = params.normalize(params.raw);
|
||||||
|
if (!normalized) {
|
||||||
|
return { kind: "exact", value: "" };
|
||||||
|
}
|
||||||
|
if (normalized === "*") {
|
||||||
|
return { kind: "all" };
|
||||||
|
}
|
||||||
|
if (!normalized.includes("*")) {
|
||||||
|
return { kind: "exact", value: normalized };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
kind: "regex",
|
||||||
|
value: new RegExp(`^${escapeRegex(normalized).replaceAll("\\*", ".*")}$`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function compileGlobPatterns(params: {
|
||||||
|
raw?: string[] | undefined;
|
||||||
|
normalize: (value: string) => string;
|
||||||
|
}): CompiledGlobPattern[] {
|
||||||
|
if (!Array.isArray(params.raw)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return params.raw
|
||||||
|
.map((raw) => compileGlobPattern({ raw, normalize: params.normalize }))
|
||||||
|
.filter((pattern) => pattern.kind !== "exact" || pattern.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function matchesAnyGlobPattern(value: string, patterns: CompiledGlobPattern[]): boolean {
|
||||||
|
for (const pattern of patterns) {
|
||||||
|
if (pattern.kind === "all") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (pattern.kind === "exact" && value === pattern.value) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (pattern.kind === "regex" && pattern.value.test(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
@@ -1,69 +1,26 @@
|
|||||||
import type { ContextPruningToolMatch } from "./settings.js";
|
import type { ContextPruningToolMatch } from "./settings.js";
|
||||||
|
import { compileGlobPatterns, matchesAnyGlobPattern } from "../../glob-pattern.js";
|
||||||
|
|
||||||
function normalizePatterns(patterns?: string[]): string[] {
|
function normalizeGlob(value: string) {
|
||||||
if (!Array.isArray(patterns)) {
|
return String(value ?? "")
|
||||||
return [];
|
.trim()
|
||||||
}
|
.toLowerCase();
|
||||||
return patterns
|
|
||||||
.map((p) =>
|
|
||||||
String(p ?? "")
|
|
||||||
.trim()
|
|
||||||
.toLowerCase(),
|
|
||||||
)
|
|
||||||
.filter(Boolean);
|
|
||||||
}
|
|
||||||
|
|
||||||
type CompiledPattern =
|
|
||||||
| { kind: "all" }
|
|
||||||
| { kind: "exact"; value: string }
|
|
||||||
| { kind: "regex"; value: RegExp };
|
|
||||||
|
|
||||||
function compilePattern(pattern: string): CompiledPattern {
|
|
||||||
if (pattern === "*") {
|
|
||||||
return { kind: "all" };
|
|
||||||
}
|
|
||||||
if (!pattern.includes("*")) {
|
|
||||||
return { kind: "exact", value: pattern };
|
|
||||||
}
|
|
||||||
|
|
||||||
const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
const re = new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`);
|
|
||||||
return { kind: "regex", value: re };
|
|
||||||
}
|
|
||||||
|
|
||||||
function compilePatterns(patterns?: string[]): CompiledPattern[] {
|
|
||||||
return normalizePatterns(patterns).map(compilePattern);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesAny(toolName: string, patterns: CompiledPattern[]): boolean {
|
|
||||||
for (const p of patterns) {
|
|
||||||
if (p.kind === "all") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (p.kind === "exact" && toolName === p.value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (p.kind === "regex" && p.value.test(toolName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeToolPrunablePredicate(
|
export function makeToolPrunablePredicate(
|
||||||
match: ContextPruningToolMatch,
|
match: ContextPruningToolMatch,
|
||||||
): (toolName: string) => boolean {
|
): (toolName: string) => boolean {
|
||||||
const deny = compilePatterns(match.deny);
|
const deny = compileGlobPatterns({ raw: match.deny, normalize: normalizeGlob });
|
||||||
const allow = compilePatterns(match.allow);
|
const allow = compileGlobPatterns({ raw: match.allow, normalize: normalizeGlob });
|
||||||
|
|
||||||
return (toolName: string) => {
|
return (toolName: string) => {
|
||||||
const normalized = toolName.trim().toLowerCase();
|
const normalized = normalizeGlob(toolName);
|
||||||
if (matchesAny(normalized, deny)) {
|
if (matchesAnyGlobPattern(normalized, deny)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (allow.length === 0) {
|
if (allow.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return matchesAny(normalized, allow);
|
return matchesAnyGlobPattern(normalized, allow);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,70 +6,30 @@ import { resolveChannelGroupToolsPolicy } from "../config/group-policy.js";
|
|||||||
import { resolveThreadParentSessionKey } from "../sessions/session-key-utils.js";
|
import { resolveThreadParentSessionKey } from "../sessions/session-key-utils.js";
|
||||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||||
import { resolveAgentConfig, resolveAgentIdFromSessionKey } from "./agent-scope.js";
|
import { resolveAgentConfig, resolveAgentIdFromSessionKey } from "./agent-scope.js";
|
||||||
|
import { compileGlobPatterns, matchesAnyGlobPattern } from "./glob-pattern.js";
|
||||||
import { expandToolGroups, normalizeToolName } from "./tool-policy.js";
|
import { expandToolGroups, normalizeToolName } from "./tool-policy.js";
|
||||||
|
|
||||||
type CompiledPattern =
|
|
||||||
| { kind: "all" }
|
|
||||||
| { kind: "exact"; value: string }
|
|
||||||
| { kind: "regex"; value: RegExp };
|
|
||||||
|
|
||||||
function compilePattern(pattern: string): CompiledPattern {
|
|
||||||
const normalized = normalizeToolName(pattern);
|
|
||||||
if (!normalized) {
|
|
||||||
return { kind: "exact", value: "" };
|
|
||||||
}
|
|
||||||
if (normalized === "*") {
|
|
||||||
return { kind: "all" };
|
|
||||||
}
|
|
||||||
if (!normalized.includes("*")) {
|
|
||||||
return { kind: "exact", value: normalized };
|
|
||||||
}
|
|
||||||
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
return {
|
|
||||||
kind: "regex",
|
|
||||||
value: new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function compilePatterns(patterns?: string[]): CompiledPattern[] {
|
|
||||||
if (!Array.isArray(patterns)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return expandToolGroups(patterns)
|
|
||||||
.map(compilePattern)
|
|
||||||
.filter((pattern) => pattern.kind !== "exact" || pattern.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesAny(name: string, patterns: CompiledPattern[]): boolean {
|
|
||||||
for (const pattern of patterns) {
|
|
||||||
if (pattern.kind === "all") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (pattern.kind === "exact" && name === pattern.value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (pattern.kind === "regex" && pattern.value.test(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeToolPolicyMatcher(policy: SandboxToolPolicy) {
|
function makeToolPolicyMatcher(policy: SandboxToolPolicy) {
|
||||||
const deny = compilePatterns(policy.deny);
|
const deny = compileGlobPatterns({
|
||||||
const allow = compilePatterns(policy.allow);
|
raw: expandToolGroups(policy.deny ?? []),
|
||||||
|
normalize: normalizeToolName,
|
||||||
|
});
|
||||||
|
const allow = compileGlobPatterns({
|
||||||
|
raw: expandToolGroups(policy.allow ?? []),
|
||||||
|
normalize: normalizeToolName,
|
||||||
|
});
|
||||||
return (name: string) => {
|
return (name: string) => {
|
||||||
const normalized = normalizeToolName(name);
|
const normalized = normalizeToolName(name);
|
||||||
if (matchesAny(normalized, deny)) {
|
if (matchesAnyGlobPattern(normalized, deny)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (allow.length === 0) {
|
if (allow.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (matchesAny(normalized, allow)) {
|
if (matchesAnyGlobPattern(normalized, allow)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (normalized === "apply_patch" && matchesAny("exec", allow)) {
|
if (normalized === "apply_patch" && matchesAnyGlobPattern("exec", allow)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -5,67 +5,31 @@ import type {
|
|||||||
SandboxToolPolicySource,
|
SandboxToolPolicySource,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import { resolveAgentConfig } from "../agent-scope.js";
|
import { resolveAgentConfig } from "../agent-scope.js";
|
||||||
|
import { compileGlobPatterns, matchesAnyGlobPattern } from "../glob-pattern.js";
|
||||||
import { expandToolGroups } from "../tool-policy.js";
|
import { expandToolGroups } from "../tool-policy.js";
|
||||||
import { DEFAULT_TOOL_ALLOW, DEFAULT_TOOL_DENY } from "./constants.js";
|
import { DEFAULT_TOOL_ALLOW, DEFAULT_TOOL_DENY } from "./constants.js";
|
||||||
|
|
||||||
type CompiledPattern =
|
function normalizeGlob(value: string) {
|
||||||
| { kind: "all" }
|
return value.trim().toLowerCase();
|
||||||
| { kind: "exact"; value: string }
|
|
||||||
| { kind: "regex"; value: RegExp };
|
|
||||||
|
|
||||||
function compilePattern(pattern: string): CompiledPattern {
|
|
||||||
const normalized = pattern.trim().toLowerCase();
|
|
||||||
if (!normalized) {
|
|
||||||
return { kind: "exact", value: "" };
|
|
||||||
}
|
|
||||||
if (normalized === "*") {
|
|
||||||
return { kind: "all" };
|
|
||||||
}
|
|
||||||
if (!normalized.includes("*")) {
|
|
||||||
return { kind: "exact", value: normalized };
|
|
||||||
}
|
|
||||||
const escaped = normalized.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
return {
|
|
||||||
kind: "regex",
|
|
||||||
value: new RegExp(`^${escaped.replaceAll("\\*", ".*")}$`),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function compilePatterns(patterns?: string[]): CompiledPattern[] {
|
|
||||||
if (!Array.isArray(patterns)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return expandToolGroups(patterns)
|
|
||||||
.map(compilePattern)
|
|
||||||
.filter((pattern) => pattern.kind !== "exact" || pattern.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchesAny(name: string, patterns: CompiledPattern[]): boolean {
|
|
||||||
for (const pattern of patterns) {
|
|
||||||
if (pattern.kind === "all") {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (pattern.kind === "exact" && name === pattern.value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (pattern.kind === "regex" && pattern.value.test(name)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isToolAllowed(policy: SandboxToolPolicy, name: string) {
|
export function isToolAllowed(policy: SandboxToolPolicy, name: string) {
|
||||||
const normalized = name.trim().toLowerCase();
|
const normalized = normalizeGlob(name);
|
||||||
const deny = compilePatterns(policy.deny);
|
const deny = compileGlobPatterns({
|
||||||
if (matchesAny(normalized, deny)) {
|
raw: expandToolGroups(policy.deny ?? []),
|
||||||
|
normalize: normalizeGlob,
|
||||||
|
});
|
||||||
|
if (matchesAnyGlobPattern(normalized, deny)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const allow = compilePatterns(policy.allow);
|
const allow = compileGlobPatterns({
|
||||||
|
raw: expandToolGroups(policy.allow ?? []),
|
||||||
|
normalize: normalizeGlob,
|
||||||
|
});
|
||||||
if (allow.length === 0) {
|
if (allow.length === 0) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return matchesAny(normalized, allow);
|
return matchesAnyGlobPattern(normalized, allow);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveSandboxToolPolicyForAgent(
|
export function resolveSandboxToolPolicyForAgent(
|
||||||
|
|||||||
Reference in New Issue
Block a user