diff --git a/src/agents/agent-scope.ts b/src/agents/agent-scope.ts index e895128f460..386c2c1f84f 100644 --- a/src/agents/agent-scope.ts +++ b/src/agents/agent-scope.ts @@ -13,7 +13,13 @@ import { import { resolveUserPath } from "../utils.js"; import { normalizeSkillFilter } from "./skills/filter.js"; import { resolveDefaultAgentWorkspaceDir } from "./workspace.js"; -const log = createSubsystemLogger("agent-scope"); + +let log: ReturnType | null = null; + +function getLog(): ReturnType { + log ??= createSubsystemLogger("agent-scope"); + return log; +} /** Strip null bytes from paths to prevent ENOTDIR errors. */ function stripNullBytes(s: string): string { @@ -80,7 +86,7 @@ export function resolveDefaultAgentId(cfg: OpenClawConfig): string { const defaults = agents.filter((agent) => agent?.default); if (defaults.length > 1 && !defaultAgentWarned) { defaultAgentWarned = true; - log.warn("Multiple agents marked default=true; using the first entry as default."); + getLog().warn("Multiple agents marked default=true; using the first entry as default."); } const chosen = (defaults[0] ?? agents[0])?.id?.trim(); return normalizeAgentId(chosen || DEFAULT_AGENT_ID); diff --git a/src/agents/model-selection.ts b/src/agents/model-selection.ts index 95969c71c09..574479bab03 100644 --- a/src/agents/model-selection.ts +++ b/src/agents/model-selection.ts @@ -23,7 +23,12 @@ import { normalizeProviderIdForAuth, } from "./provider-id.js"; -const log = createSubsystemLogger("model-selection"); +let log: ReturnType | null = null; + +function getLog(): ReturnType { + log ??= createSubsystemLogger("model-selection"); + return log; +} export type ModelRef = { provider: string; @@ -292,7 +297,7 @@ export function resolveConfiguredModelRef(params: { // Default to anthropic if no provider is specified, but warn as this is deprecated. const safeTrimmed = sanitizeForLog(trimmed); - log.warn( + getLog().warn( `Model "${safeTrimmed}" specified without provider. Falling back to "anthropic/${safeTrimmed}". Please use "anthropic/${safeTrimmed}" in your config.`, ); return { provider: "anthropic", model: trimmed }; @@ -310,7 +315,9 @@ export function resolveConfiguredModelRef(params: { // User specified a model but it could not be resolved — warn before falling back. const safe = sanitizeForLog(trimmed); const safeFallback = sanitizeForLog(`${params.defaultProvider}/${params.defaultModel}`); - log.warn(`Model "${safe}" could not be resolved. Falling back to default "${safeFallback}".`); + getLog().warn( + `Model "${safe}" could not be resolved. Falling back to default "${safeFallback}".`, + ); } // Before falling back to the hardcoded default, check if the default provider // is actually available. If it isn't but other providers are configured, prefer diff --git a/src/config/schema.hints.ts b/src/config/schema.hints.ts index 9d56ff2566c..665a547844b 100644 --- a/src/config/schema.hints.ts +++ b/src/config/schema.hints.ts @@ -6,7 +6,14 @@ import { FIELD_LABELS } from "./schema.labels.js"; import { applyDerivedTags } from "./schema.tags.js"; import { sensitive } from "./zod-schema.sensitive.js"; -const log = createSubsystemLogger("config/schema"); +let log: ReturnType | null = null; + +function getLog(): ReturnType { + if (!log) { + log = createSubsystemLogger("config/schema"); + } + return log; +} export type { ConfigUiHint, ConfigUiHints } from "../shared/config-ui-hints-types.js"; @@ -198,7 +205,7 @@ export function mapSensitivePaths( if (isSensitive) { next[path] = { ...next[path], sensitive: true }; } else if (isSensitiveConfigPath(path) && !next[path]?.sensitive) { - log.debug(`possibly sensitive key found: (${path})`); + getLog().debug(`possibly sensitive key found: (${path})`); } if (currentSchema instanceof z.ZodObject) { diff --git a/src/global-state.ts b/src/global-state.ts new file mode 100644 index 00000000000..545783ef0a4 --- /dev/null +++ b/src/global-state.ts @@ -0,0 +1,18 @@ +let globalVerbose = false; +let globalYes = false; + +export function setVerbose(v: boolean) { + globalVerbose = v; +} + +export function isVerbose() { + return globalVerbose; +} + +export function setYes(v: boolean) { + globalYes = v; +} + +export function isYes() { + return globalYes; +} diff --git a/src/globals.ts b/src/globals.ts index 5523c41ec72..9cbb534219f 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -1,19 +1,10 @@ +export { isVerbose, isYes, setVerbose, setYes } from "./global-state.js"; +import { isVerbose } from "./global-state.js"; import { getLogger, isFileLogLevelEnabled } from "./logging/logger.js"; import { theme } from "./terminal/theme.js"; -let globalVerbose = false; -let globalYes = false; - -export function setVerbose(v: boolean) { - globalVerbose = v; -} - -export function isVerbose() { - return globalVerbose; -} - export function shouldLogVerbose() { - return globalVerbose || isFileLogLevelEnabled("debug"); + return isVerbose() || isFileLogLevelEnabled("debug"); } export function logVerbose(message: string) { @@ -25,27 +16,19 @@ export function logVerbose(message: string) { } catch { // ignore logger failures to avoid breaking verbose printing } - if (!globalVerbose) { + if (!isVerbose()) { return; } console.log(theme.muted(message)); } export function logVerboseConsole(message: string) { - if (!globalVerbose) { + if (!isVerbose()) { return; } console.log(theme.muted(message)); } -export function setYes(v: boolean) { - globalYes = v; -} - -export function isYes() { - return globalYes; -} - export const success = theme.success; export const warn = theme.warn; export const info = theme.info; diff --git a/src/infra/env.ts b/src/infra/env.ts index 47c16fed1df..4b299bac699 100644 --- a/src/infra/env.ts +++ b/src/infra/env.ts @@ -1,9 +1,16 @@ import { createSubsystemLogger } from "../logging/subsystem.js"; import { parseBooleanValue } from "../utils/boolean.js"; -const log = createSubsystemLogger("env"); +let log: ReturnType | null = null; const loggedEnv = new Set(); +function getLog(): ReturnType { + if (!log) { + log = createSubsystemLogger("env"); + } + return log; +} + type AcceptedEnvOption = { key: string; description: string; @@ -34,7 +41,9 @@ export function logAcceptedEnvOption(option: AcceptedEnvOption): void { return; } loggedEnv.add(option.key); - log.info(`env: ${option.key}=${formatEnvValue(rawValue, option.redact)} (${option.description})`); + getLog().info( + `env: ${option.key}=${formatEnvValue(rawValue, option.redact)} (${option.description})`, + ); } export function normalizeZaiEnv(): void { diff --git a/src/logging/console.ts b/src/logging/console.ts index 8f5ba3b75c6..dbf8899ae8a 100644 --- a/src/logging/console.ts +++ b/src/logging/console.ts @@ -1,6 +1,6 @@ import util from "node:util"; import type { OpenClawConfig } from "../config/types.js"; -import { isVerbose } from "../globals.js"; +import { isVerbose } from "../global-state.js"; import { stripAnsi } from "../terminal/ansi.js"; import { readLoggingConfig } from "./config.js"; import { resolveEnvLogLevelOverride } from "./env-log-level.js"; diff --git a/src/logging/subsystem.ts b/src/logging/subsystem.ts index fec90dc1f9f..6438cbba516 100644 --- a/src/logging/subsystem.ts +++ b/src/logging/subsystem.ts @@ -1,6 +1,6 @@ import { Chalk } from "chalk"; import type { Logger as TsLogger } from "tslog"; -import { isVerbose } from "../globals.js"; +import { isVerbose } from "../global-state.js"; import { defaultRuntime, type OutputRuntimeEnv, type RuntimeEnv } from "../runtime.js"; import { clearActiveProgressLine } from "../terminal/progress-line.js"; import { @@ -307,13 +307,13 @@ function logToFile( export function createSubsystemLogger(subsystem: string): SubsystemLogger { let fileLogger: TsLogger | null = null; - const getFileLogger = () => { + const getFileLogger = (): TsLogger => { if (!fileLogger) { fileLogger = getChildLogger({ subsystem }); } return fileLogger; }; - const emit = (level: LogLevel, message: string, meta?: Record) => { + const emit = (level: LogLevel, message: string, meta?: Record): void => { const consoleSettings = getConsoleSettings(); const consoleEnabled = shouldLogToConsole(level, { level: consoleSettings.level }) && @@ -366,11 +366,13 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { shouldLogSubsystemToConsole(subsystem) ); }; - const isFileEnabled = (level: LogLevel): boolean => isFileLogLevelEnabled(level); + const isFileEnabled = (level: LogLevel): boolean => { + return isFileLogLevelEnabled(level); + }; const logger: SubsystemLogger = { subsystem, - isEnabled: (level, target = "any") => { + isEnabled(level, target = "any") { if (target === "console") { return isConsoleEnabled(level); } @@ -379,13 +381,25 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { } return isConsoleEnabled(level) || isFileEnabled(level); }, - trace: (message, meta) => emit("trace", message, meta), - debug: (message, meta) => emit("debug", message, meta), - info: (message, meta) => emit("info", message, meta), - warn: (message, meta) => emit("warn", message, meta), - error: (message, meta) => emit("error", message, meta), - fatal: (message, meta) => emit("fatal", message, meta), - raw: (message) => { + trace(message, meta) { + emit("trace", message, meta); + }, + debug(message, meta) { + emit("debug", message, meta); + }, + info(message, meta) { + emit("info", message, meta); + }, + warn(message, meta) { + emit("warn", message, meta); + }, + error(message, meta) { + emit("error", message, meta); + }, + fatal(message, meta) { + emit("fatal", message, meta); + }, + raw(message) { if (isFileEnabled("info")) { logToFile(getFileLogger(), "info", message, { raw: true }); } @@ -396,7 +410,9 @@ export function createSubsystemLogger(subsystem: string): SubsystemLogger { writeConsoleLine("info", message); } }, - child: (name) => createSubsystemLogger(`${subsystem}/${name}`), + child(name) { + return createSubsystemLogger(`${subsystem}/${name}`); + }, }; return logger; } diff --git a/ui/src/types/create-markdown-preview.d.ts b/ui/src/types/create-markdown-preview.d.ts new file mode 100644 index 00000000000..88a9ce8fcbd --- /dev/null +++ b/ui/src/types/create-markdown-preview.d.ts @@ -0,0 +1,7 @@ +declare module "@create-markdown/preview" { + export type PreviewThemeOptions = { + sanitize?: ((html: string) => string) | undefined; + }; + + export function applyPreviewTheme(html: string, options?: PreviewThemeOptions): string; +}