diff --git a/src/agents/tool-policy-shared.ts b/src/agents/tool-policy-shared.ts new file mode 100644 index 00000000000..0bfee5cecaa --- /dev/null +++ b/src/agents/tool-policy-shared.ts @@ -0,0 +1,121 @@ +export type ToolProfileId = "minimal" | "coding" | "messaging" | "full"; + +type ToolProfilePolicy = { + allow?: string[]; + deny?: string[]; +}; + +const TOOL_NAME_ALIASES: Record = { + bash: "exec", + "apply-patch": "apply_patch", +}; + +export const TOOL_GROUPS: Record = { + // NOTE: Keep canonical (lowercase) tool names here. + "group:memory": ["memory_search", "memory_get"], + "group:web": ["web_search", "web_fetch"], + // Basic workspace/file tools + "group:fs": ["read", "write", "edit", "apply_patch"], + // Host/runtime execution tools + "group:runtime": ["exec", "process"], + // Session management tools + "group:sessions": [ + "sessions_list", + "sessions_history", + "sessions_send", + "sessions_spawn", + "subagents", + "session_status", + ], + // UI helpers + "group:ui": ["browser", "canvas"], + // Automation + infra + "group:automation": ["cron", "gateway"], + // Messaging surface + "group:messaging": ["message"], + // Nodes + device tools + "group:nodes": ["nodes"], + // All OpenClaw native tools (excludes provider plugins). + "group:openclaw": [ + "browser", + "canvas", + "nodes", + "cron", + "message", + "gateway", + "agents_list", + "sessions_list", + "sessions_history", + "sessions_send", + "sessions_spawn", + "subagents", + "session_status", + "memory_search", + "memory_get", + "web_search", + "web_fetch", + "image", + ], +}; + +const TOOL_PROFILES: Record = { + minimal: { + allow: ["session_status"], + }, + coding: { + allow: ["group:fs", "group:runtime", "group:sessions", "group:memory", "image"], + }, + messaging: { + allow: [ + "group:messaging", + "sessions_list", + "sessions_history", + "sessions_send", + "session_status", + ], + }, + full: {}, +}; + +export function normalizeToolName(name: string) { + const normalized = name.trim().toLowerCase(); + return TOOL_NAME_ALIASES[normalized] ?? normalized; +} + +export function normalizeToolList(list?: string[]) { + if (!list) { + return []; + } + return list.map(normalizeToolName).filter(Boolean); +} + +export function expandToolGroups(list?: string[]) { + const normalized = normalizeToolList(list); + const expanded: string[] = []; + for (const value of normalized) { + const group = TOOL_GROUPS[value]; + if (group) { + expanded.push(...group); + continue; + } + expanded.push(value); + } + return Array.from(new Set(expanded)); +} + +export function resolveToolProfilePolicy(profile?: string): ToolProfilePolicy | undefined { + if (!profile) { + return undefined; + } + const resolved = TOOL_PROFILES[profile as ToolProfileId]; + if (!resolved) { + return undefined; + } + if (!resolved.allow && !resolved.deny) { + return undefined; + } + return { + allow: resolved.allow ? [...resolved.allow] : undefined, + deny: resolved.deny ? [...resolved.deny] : undefined, + }; +} diff --git a/src/logging/console.ts b/src/logging/console.ts index 3454bb604ec..89aefbe9cfa 100644 --- a/src/logging/console.ts +++ b/src/logging/console.ts @@ -1,4 +1,3 @@ -import { createRequire } from "node:module"; import util from "node:util"; import type { OpenClawConfig } from "../config/types.js"; import { isVerbose } from "../globals.js"; @@ -16,14 +15,37 @@ type ConsoleSettings = { }; export type ConsoleLoggerSettings = ConsoleSettings; -const requireConfig = createRequire(import.meta.url); +function resolveNodeRequire(): ((id: string) => NodeJS.Require) | null { + const getBuiltinModule = ( + process as NodeJS.Process & { + getBuiltinModule?: (id: string) => unknown; + } + ).getBuiltinModule; + if (typeof getBuiltinModule !== "function") { + return null; + } + try { + const moduleNamespace = getBuiltinModule("module") as { + createRequire?: (id: string) => NodeJS.Require; + }; + return typeof moduleNamespace.createRequire === "function" + ? moduleNamespace.createRequire + : null; + } catch { + return null; + } +} + +const requireConfig = resolveNodeRequire()?.(import.meta.url) ?? null; type ConsoleConfigLoader = () => OpenClawConfig["logging"] | undefined; const loadConfigFallbackDefault: ConsoleConfigLoader = () => { try { - const loaded = requireConfig("../config/config.js") as { - loadConfig?: () => OpenClawConfig; - }; - return loaded.loadConfig?.().logging; + const loaded = requireConfig?.("../config/config.js") as + | { + loadConfig?: () => OpenClawConfig; + } + | undefined; + return loaded?.loadConfig?.().logging; } catch { return undefined; } diff --git a/src/logging/logger.ts b/src/logging/logger.ts index aaa1b46aff0..f4db1a3a2b0 100644 --- a/src/logging/logger.ts +++ b/src/logging/logger.ts @@ -1,5 +1,4 @@ import fs from "node:fs"; -import { createRequire } from "node:module"; import path from "node:path"; import { Logger as TsLogger } from "tslog"; import type { OpenClawConfig } from "../config/types.js"; @@ -16,7 +15,28 @@ const LOG_PREFIX = "openclaw"; const LOG_SUFFIX = ".log"; const MAX_LOG_AGE_MS = 24 * 60 * 60 * 1000; // 24h -const requireConfig = createRequire(import.meta.url); +function resolveNodeRequire(): ((id: string) => NodeJS.Require) | null { + const getBuiltinModule = ( + process as NodeJS.Process & { + getBuiltinModule?: (id: string) => unknown; + } + ).getBuiltinModule; + if (typeof getBuiltinModule !== "function") { + return null; + } + try { + const moduleNamespace = getBuiltinModule("module") as { + createRequire?: (id: string) => NodeJS.Require; + }; + return typeof moduleNamespace.createRequire === "function" + ? moduleNamespace.createRequire + : null; + } catch { + return null; + } +} + +const requireConfig = resolveNodeRequire()?.(import.meta.url) ?? null; export type LoggerSettings = { level?: LogLevel; @@ -55,10 +75,12 @@ function resolveSettings(): ResolvedSettings { (loggingState.overrideSettings as LoggerSettings | null) ?? readLoggingConfig(); if (!cfg) { try { - const loaded = requireConfig("../config/config.js") as { - loadConfig?: () => OpenClawConfig; - }; - cfg = loaded.loadConfig?.().logging; + const loaded = requireConfig?.("../config/config.js") as + | { + loadConfig?: () => OpenClawConfig; + } + | undefined; + cfg = loaded?.loadConfig?.().logging; } catch { cfg = undefined; } diff --git a/src/logging/redact.ts b/src/logging/redact.ts index e60766cba4c..ca6fdd3c09b 100644 --- a/src/logging/redact.ts +++ b/src/logging/redact.ts @@ -1,7 +1,27 @@ -import { createRequire } from "node:module"; import type { OpenClawConfig } from "../config/config.js"; -const requireConfig = createRequire(import.meta.url); +function resolveNodeRequire(): ((id: string) => NodeJS.Require) | null { + const getBuiltinModule = ( + process as NodeJS.Process & { + getBuiltinModule?: (id: string) => unknown; + } + ).getBuiltinModule; + if (typeof getBuiltinModule !== "function") { + return null; + } + try { + const moduleNamespace = getBuiltinModule("module") as { + createRequire?: (id: string) => NodeJS.Require; + }; + return typeof moduleNamespace.createRequire === "function" + ? moduleNamespace.createRequire + : null; + } catch { + return null; + } +} + +const requireConfig = resolveNodeRequire()?.(import.meta.url) ?? null; export type RedactSensitiveMode = "off" | "tools"; @@ -110,10 +130,12 @@ function redactText(text: string, patterns: RegExp[]): string { function resolveConfigRedaction(): RedactOptions { let cfg: OpenClawConfig["logging"] | undefined; try { - const loaded = requireConfig("../config/config.js") as { - loadConfig?: () => OpenClawConfig; - }; - cfg = loaded.loadConfig?.().logging; + const loaded = requireConfig?.("../config/config.js") as + | { + loadConfig?: () => OpenClawConfig; + } + | undefined; + cfg = loaded?.loadConfig?.().logging; } catch { cfg = undefined; } diff --git a/src/logging/subsystem.ts b/src/logging/subsystem.ts index 088f173aa54..32fe853f081 100644 --- a/src/logging/subsystem.ts +++ b/src/logging/subsystem.ts @@ -1,4 +1,3 @@ -import { inspect } from "node:util"; import { Chalk } from "chalk"; import type { Logger as TsLogger } from "tslog"; import { CHAT_CHANNEL_ORDER } from "../channels/registry.js"; @@ -36,6 +35,39 @@ function shouldLogToConsole(level: LogLevel, settings: { level: LogLevel }): boo type ChalkInstance = InstanceType; +const inspectValue: ((value: unknown) => string) | null = (() => { + const getBuiltinModule = ( + process as NodeJS.Process & { + getBuiltinModule?: (id: string) => unknown; + } + ).getBuiltinModule; + if (typeof getBuiltinModule !== "function") { + return null; + } + try { + const utilNamespace = getBuiltinModule("util") as { + inspect?: (value: unknown) => string; + }; + return typeof utilNamespace.inspect === "function" ? utilNamespace.inspect : null; + } catch { + return null; + } +})(); + +function formatRuntimeArg(arg: unknown): string { + if (typeof arg === "string") { + return arg; + } + if (inspectValue) { + return inspectValue(arg); + } + try { + return JSON.stringify(arg); + } catch { + return String(arg); + } +} + function isRichConsoleEnv(): boolean { const term = (process.env.TERM ?? "").toLowerCase(); if (process.env.COLORTERM || process.env.TERM_PROGRAM) { @@ -323,7 +355,7 @@ export function runtimeForLogger( ): RuntimeEnv { const formatArgs = (...args: unknown[]) => args - .map((arg) => (typeof arg === "string" ? arg : inspect(arg))) + .map((arg) => formatRuntimeArg(arg)) .join(" ") .trim(); return { diff --git a/ui/src/ui/views/agents-panels-tools-skills.ts b/ui/src/ui/views/agents-panels-tools-skills.ts index 517be376ced..687ec749a62 100644 --- a/ui/src/ui/views/agents-panels-tools-skills.ts +++ b/ui/src/ui/views/agents-panels-tools-skills.ts @@ -1,5 +1,5 @@ import { html, nothing } from "lit"; -import { normalizeToolName } from "../../../../src/agents/tool-policy.js"; +import { normalizeToolName } from "../../../../src/agents/tool-policy-shared.js"; import type { SkillStatusEntry, SkillStatusReport } from "../types.ts"; import { isAllowedByPolicy, diff --git a/ui/src/ui/views/agents-utils.ts b/ui/src/ui/views/agents-utils.ts index fedfb385b33..ecd2c90f13b 100644 --- a/ui/src/ui/views/agents-utils.ts +++ b/ui/src/ui/views/agents-utils.ts @@ -3,7 +3,7 @@ import { expandToolGroups, normalizeToolName, resolveToolProfilePolicy, -} from "../../../../src/agents/tool-policy.js"; +} from "../../../../src/agents/tool-policy-shared.js"; import type { AgentIdentityResult, AgentsFilesListResult, AgentsListResult } from "../types.ts"; export const TOOL_SECTIONS = [