diff --git a/src/agents/skills/config.ts b/src/agents/skills/config.ts index 0c5679c5930..554e8d18644 100644 --- a/src/agents/skills/config.ts +++ b/src/agents/skills/config.ts @@ -1,7 +1,11 @@ -import fs from "node:fs"; -import path from "node:path"; import type { OpenClawConfig, SkillConfig } from "../../config/config.js"; import type { SkillEligibilityContext, SkillEntry } from "./types.js"; +import { + hasBinary, + isConfigPathTruthyWithDefaults, + resolveConfigPath, + resolveRuntimePlatform, +} from "../../shared/config-eval.js"; import { resolveSkillKey } from "./frontmatter.js"; const DEFAULT_CONFIG_VALUES: Record = { @@ -9,40 +13,10 @@ const DEFAULT_CONFIG_VALUES: Record = { "browser.evaluateEnabled": true, }; -function isTruthy(value: unknown): boolean { - if (value === undefined || value === null) { - return false; - } - if (typeof value === "boolean") { - return value; - } - if (typeof value === "number") { - return value !== 0; - } - if (typeof value === "string") { - return value.trim().length > 0; - } - return true; -} - -export function resolveConfigPath(config: OpenClawConfig | undefined, pathStr: string) { - const parts = pathStr.split(".").filter(Boolean); - let current: unknown = config; - for (const part of parts) { - if (typeof current !== "object" || current === null) { - return undefined; - } - current = (current as Record)[part]; - } - return current; -} +export { hasBinary, resolveConfigPath, resolveRuntimePlatform }; export function isConfigPathTruthy(config: OpenClawConfig | undefined, pathStr: string): boolean { - const value = resolveConfigPath(config, pathStr); - if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) { - return DEFAULT_CONFIG_VALUES[pathStr]; - } - return isTruthy(value); + return isConfigPathTruthyWithDefaults(config, pathStr, DEFAULT_CONFIG_VALUES); } export function resolveSkillConfig( @@ -60,10 +34,6 @@ export function resolveSkillConfig( return entry; } -export function resolveRuntimePlatform(): string { - return process.platform; -} - function normalizeAllowlist(input: unknown): string[] | undefined { if (!input) { return undefined; @@ -96,29 +66,6 @@ export function isBundledSkillAllowed(entry: SkillEntry, allowlist?: string[]): return allowlist.includes(key) || allowlist.includes(entry.skill.name); } -export function hasBinary(bin: string): boolean { - const pathEnv = process.env.PATH ?? ""; - const parts = pathEnv.split(path.delimiter).filter(Boolean); - const winPathExt = process.env.PATHEXT; - const winExtensions = - winPathExt !== undefined - ? winPathExt.split(";").filter(Boolean) - : [".EXE", ".CMD", ".BAT", ".COM"]; - const extensions = process.platform === "win32" ? ["", ...winExtensions] : [""]; - for (const part of parts) { - for (const ext of extensions) { - const candidate = path.join(part, bin + ext); - try { - fs.accessSync(candidate, fs.constants.X_OK); - return true; - } catch { - // keep scanning - } - } - } - return false; -} - export function shouldIncludeSkill(params: { entry: SkillEntry; config?: OpenClawConfig; diff --git a/src/hooks/config.ts b/src/hooks/config.ts index 2572a8003a5..f5dec7eacb8 100644 --- a/src/hooks/config.ts +++ b/src/hooks/config.ts @@ -1,7 +1,11 @@ -import fs from "node:fs"; -import path from "node:path"; import type { OpenClawConfig, HookConfig } from "../config/config.js"; import type { HookEligibilityContext, HookEntry } from "./types.js"; +import { + hasBinary, + isConfigPathTruthyWithDefaults, + resolveConfigPath, + resolveRuntimePlatform, +} from "../shared/config-eval.js"; import { resolveHookKey } from "./frontmatter.js"; const DEFAULT_CONFIG_VALUES: Record = { @@ -10,40 +14,10 @@ const DEFAULT_CONFIG_VALUES: Record = { "workspace.dir": true, }; -function isTruthy(value: unknown): boolean { - if (value === undefined || value === null) { - return false; - } - if (typeof value === "boolean") { - return value; - } - if (typeof value === "number") { - return value !== 0; - } - if (typeof value === "string") { - return value.trim().length > 0; - } - return true; -} - -export function resolveConfigPath(config: OpenClawConfig | undefined, pathStr: string) { - const parts = pathStr.split(".").filter(Boolean); - let current: unknown = config; - for (const part of parts) { - if (typeof current !== "object" || current === null) { - return undefined; - } - current = (current as Record)[part]; - } - return current; -} +export { hasBinary, resolveConfigPath, resolveRuntimePlatform }; export function isConfigPathTruthy(config: OpenClawConfig | undefined, pathStr: string): boolean { - const value = resolveConfigPath(config, pathStr); - if (value === undefined && pathStr in DEFAULT_CONFIG_VALUES) { - return DEFAULT_CONFIG_VALUES[pathStr]; - } - return isTruthy(value); + return isConfigPathTruthyWithDefaults(config, pathStr, DEFAULT_CONFIG_VALUES); } export function resolveHookConfig( @@ -61,31 +35,6 @@ export function resolveHookConfig( return entry; } -export function resolveRuntimePlatform(): string { - return process.platform; -} - -export function hasBinary(bin: string): boolean { - const pathEnv = process.env.PATH ?? ""; - const parts = pathEnv.split(path.delimiter).filter(Boolean); - const extensions = - process.platform === "win32" - ? ["", ...(process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";").filter(Boolean)] - : [""]; - for (const part of parts) { - for (const ext of extensions) { - const candidate = path.join(part, bin + ext); - try { - fs.accessSync(candidate, fs.constants.X_OK); - return true; - } catch { - // keep scanning - } - } - } - return false; -} - export function shouldIncludeHook(params: { entry: HookEntry; config?: OpenClawConfig; diff --git a/src/shared/config-eval.ts b/src/shared/config-eval.ts new file mode 100644 index 00000000000..60b48c3019d --- /dev/null +++ b/src/shared/config-eval.ts @@ -0,0 +1,71 @@ +import fs from "node:fs"; +import path from "node:path"; + +export function isTruthy(value: unknown): boolean { + if (value === undefined || value === null) { + return false; + } + if (typeof value === "boolean") { + return value; + } + if (typeof value === "number") { + return value !== 0; + } + if (typeof value === "string") { + return value.trim().length > 0; + } + return true; +} + +export function resolveConfigPath(config: unknown, pathStr: string): unknown { + const parts = pathStr.split(".").filter(Boolean); + let current: unknown = config; + for (const part of parts) { + if (typeof current !== "object" || current === null) { + return undefined; + } + current = (current as Record)[part]; + } + return current; +} + +export function isConfigPathTruthyWithDefaults( + config: unknown, + pathStr: string, + defaults: Record, +): boolean { + const value = resolveConfigPath(config, pathStr); + if (value === undefined && pathStr in defaults) { + return defaults[pathStr] ?? false; + } + return isTruthy(value); +} + +export function resolveRuntimePlatform(): string { + return process.platform; +} + +function windowsPathExtensions(): string[] { + const raw = process.env.PATHEXT; + const list = + raw !== undefined ? raw.split(";").map((v) => v.trim()) : [".EXE", ".CMD", ".BAT", ".COM"]; + return ["", ...list.filter(Boolean)]; +} + +export function hasBinary(bin: string): boolean { + const pathEnv = process.env.PATH ?? ""; + const parts = pathEnv.split(path.delimiter).filter(Boolean); + const extensions = process.platform === "win32" ? windowsPathExtensions() : [""]; + for (const part of parts) { + for (const ext of extensions) { + const candidate = path.join(part, bin + ext); + try { + fs.accessSync(candidate, fs.constants.X_OK); + return true; + } catch { + // keep scanning + } + } + } + return false; +}