UNPKG

@gguf/claw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,502 lines (1,466 loc) 510 kB
import { createRequire } from "node:module"; import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { Logger } from "tslog"; import JSON5 from "json5"; import chalk, { Chalk } from "chalk"; import fs$1 from "node:fs/promises"; import { execFile, execFileSync, spawn } from "node:child_process"; import { isDeepStrictEqual, promisify } from "node:util"; import crypto, { X509Certificate, randomUUID } from "node:crypto"; import { spinner } from "@clack/prompts"; import { fileURLToPath } from "node:url"; import dotenv from "dotenv"; import { getOAuthProviders } from "@mariozechner/pi-ai"; import "@aws-sdk/client-bedrock"; import AjvPkg from "ajv"; import { z } from "zod"; import "@mariozechner/pi-coding-agent"; import { WebSocket } from "ws"; import { Buffer as Buffer$1 } from "node:buffer"; import net from "node:net"; import { Type } from "@sinclair/typebox"; import { Writable } from "node:stream"; import { createOscProgressController, supportsOscProgress } from "osc-progress"; //#region src/infra/home-dir.ts function normalize(value) { const trimmed = value?.trim(); return trimmed ? trimmed : void 0; } function resolveEffectiveHomeDir(env = process.env, homedir = os.homedir) { const raw = resolveRawHomeDir(env, homedir); return raw ? path.resolve(raw) : void 0; } function resolveRawHomeDir(env, homedir) { const explicitHome = normalize(env.OPENCLAW_HOME); if (explicitHome) { if (explicitHome === "~" || explicitHome.startsWith("~/") || explicitHome.startsWith("~\\")) { const fallbackHome = normalize(env.HOME) ?? normalize(env.USERPROFILE) ?? normalizeSafe(homedir); if (fallbackHome) return explicitHome.replace(/^~(?=$|[\\/])/, fallbackHome); return; } return explicitHome; } const envHome = normalize(env.HOME); if (envHome) return envHome; const userProfile = normalize(env.USERPROFILE); if (userProfile) return userProfile; return normalizeSafe(homedir); } function normalizeSafe(homedir) { try { return normalize(homedir()); } catch { return; } } function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) { return resolveEffectiveHomeDir(env, homedir) ?? path.resolve(process.cwd()); } function expandHomePrefix(input, opts) { if (!input.startsWith("~")) return input; const home = normalize(opts?.home) ?? resolveEffectiveHomeDir(opts?.env ?? process.env, opts?.homedir ?? os.homedir); if (!home) return input; return input.replace(/^~(?=$|[\\/])/, home); } //#endregion //#region src/config/paths.ts /** * Nix mode detection: When OPENCLAW_NIX_MODE=1, the gateway is running under Nix. * In this mode: * - No auto-install flows should be attempted * - Missing dependencies should produce actionable Nix-specific error messages * - Config is managed externally (read-only from Nix perspective) */ function resolveIsNixMode(env = process.env) { return env.OPENCLAW_NIX_MODE === "1"; } const isNixMode = resolveIsNixMode(); const LEGACY_STATE_DIRNAMES = [ ".clawdbot", ".moldbot", ".moltbot" ]; const NEW_STATE_DIRNAME = ".openclaw"; const CONFIG_FILENAME = "openclaw.json"; const LEGACY_CONFIG_FILENAMES = [ "clawdbot.json", "moldbot.json", "moltbot.json" ]; function resolveDefaultHomeDir() { return resolveRequiredHomeDir(process.env, os.homedir); } /** Build a homedir thunk that respects OPENCLAW_HOME for the given env. */ function envHomedir(env) { return () => resolveRequiredHomeDir(env, os.homedir); } function legacyStateDirs(homedir = resolveDefaultHomeDir) { return LEGACY_STATE_DIRNAMES.map((dir) => path.join(homedir(), dir)); } function newStateDir(homedir = resolveDefaultHomeDir) { return path.join(homedir(), NEW_STATE_DIRNAME); } /** * State directory for mutable data (sessions, logs, caches). * Can be overridden via OPENCLAW_STATE_DIR. * Default: ~/.openclaw */ function resolveStateDir(env = process.env, homedir = envHomedir(env)) { const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir); const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); if (override) return resolveUserPath$1(override, env, effectiveHomedir); const newDir = newStateDir(effectiveHomedir); const legacyDirs = legacyStateDirs(effectiveHomedir); if (fs.existsSync(newDir)) return newDir; const existingLegacy = legacyDirs.find((dir) => { try { return fs.existsSync(dir); } catch { return false; } }); if (existingLegacy) return existingLegacy; return newDir; } function resolveUserPath$1(input, env = process.env, homedir = envHomedir(env)) { const trimmed = input.trim(); if (!trimmed) return trimmed; if (trimmed.startsWith("~")) { const expanded = expandHomePrefix(trimmed, { home: resolveRequiredHomeDir(env, homedir), env, homedir }); return path.resolve(expanded); } return path.resolve(trimmed); } const STATE_DIR = resolveStateDir(); /** * Config file path (JSON5). * Can be overridden via OPENCLAW_CONFIG_PATH. * Default: ~/.openclaw/openclaw.json (or $OPENCLAW_STATE_DIR/openclaw.json) */ function resolveCanonicalConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) { const override = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim(); if (override) return resolveUserPath$1(override, env, envHomedir(env)); return path.join(stateDir, CONFIG_FILENAME); } /** * Resolve the active config path by preferring existing config candidates * before falling back to the canonical path. */ function resolveConfigPathCandidate(env = process.env, homedir = envHomedir(env)) { const existing = resolveDefaultConfigCandidates(env, homedir).find((candidate) => { try { return fs.existsSync(candidate); } catch { return false; } }); if (existing) return existing; return resolveCanonicalConfigPath(env, resolveStateDir(env, homedir)); } /** * Active config path (prefers existing config files). */ function resolveConfigPath(env = process.env, stateDir = resolveStateDir(env, envHomedir(env)), homedir = envHomedir(env)) { const override = env.OPENCLAW_CONFIG_PATH?.trim(); if (override) return resolveUserPath$1(override, env, homedir); const stateOverride = env.OPENCLAW_STATE_DIR?.trim(); const existing = [path.join(stateDir, CONFIG_FILENAME), ...LEGACY_CONFIG_FILENAMES.map((name) => path.join(stateDir, name))].find((candidate) => { try { return fs.existsSync(candidate); } catch { return false; } }); if (existing) return existing; if (stateOverride) return path.join(stateDir, CONFIG_FILENAME); const defaultStateDir = resolveStateDir(env, homedir); if (path.resolve(stateDir) === path.resolve(defaultStateDir)) return resolveConfigPathCandidate(env, homedir); return path.join(stateDir, CONFIG_FILENAME); } const CONFIG_PATH = resolveConfigPathCandidate(); /** * Resolve default config path candidates across default locations. * Order: explicit config path → state-dir-derived paths → new default. */ function resolveDefaultConfigCandidates(env = process.env, homedir = envHomedir(env)) { const effectiveHomedir = () => resolveRequiredHomeDir(env, homedir); const explicit = env.OPENCLAW_CONFIG_PATH?.trim() || env.CLAWDBOT_CONFIG_PATH?.trim(); if (explicit) return [resolveUserPath$1(explicit, env, effectiveHomedir)]; const candidates = []; const openclawStateDir = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); if (openclawStateDir) { const resolved = resolveUserPath$1(openclawStateDir, env, effectiveHomedir); candidates.push(path.join(resolved, CONFIG_FILENAME)); candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(resolved, name))); } const defaultDirs = [newStateDir(effectiveHomedir), ...legacyStateDirs(effectiveHomedir)]; for (const dir of defaultDirs) { candidates.push(path.join(dir, CONFIG_FILENAME)); candidates.push(...LEGACY_CONFIG_FILENAMES.map((name) => path.join(dir, name))); } return candidates; } const DEFAULT_GATEWAY_PORT = 18789; /** * OAuth credentials storage directory. * * Precedence: * - `OPENCLAW_OAUTH_DIR` (explicit override) * - `$*_STATE_DIR/credentials` (canonical server/default) */ function resolveOAuthDir(env = process.env, stateDir = resolveStateDir(env, envHomedir(env))) { const override = env.OPENCLAW_OAUTH_DIR?.trim(); if (override) return resolveUserPath$1(override, env, envHomedir(env)); return path.join(stateDir, "credentials"); } function resolveGatewayPort(cfg, env = process.env) { const envRaw = env.OPENCLAW_GATEWAY_PORT?.trim() || env.CLAWDBOT_GATEWAY_PORT?.trim(); if (envRaw) { const parsed = Number.parseInt(envRaw, 10); if (Number.isFinite(parsed) && parsed > 0) return parsed; } const configPort = cfg?.gateway?.port; if (typeof configPort === "number" && Number.isFinite(configPort)) { if (configPort > 0) return configPort; } return DEFAULT_GATEWAY_PORT; } //#endregion //#region src/infra/tmp-openclaw-dir.ts const POSIX_OPENCLAW_TMP_DIR = "/tmp/openclaw"; function isNodeErrorWithCode(err, code) { return typeof err === "object" && err !== null && "code" in err && err.code === code; } function resolvePreferredOpenClawTmpDir(options = {}) { const accessSync = options.accessSync ?? fs.accessSync; const lstatSync = options.lstatSync ?? fs.lstatSync; const mkdirSync = options.mkdirSync ?? fs.mkdirSync; const getuid = options.getuid ?? (() => { try { return typeof process.getuid === "function" ? process.getuid() : void 0; } catch { return; } }); const tmpdir = options.tmpdir ?? os.tmpdir; const uid = getuid(); const isSecureDirForUser = (st) => { if (uid === void 0) return true; if (typeof st.uid === "number" && st.uid !== uid) return false; if (typeof st.mode === "number" && (st.mode & 18) !== 0) return false; return true; }; const fallback = () => { const base = tmpdir(); const suffix = uid === void 0 ? "openclaw" : `openclaw-${uid}`; return path.join(base, suffix); }; try { const preferred = lstatSync(POSIX_OPENCLAW_TMP_DIR); if (!preferred.isDirectory() || preferred.isSymbolicLink()) return fallback(); accessSync(POSIX_OPENCLAW_TMP_DIR, fs.constants.W_OK | fs.constants.X_OK); if (!isSecureDirForUser(preferred)) return fallback(); return POSIX_OPENCLAW_TMP_DIR; } catch (err) { if (!isNodeErrorWithCode(err, "ENOENT")) return fallback(); } try { accessSync("/tmp", fs.constants.W_OK | fs.constants.X_OK); mkdirSync(POSIX_OPENCLAW_TMP_DIR, { recursive: true, mode: 448 }); try { const preferred = lstatSync(POSIX_OPENCLAW_TMP_DIR); if (!preferred.isDirectory() || preferred.isSymbolicLink()) return fallback(); if (!isSecureDirForUser(preferred)) return fallback(); } catch { return fallback(); } return POSIX_OPENCLAW_TMP_DIR; } catch { return fallback(); } } //#endregion //#region src/logging/config.ts function readLoggingConfig() { const configPath = resolveConfigPath(); try { if (!fs.existsSync(configPath)) return; const raw = fs.readFileSync(configPath, "utf-8"); const logging = JSON5.parse(raw)?.logging; if (!logging || typeof logging !== "object" || Array.isArray(logging)) return; return logging; } catch { return; } } //#endregion //#region src/logging/levels.ts const ALLOWED_LOG_LEVELS = [ "silent", "fatal", "error", "warn", "info", "debug", "trace" ]; function normalizeLogLevel(level, fallback = "info") { const candidate = (level ?? fallback).trim(); return ALLOWED_LOG_LEVELS.includes(candidate) ? candidate : fallback; } function levelToMinLevel(level) { return { fatal: 0, error: 1, warn: 2, info: 3, debug: 4, trace: 5, silent: Number.POSITIVE_INFINITY }[level]; } //#endregion //#region src/logging/state.ts const loggingState = { cachedLogger: null, cachedSettings: null, cachedConsoleSettings: null, overrideSettings: null, consolePatched: false, forceConsoleToStderr: false, consoleTimestampPrefix: false, consoleSubsystemFilter: null, resolvingConsoleSettings: false, streamErrorHandlersInstalled: false, rawConsole: null }; //#endregion //#region src/logging/logger.ts const DEFAULT_LOG_DIR = resolvePreferredOpenClawTmpDir(); const DEFAULT_LOG_FILE = path.join(DEFAULT_LOG_DIR, "openclaw.log"); const LOG_PREFIX = "openclaw"; const LOG_SUFFIX = ".log"; const MAX_LOG_AGE_MS = 1440 * 60 * 1e3; function resolveNodeRequire$2() { const getBuiltinModule = process.getBuiltinModule; if (typeof getBuiltinModule !== "function") return null; try { const moduleNamespace = getBuiltinModule("module"); return typeof moduleNamespace.createRequire === "function" ? moduleNamespace.createRequire : null; } catch { return null; } } const requireConfig$2 = resolveNodeRequire$2()?.(import.meta.url) ?? null; const externalTransports = /* @__PURE__ */ new Set(); function attachExternalTransport(logger, transport) { logger.attachTransport((logObj) => { if (!externalTransports.has(transport)) return; try { transport(logObj); } catch {} }); } function resolveSettings() { let cfg = loggingState.overrideSettings ?? readLoggingConfig(); if (!cfg) try { cfg = (requireConfig$2?.("../config/config.js"))?.loadConfig?.().logging; } catch { cfg = void 0; } const defaultLevel = process.env.VITEST === "true" && process.env.OPENCLAW_TEST_FILE_LOG !== "1" ? "silent" : "info"; return { level: normalizeLogLevel(cfg?.level, defaultLevel), file: cfg?.file ?? defaultRollingPathForToday() }; } function settingsChanged(a, b) { if (!a) return true; return a.level !== b.level || a.file !== b.file; } function isFileLogLevelEnabled(level) { const settings = loggingState.cachedSettings ?? resolveSettings(); if (!loggingState.cachedSettings) loggingState.cachedSettings = settings; if (settings.level === "silent") return false; return levelToMinLevel(level) <= levelToMinLevel(settings.level); } function buildLogger(settings) { fs.mkdirSync(path.dirname(settings.file), { recursive: true }); if (isRollingPath(settings.file)) pruneOldRollingLogs(path.dirname(settings.file)); const logger = new Logger({ name: "openclaw", minLevel: levelToMinLevel(settings.level), type: "hidden" }); logger.attachTransport((logObj) => { try { const time = logObj.date?.toISOString?.() ?? (/* @__PURE__ */ new Date()).toISOString(); const line = JSON.stringify({ ...logObj, time }); fs.appendFileSync(settings.file, `${line}\n`, { encoding: "utf8" }); } catch {} }); for (const transport of externalTransports) attachExternalTransport(logger, transport); return logger; } function getLogger() { const settings = resolveSettings(); const cachedLogger = loggingState.cachedLogger; const cachedSettings = loggingState.cachedSettings; if (!cachedLogger || settingsChanged(cachedSettings, settings)) { loggingState.cachedLogger = buildLogger(settings); loggingState.cachedSettings = settings; } return loggingState.cachedLogger; } function getChildLogger(bindings, opts) { const base = getLogger(); const minLevel = opts?.level ? levelToMinLevel(opts.level) : void 0; const name = bindings ? JSON.stringify(bindings) : void 0; return base.getSubLogger({ name, minLevel, prefix: bindings ? [name ?? ""] : [] }); } function getResolvedLoggerSettings() { return resolveSettings(); } function formatLocalDate(date) { return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`; } function defaultRollingPathForToday() { const today = formatLocalDate(/* @__PURE__ */ new Date()); return path.join(DEFAULT_LOG_DIR, `${LOG_PREFIX}-${today}${LOG_SUFFIX}`); } function isRollingPath(file) { const base = path.basename(file); return base.startsWith(`${LOG_PREFIX}-`) && base.endsWith(LOG_SUFFIX) && base.length === `${LOG_PREFIX}-YYYY-MM-DD${LOG_SUFFIX}`.length; } function pruneOldRollingLogs(dir) { try { const entries = fs.readdirSync(dir, { withFileTypes: true }); const cutoff = Date.now() - MAX_LOG_AGE_MS; for (const entry of entries) { if (!entry.isFile()) continue; if (!entry.name.startsWith(`${LOG_PREFIX}-`) || !entry.name.endsWith(LOG_SUFFIX)) continue; const fullPath = path.join(dir, entry.name); try { if (fs.statSync(fullPath).mtimeMs < cutoff) fs.rmSync(fullPath, { force: true }); } catch {} } } catch {} } //#endregion //#region src/terminal/palette.ts const LOBSTER_PALETTE = { accent: "#FF5A2D", accentBright: "#FF7A3D", accentDim: "#D14A22", info: "#FF8A5B", success: "#2FBF71", warn: "#FFB020", error: "#E23D2D", muted: "#8B7F77" }; //#endregion //#region src/terminal/theme.ts const hasForceColor = typeof process.env.FORCE_COLOR === "string" && process.env.FORCE_COLOR.trim().length > 0 && process.env.FORCE_COLOR.trim() !== "0"; const baseChalk = process.env.NO_COLOR && !hasForceColor ? new Chalk({ level: 0 }) : chalk; const hex = (value) => baseChalk.hex(value); const theme = { accent: hex(LOBSTER_PALETTE.accent), accentBright: hex(LOBSTER_PALETTE.accentBright), accentDim: hex(LOBSTER_PALETTE.accentDim), info: hex(LOBSTER_PALETTE.info), success: hex(LOBSTER_PALETTE.success), warn: hex(LOBSTER_PALETTE.warn), error: hex(LOBSTER_PALETTE.error), muted: hex(LOBSTER_PALETTE.muted), heading: baseChalk.bold.hex(LOBSTER_PALETTE.accent), command: hex(LOBSTER_PALETTE.accentBright), option: hex(LOBSTER_PALETTE.warn) }; const isRich = () => Boolean(baseChalk.level > 0); const colorize = (rich, color, value) => rich ? color(value) : value; //#endregion //#region src/globals.ts let globalVerbose = false; function isVerbose() { return globalVerbose; } function logVerboseConsole(message) { if (!globalVerbose) return; console.log(theme.muted(message)); } const success = theme.success; const warn = theme.warn; const info = theme.info; const danger = theme.error; //#endregion //#region src/infra/plain-object.ts /** * Strict plain-object guard (excludes arrays and host objects). */ function isPlainObject$1(value) { return typeof value === "object" && value !== null && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]"; } //#endregion //#region src/utils.ts async function ensureDir$1(dir) { await fs.promises.mkdir(dir, { recursive: true }); } /** * Type guard for Record<string, unknown> (less strict than isPlainObject). * Accepts any non-null object that isn't an array. */ function isRecord(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function resolveUserPath(input) { const trimmed = input.trim(); if (!trimmed) return trimmed; if (trimmed.startsWith("~")) { const expanded = expandHomePrefix(trimmed, { home: resolveRequiredHomeDir(process.env, os.homedir), env: process.env, homedir: os.homedir }); return path.resolve(expanded); } return path.resolve(trimmed); } function resolveConfigDir(env = process.env, homedir = os.homedir) { const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim(); if (override) return resolveUserPath(override); const newDir = path.join(resolveRequiredHomeDir(env, homedir), ".openclaw"); try { if (fs.existsSync(newDir)) return newDir; } catch {} return newDir; } function resolveHomeDir$2() { return resolveEffectiveHomeDir(process.env, os.homedir); } function resolveHomeDisplayPrefix() { const home = resolveHomeDir$2(); if (!home) return; if (process.env.OPENCLAW_HOME?.trim()) return { home, prefix: "$OPENCLAW_HOME" }; return { home, prefix: "~" }; } function shortenHomePath(input) { if (!input) return input; const display = resolveHomeDisplayPrefix(); if (!display) return input; const { home, prefix } = display; if (input === home) return prefix; if (input.startsWith(`${home}/`) || input.startsWith(`${home}\\`)) return `${prefix}${input.slice(home.length)}`; return input; } function shortenHomeInString(input) { if (!input) return input; const display = resolveHomeDisplayPrefix(); if (!display) return input; return input.split(display.home).join(display.prefix); } function formatTerminalLink(label, url, opts) { const esc = "\x1B"; const safeLabel = label.replaceAll(esc, ""); const safeUrl = url.replaceAll(esc, ""); if (!(opts?.force === true ? true : opts?.force === false ? false : Boolean(process.stdout.isTTY))) return opts?.fallback ?? `${safeLabel} (${safeUrl})`; return `\u001b]8;;${safeUrl}\u0007${safeLabel}\u001b]8;;\u0007`; } const CONFIG_DIR = resolveConfigDir(); //#endregion //#region src/terminal/links.ts const DOCS_ROOT = "https://docs.openclaw.ai"; function formatDocsLink(path, label, opts) { const trimmed = path.trim(); const url = trimmed.startsWith("http") ? trimmed : `${DOCS_ROOT}${trimmed.startsWith("/") ? trimmed : `/${trimmed}`}`; return formatTerminalLink(label ?? url, url, { fallback: opts?.fallback ?? url, force: opts?.force }); } //#endregion //#region src/cli/command-options.ts function getOptionSource(command, name) { if (typeof command.getOptionValueSource !== "function") return; return command.getOptionValueSource(name); } const MAX_INHERIT_DEPTH = 2; function inheritOptionFromParent(command, name) { if (!command) return; const childSource = getOptionSource(command, name); if (childSource && childSource !== "default") return; let depth = 0; let ancestor = command.parent; while (ancestor && depth < MAX_INHERIT_DEPTH) { const source = getOptionSource(ancestor, name); if (source && source !== "default") return ancestor.opts()[name]; depth += 1; ancestor = ancestor.parent; } } //#endregion //#region src/cli/cli-name.ts const DEFAULT_CLI_NAME = "openclaw"; const KNOWN_CLI_NAMES = new Set([DEFAULT_CLI_NAME]); const CLI_PREFIX_RE$1 = /^(?:((?:pnpm|npm|bunx|npx)\s+))?(openclaw)\b/; function resolveCliName(argv = process.argv) { const argv1 = argv[1]; if (!argv1) return DEFAULT_CLI_NAME; const base = path.basename(argv1).trim(); if (KNOWN_CLI_NAMES.has(base)) return base; return DEFAULT_CLI_NAME; } function replaceCliName(command, cliName = resolveCliName()) { if (!command.trim()) return command; if (!CLI_PREFIX_RE$1.test(command)) return command; return command.replace(CLI_PREFIX_RE$1, (_match, runner) => { return `${runner ?? ""}${cliName}`; }); } //#endregion //#region src/cli/profile-utils.ts const PROFILE_NAME_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i; function isValidProfileName(value) { if (!value) return false; return PROFILE_NAME_RE.test(value); } function normalizeProfileName(raw) { const profile = raw?.trim(); if (!profile) return null; if (profile.toLowerCase() === "default") return null; if (!isValidProfileName(profile)) return null; return profile; } //#endregion //#region src/cli/command-format.ts const CLI_PREFIX_RE = /^(?:pnpm|npm|bunx|npx)\s+openclaw\b|^openclaw\b/; const PROFILE_FLAG_RE = /(?:^|\s)--profile(?:\s|=|$)/; const DEV_FLAG_RE = /(?:^|\s)--dev(?:\s|$)/; function formatCliCommand(command, env = process.env) { const normalizedCommand = replaceCliName(command, resolveCliName()); const profile = normalizeProfileName(env.OPENCLAW_PROFILE); if (!profile) return normalizedCommand; if (!CLI_PREFIX_RE.test(normalizedCommand)) return normalizedCommand; if (PROFILE_FLAG_RE.test(normalizedCommand) || DEV_FLAG_RE.test(normalizedCommand)) return normalizedCommand; return normalizedCommand.replace(CLI_PREFIX_RE, (match) => `${match} --profile ${profile}`); } //#endregion //#region src/config/env-vars.ts function collectConfigEnvVars(cfg) { const envConfig = cfg?.env; if (!envConfig) return {}; const entries = {}; if (envConfig.vars) for (const [key, value] of Object.entries(envConfig.vars)) { if (!value) continue; entries[key] = value; } for (const [key, value] of Object.entries(envConfig)) { if (key === "shellEnv" || key === "vars") continue; if (typeof value !== "string" || !value.trim()) continue; entries[key] = value; } return entries; } function applyConfigEnvVars(cfg, env = process.env) { const entries = collectConfigEnvVars(cfg); for (const [key, value] of Object.entries(entries)) { if (env[key]?.trim()) continue; env[key] = value; } } //#endregion //#region src/daemon/constants.ts const GATEWAY_LAUNCH_AGENT_LABEL = "ai.openclaw.gateway"; const GATEWAY_SYSTEMD_SERVICE_NAME = "openclaw-gateway"; const GATEWAY_WINDOWS_TASK_NAME = "OpenClaw Gateway"; const GATEWAY_SERVICE_MARKER = "openclaw"; const GATEWAY_SERVICE_KIND = "gateway"; function normalizeGatewayProfile(profile) { const trimmed = profile?.trim(); if (!trimmed || trimmed.toLowerCase() === "default") return null; return trimmed; } function resolveGatewayProfileSuffix(profile) { const normalized = normalizeGatewayProfile(profile); return normalized ? `-${normalized}` : ""; } function resolveGatewayLaunchAgentLabel(profile) { const normalized = normalizeGatewayProfile(profile); if (!normalized) return GATEWAY_LAUNCH_AGENT_LABEL; return `ai.openclaw.${normalized}`; } function resolveLegacyGatewayLaunchAgentLabels(profile) { return []; } function resolveGatewaySystemdServiceName(profile) { const suffix = resolveGatewayProfileSuffix(profile); if (!suffix) return GATEWAY_SYSTEMD_SERVICE_NAME; return `openclaw-gateway${suffix}`; } function resolveGatewayWindowsTaskName(profile) { const normalized = normalizeGatewayProfile(profile); if (!normalized) return GATEWAY_WINDOWS_TASK_NAME; return `OpenClaw Gateway (${normalized})`; } function formatGatewayServiceDescription(params) { const profile = normalizeGatewayProfile(params?.profile); const version = params?.version?.trim(); const parts = []; if (profile) parts.push(`profile: ${profile}`); if (version) parts.push(`v${version}`); if (parts.length === 0) return "OpenClaw Gateway"; return `OpenClaw Gateway (${parts.join(", ")})`; } function resolveGatewayServiceDescription(params) { return params.description ?? formatGatewayServiceDescription({ profile: params.env.OPENCLAW_PROFILE, version: params.environment?.OPENCLAW_SERVICE_VERSION ?? params.env.OPENCLAW_SERVICE_VERSION }); } //#endregion //#region src/daemon/program-args.ts function isNodeRuntime$1(execPath) { const base = path.basename(execPath).toLowerCase(); return base === "node" || base === "node.exe"; } function isBunRuntime$1(execPath) { const base = path.basename(execPath).toLowerCase(); return base === "bun" || base === "bun.exe"; } async function resolveCliEntrypointPathForService() { const argv1 = process.argv[1]; if (!argv1) throw new Error("Unable to resolve CLI entrypoint path"); const normalized = path.resolve(argv1); const resolvedPath = await resolveRealpathSafe(normalized); if (/[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(resolvedPath)) { await fs$1.access(resolvedPath); if (/[/\\]dist[/\\].+\.(cjs|js|mjs)$/.test(normalized) && normalized !== resolvedPath) try { await fs$1.access(normalized); return normalized; } catch {} return resolvedPath; } const distCandidates = buildDistCandidates(resolvedPath, normalized); for (const candidate of distCandidates) try { await fs$1.access(candidate); return candidate; } catch {} throw new Error(`Cannot find built CLI at ${distCandidates.join(" or ")}. Run "pnpm build" first, or use dev mode.`); } async function resolveRealpathSafe(inputPath) { try { return await fs$1.realpath(inputPath); } catch { return inputPath; } } function buildDistCandidates(...inputs) { const candidates = []; const seen = /* @__PURE__ */ new Set(); for (const inputPath of inputs) { if (!inputPath) continue; const baseDir = path.dirname(inputPath); appendDistCandidates(candidates, seen, path.resolve(baseDir, "..")); appendDistCandidates(candidates, seen, baseDir); appendNodeModulesBinCandidates(candidates, seen, inputPath); } return candidates; } function appendDistCandidates(candidates, seen, baseDir) { const distDir = path.resolve(baseDir, "dist"); const distEntries = [ path.join(distDir, "index.js"), path.join(distDir, "index.mjs"), path.join(distDir, "entry.js"), path.join(distDir, "entry.mjs") ]; for (const entry of distEntries) { if (seen.has(entry)) continue; seen.add(entry); candidates.push(entry); } } function appendNodeModulesBinCandidates(candidates, seen, inputPath) { const parts = inputPath.split(path.sep); const binIndex = parts.lastIndexOf(".bin"); if (binIndex <= 0) return; if (parts[binIndex - 1] !== "node_modules") return; const binName = path.basename(inputPath); const nodeModulesDir = parts.slice(0, binIndex).join(path.sep); appendDistCandidates(candidates, seen, path.join(nodeModulesDir, binName)); } function resolveRepoRootForDev() { const argv1 = process.argv[1]; if (!argv1) throw new Error("Unable to resolve repo root"); const parts = path.resolve(argv1).split(path.sep); const srcIndex = parts.lastIndexOf("src"); if (srcIndex === -1) throw new Error("Dev mode requires running from repo (src/index.ts)"); return parts.slice(0, srcIndex).join(path.sep); } async function resolveBunPath() { return await resolveBinaryPath("bun"); } async function resolveNodePath() { return await resolveBinaryPath("node"); } async function resolveBinaryPath(binary) { const { execFileSync } = await import("node:child_process"); const cmd = process.platform === "win32" ? "where" : "which"; try { const resolved = execFileSync(cmd, [binary], { encoding: "utf8" }).trim().split(/\r?\n/)[0]?.trim(); if (!resolved) throw new Error("empty"); await fs$1.access(resolved); return resolved; } catch { if (binary === "bun") throw new Error("Bun not found in PATH. Install bun: https://bun.sh"); throw new Error("Node not found in PATH. Install Node 22+."); } } async function resolveCliProgramArguments(params) { const execPath = process.execPath; const runtime = params.runtime ?? "auto"; if (runtime === "node") return { programArguments: [ params.nodePath ?? (isNodeRuntime$1(execPath) ? execPath : await resolveNodePath()), await resolveCliEntrypointPathForService(), ...params.args ] }; if (runtime === "bun") { if (params.dev) { const repoRoot = resolveRepoRootForDev(); const devCliPath = path.join(repoRoot, "src", "index.ts"); await fs$1.access(devCliPath); return { programArguments: [ isBunRuntime$1(execPath) ? execPath : await resolveBunPath(), devCliPath, ...params.args ], workingDirectory: repoRoot }; } return { programArguments: [ isBunRuntime$1(execPath) ? execPath : await resolveBunPath(), await resolveCliEntrypointPathForService(), ...params.args ] }; } if (!params.dev) try { return { programArguments: [ execPath, await resolveCliEntrypointPathForService(), ...params.args ] }; } catch (error) { if (!isNodeRuntime$1(execPath)) return { programArguments: [execPath, ...params.args] }; throw error; } const repoRoot = resolveRepoRootForDev(); const devCliPath = path.join(repoRoot, "src", "index.ts"); await fs$1.access(devCliPath); if (isBunRuntime$1(execPath)) return { programArguments: [ execPath, devCliPath, ...params.args ], workingDirectory: repoRoot }; return { programArguments: [ await resolveBunPath(), devCliPath, ...params.args ], workingDirectory: repoRoot }; } async function resolveGatewayProgramArguments(params) { return resolveCliProgramArguments({ args: [ "gateway", "--port", String(params.port) ], dev: params.dev, runtime: params.runtime, nodePath: params.nodePath }); } //#endregion //#region src/terminal/progress-line.ts let activeStream = null; function registerActiveProgressLine(stream) { if (!stream.isTTY) return; activeStream = stream; } function clearActiveProgressLine() { if (!activeStream?.isTTY) return; activeStream.write("\r\x1B[2K"); } function unregisterActiveProgressLine(stream) { if (!activeStream) return; if (stream && activeStream !== stream) return; activeStream = null; } //#endregion //#region src/terminal/restore.ts const RESET_SEQUENCE = "\x1B[0m\x1B[?25h\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?2004l"; function reportRestoreFailure(scope, err, reason) { const suffix = reason ? ` (${reason})` : ""; const message = `[terminal] restore ${scope} failed${suffix}: ${String(err)}`; try { process.stderr.write(`${message}\n`); } catch (writeErr) { console.error(`[terminal] restore reporting failed${suffix}: ${String(writeErr)}`); } } function restoreTerminalState(reason, options = {}) { const resumeStdin = options.resumeStdinIfPaused ?? options.resumeStdin ?? false; try { clearActiveProgressLine(); } catch (err) { reportRestoreFailure("progress line", err, reason); } const stdin = process.stdin; if (stdin.isTTY && typeof stdin.setRawMode === "function") { try { stdin.setRawMode(false); } catch (err) { reportRestoreFailure("raw mode", err, reason); } if (resumeStdin && typeof stdin.isPaused === "function" && stdin.isPaused()) try { stdin.resume(); } catch (err) { reportRestoreFailure("stdin resume", err, reason); } } if (process.stdout.isTTY) try { process.stdout.write(RESET_SEQUENCE); } catch (err) { reportRestoreFailure("stdout reset", err, reason); } } //#endregion //#region src/runtime.ts function shouldEmitRuntimeLog(env = process.env) { if (env.VITEST !== "true") return true; if (env.OPENCLAW_TEST_RUNTIME_LOG === "1") return true; return typeof console.log.mock === "object"; } function createRuntimeIo() { return { log: (...args) => { if (!shouldEmitRuntimeLog()) return; clearActiveProgressLine(); console.log(...args); }, error: (...args) => { clearActiveProgressLine(); console.error(...args); } }; } const defaultRuntime = { ...createRuntimeIo(), exit: (code) => { restoreTerminalState("runtime exit", { resumeStdinIfPaused: false }); process.exit(code); throw new Error("unreachable"); } }; //#endregion //#region src/infra/runtime-guard.ts const MIN_NODE = { major: 22, minor: 12, patch: 0 }; const SEMVER_RE = /(\d+)\.(\d+)\.(\d+)/; function parseSemver(version) { if (!version) return null; const match = version.match(SEMVER_RE); if (!match) return null; const [, major, minor, patch] = match; return { major: Number.parseInt(major, 10), minor: Number.parseInt(minor, 10), patch: Number.parseInt(patch, 10) }; } function isAtLeast(version, minimum) { if (!version) return false; if (version.major !== minimum.major) return version.major > minimum.major; if (version.minor !== minimum.minor) return version.minor > minimum.minor; return version.patch >= minimum.patch; } function isSupportedNodeVersion(version) { return isAtLeast(parseSemver(version), MIN_NODE); } //#endregion //#region src/daemon/runtime-paths.ts const VERSION_MANAGER_MARKERS = [ "/.nvm/", "/.fnm/", "/.volta/", "/.asdf/", "/.n/", "/.nodenv/", "/.nodebrew/", "/nvs/" ]; function getPathModule$1(platform) { return platform === "win32" ? path.win32 : path.posix; } function isNodeExecPath(execPath, platform) { const base = getPathModule$1(platform).basename(execPath).toLowerCase(); return base === "node" || base === "node.exe"; } function normalizeForCompare(input, platform) { const normalized = getPathModule$1(platform).normalize(input).replaceAll("\\", "/"); if (platform === "win32") return normalized.toLowerCase(); return normalized; } function buildSystemNodeCandidates(env, platform) { if (platform === "darwin") return [ "/opt/homebrew/bin/node", "/usr/local/bin/node", "/usr/bin/node" ]; if (platform === "linux") return ["/usr/local/bin/node", "/usr/bin/node"]; if (platform === "win32") { const pathModule = getPathModule$1(platform); const programFiles = env.ProgramFiles ?? "C:\\Program Files"; const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)"; return [pathModule.join(programFiles, "nodejs", "node.exe"), pathModule.join(programFilesX86, "nodejs", "node.exe")]; } return []; } const execFileAsync$2 = promisify(execFile); async function resolveNodeVersion(nodePath, execFileImpl) { try { const { stdout } = await execFileImpl(nodePath, ["-p", "process.versions.node"], { encoding: "utf8" }); const value = stdout.trim(); return value ? value : null; } catch { return null; } } function isVersionManagedNodePath(nodePath, platform = process.platform) { const normalized = normalizeForCompare(nodePath, platform); return VERSION_MANAGER_MARKERS.some((marker) => normalized.includes(marker)); } function isSystemNodePath(nodePath, env = process.env, platform = process.platform) { const normalized = normalizeForCompare(nodePath, platform); return buildSystemNodeCandidates(env, platform).some((candidate) => { return normalized === normalizeForCompare(candidate, platform); }); } async function resolveSystemNodePath(env = process.env, platform = process.platform) { const candidates = buildSystemNodeCandidates(env, platform); for (const candidate of candidates) try { await fs$1.access(candidate); return candidate; } catch {} return null; } async function resolveSystemNodeInfo(params) { const systemNode = await resolveSystemNodePath(params.env ?? process.env, params.platform ?? process.platform); if (!systemNode) return null; const version = await resolveNodeVersion(systemNode, params.execFile ?? execFileAsync$2); return { path: systemNode, version, supported: isSupportedNodeVersion(version) }; } function renderSystemNodeWarning(systemNode, selectedNodePath) { if (!systemNode || systemNode.supported) return null; const versionLabel = systemNode.version ?? "unknown"; const selectedLabel = selectedNodePath ? ` Using ${selectedNodePath} for the daemon.` : ""; return `System Node ${versionLabel} at ${systemNode.path} is below the required Node 22+.${selectedLabel} Install Node 22+ from nodejs.org or Homebrew.`; } async function resolvePreferredNodePath(params) { if (params.runtime !== "node") return; const platform = params.platform ?? process.platform; const currentExecPath = params.execPath ?? process.execPath; if (currentExecPath && isNodeExecPath(currentExecPath, platform)) { if (isSupportedNodeVersion(await resolveNodeVersion(currentExecPath, params.execFile ?? execFileAsync$2))) return currentExecPath; } const systemNode = await resolveSystemNodeInfo(params); if (!systemNode?.supported) return; return systemNode.path; } //#endregion //#region src/version.ts const CORE_PACKAGE_NAME = "openclaw"; const PACKAGE_JSON_CANDIDATES = [ "../package.json", "../../package.json", "../../../package.json", "./package.json" ]; const BUILD_INFO_CANDIDATES = [ "../build-info.json", "../../build-info.json", "./build-info.json" ]; function readVersionFromJsonCandidates(moduleUrl, candidates, opts = {}) { try { const require = createRequire(moduleUrl); for (const candidate of candidates) try { const parsed = require(candidate); const version = parsed.version?.trim(); if (!version) continue; if (opts.requirePackageName && parsed.name !== CORE_PACKAGE_NAME) continue; return version; } catch {} return null; } catch { return null; } } function readVersionFromPackageJsonForModuleUrl(moduleUrl) { return readVersionFromJsonCandidates(moduleUrl, PACKAGE_JSON_CANDIDATES, { requirePackageName: true }); } function readVersionFromBuildInfoForModuleUrl(moduleUrl) { return readVersionFromJsonCandidates(moduleUrl, BUILD_INFO_CANDIDATES); } function resolveVersionFromModuleUrl(moduleUrl) { return readVersionFromPackageJsonForModuleUrl(moduleUrl) || readVersionFromBuildInfoForModuleUrl(moduleUrl); } const VERSION = typeof __OPENCLAW_VERSION__ === "string" && __OPENCLAW_VERSION__ || process.env.OPENCLAW_BUNDLED_VERSION || resolveVersionFromModuleUrl(import.meta.url) || "0.0.0"; //#endregion //#region src/daemon/service-env.ts function addNonEmptyDir(dirs, dir) { if (dir) dirs.push(dir); } function appendSubdir(base, subdir) { if (!base) return; return base.endsWith(`/${subdir}`) ? base : path.posix.join(base, subdir); } function addCommonUserBinDirs(dirs, home) { dirs.push(`${home}/.local/bin`); dirs.push(`${home}/.npm-global/bin`); dirs.push(`${home}/bin`); dirs.push(`${home}/.volta/bin`); dirs.push(`${home}/.asdf/shims`); dirs.push(`${home}/.bun/bin`); } function resolveSystemPathDirs(platform) { if (platform === "darwin") return [ "/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin" ]; if (platform === "linux") return [ "/usr/local/bin", "/usr/bin", "/bin" ]; return []; } /** * Resolve common user bin directories for macOS. * These are paths where npm global installs and node version managers typically place binaries. * * Key differences from Linux: * - fnm: macOS uses ~/Library/Application Support/fnm (not ~/.local/share/fnm) * - pnpm: macOS uses ~/Library/pnpm (not ~/.local/share/pnpm) */ function resolveDarwinUserBinDirs(home, env) { if (!home) return []; const dirs = []; addNonEmptyDir(dirs, env?.PNPM_HOME); addNonEmptyDir(dirs, appendSubdir(env?.NPM_CONFIG_PREFIX, "bin")); addNonEmptyDir(dirs, appendSubdir(env?.BUN_INSTALL, "bin")); addNonEmptyDir(dirs, appendSubdir(env?.VOLTA_HOME, "bin")); addNonEmptyDir(dirs, appendSubdir(env?.ASDF_DATA_DIR, "shims")); addNonEmptyDir(dirs, env?.NVM_DIR); addNonEmptyDir(dirs, appendSubdir(env?.FNM_DIR, "aliases/default/bin")); addCommonUserBinDirs(dirs, home); dirs.push(`${home}/Library/Application Support/fnm/aliases/default/bin`); dirs.push(`${home}/.fnm/aliases/default/bin`); dirs.push(`${home}/Library/pnpm`); dirs.push(`${home}/.local/share/pnpm`); return dirs; } /** * Resolve common user bin directories for Linux. * These are paths where npm global installs and node version managers typically place binaries. */ function resolveLinuxUserBinDirs(home, env) { if (!home) return []; const dirs = []; addNonEmptyDir(dirs, env?.PNPM_HOME); addNonEmptyDir(dirs, appendSubdir(env?.NPM_CONFIG_PREFIX, "bin")); addNonEmptyDir(dirs, appendSubdir(env?.BUN_INSTALL, "bin")); addNonEmptyDir(dirs, appendSubdir(env?.VOLTA_HOME, "bin")); addNonEmptyDir(dirs, appendSubdir(env?.ASDF_DATA_DIR, "shims")); addNonEmptyDir(dirs, appendSubdir(env?.NVM_DIR, "current/bin")); addNonEmptyDir(dirs, appendSubdir(env?.FNM_DIR, "current/bin")); addCommonUserBinDirs(dirs, home); dirs.push(`${home}/.nvm/current/bin`); dirs.push(`${home}/.fnm/current/bin`); dirs.push(`${home}/.local/share/pnpm`); return dirs; } function getMinimalServicePathParts(options = {}) { const platform = options.platform ?? process.platform; if (platform === "win32") return []; const parts = []; const extraDirs = options.extraDirs ?? []; const systemDirs = resolveSystemPathDirs(platform); const userDirs = platform === "linux" ? resolveLinuxUserBinDirs(options.home, options.env) : platform === "darwin" ? resolveDarwinUserBinDirs(options.home, options.env) : []; const add = (dir) => { if (!dir) return; if (!parts.includes(dir)) parts.push(dir); }; for (const dir of extraDirs) add(dir); for (const dir of userDirs) add(dir); for (const dir of systemDirs) add(dir); return parts; } function getMinimalServicePathPartsFromEnv(options = {}) { const env = options.env ?? process.env; return getMinimalServicePathParts({ ...options, home: options.home ?? env.HOME, env }); } function buildMinimalServicePath(options = {}) { const env = options.env ?? process.env; if ((options.platform ?? process.platform) === "win32") return env.PATH ?? ""; return getMinimalServicePathPartsFromEnv({ ...options, env }).join(path.posix.delimiter); } function buildServiceEnvironment(params) { const { env, port, token, launchdLabel } = params; const profile = env.OPENCLAW_PROFILE; const resolvedLaunchdLabel = launchdLabel || (process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : void 0); const systemdUnit = `${resolveGatewaySystemdServiceName(profile)}.service`; const stateDir = env.OPENCLAW_STATE_DIR; const configPath = env.OPENCLAW_CONFIG_PATH; const tmpDir = env.TMPDIR?.trim() || os.tmpdir(); return { HOME: env.HOME, TMPDIR: tmpDir, PATH: buildMinimalServicePath({ env }), OPENCLAW_PROFILE: profile, OPENCLAW_STATE_DIR: stateDir, OPENCLAW_CONFIG_PATH: configPath, OPENCLAW_GATEWAY_PORT: String(port), OPENCLAW_GATEWAY_TOKEN: token, OPENCLAW_LAUNCHD_LABEL: resolvedLaunchdLabel, OPENCLAW_SYSTEMD_UNIT: systemdUnit, OPENCLAW_SERVICE_MARKER: GATEWAY_SERVICE_MARKER, OPENCLAW_SERVICE_KIND: GATEWAY_SERVICE_KIND, OPENCLAW_SERVICE_VERSION: VERSION }; } //#endregion //#region src/commands/daemon-install-runtime-warning.ts async function emitNodeRuntimeWarning(params) { if (params.runtime !== "node") return; const warning = renderSystemNodeWarning(await resolveSystemNodeInfo({ env: params.env }), params.nodeProgram); if (warning) params.warn?.(warning, params.title); } //#endregion //#region src/commands/daemon-install-helpers.ts function resolveGatewayDevMode(argv = process.argv) { const normalizedEntry = argv[1]?.replaceAll("\\", "/"); return Boolean(normalizedEntry?.includes("/src/") && normalizedEntry.endsWith(".ts")); } async function buildGatewayInstallPlan(params) { const devMode = params.devMode ?? resolveGatewayDevMode(); const nodePath = params.nodePath ?? await resolvePreferredNodePath({ env: params.env, runtime: params.runtime }); const { programArguments, workingDirectory } = await resolveGatewayProgramArguments({ port: params.port, dev: devMode, runtime: params.runtime, nodePath }); await emitNodeRuntimeWarning({ env: params.env, runtime: params.runtime, nodeProgram: programArguments[0], warn: params.warn, title: "Gateway runtime" }); const serviceEnvironment = buildServiceEnvironment({ env: params.env, port: params.port, token: params.token, launchdLabel: process.platform === "darwin" ? resolveGatewayLaunchAgentLabel(params.env.OPENCLAW_PROFILE) : void 0 }); const environment = { ...collectConfigEnvVars(params.config) }; Object.assign(environment, serviceEnvironment); return { programArguments, workingDirectory, environment }; } //#endregion //#region src/commands/daemon-runtime.ts const DEFAULT_GATEWAY_DAEMON_RUNTIME = "node"; function isGatewayDaemonRuntime(value) { return value === "node" || value === "bun"; } //#endregion //#region src/plugins/registry.ts function createEmptyPluginRegistry() { return { plugins: [], tools: [], hooks: [], typedHooks: [], channels: [], providers: [], gatewayHandlers: {}, httpHandlers: [], httpRoutes: [], cliRegistrars: [], services: [], commands: [], diagnostics: [] }; } //#endregion //#region src/plugins/runtime.ts const REGISTRY_STATE = Symbol.for("openclaw.pluginRegistryState"); const state = (() => { const globalState = globalThis; if (!globalState[REGISTRY_STATE]) globalState[REGISTRY_STATE] = { registry: createEmptyPluginRegistry(), key: null }; return globalState[REGISTRY_STATE]; })(); //#endregion //#region src/channels/registry.ts const CHAT_CHANNEL_ORDER = [ "telegram", "whatsapp", "discord", "irc", "googlechat", "slack", "signal", "imessage" ]; const CHANNEL_IDS = [...CHAT_CHANNEL_ORDER]; const CHAT_CHANNEL_ALIASES = { imsg: "imessage", "internet-relay-chat": "irc", "google-chat": "googlechat", gchat: "googlechat" }; const normalizeChannelKey = (raw) => { return raw?.trim().toLowerCase() || void 0; }; function normalizeChatChannelId(raw) { const normalized = normalizeChannelKey(raw); if (!normalized) return null; const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized; return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null; } //#endregion //#region src/terminal/ansi.ts const ANSI_SGR_PATTERN = "\\x1b\\[[0-9;]*m"; const OSC8_PATTERN = "\\x1b\\]8;;.*?\\x1b\\\\|\\x1b\\]8;;\\x1b\\\\"; const ANSI_REGEX = new RegExp(ANSI_SGR_PATTERN, "g"); const OSC8_REGEX = new RegExp(OSC8_PATTERN, "g"); //#endregion //#region src/logging/console.ts function resolveNodeRequire$1() { const getBuiltinModule = process.getBuiltinModule; if (typeof getBuiltinModule !== "function") return null; try { const moduleNamespace = getBuiltinModule("module"); return typeof moduleNamespace.createRequire === "function" ? moduleNamespace.createRequire : null; } catch { return null; } } const requireConfig$1 = resolveNodeRequire$1()?.(import.meta.url) ?? null; const loadConfigFallbackDefault = () => { try { return (requireConfig$1?.("../config/config.js"))?.loadConfig?.().logging; } catch { return; } }; let loadConfigFallback = loadConfigFallbackDefault; function normalizeConsoleLevel(level) { if (isVerbose()) return "debug"; if (!level && process.env.VITEST === "true" && process.env.OPENCLAW_TEST_CONSOLE !== "1") return "silent"; return normalizeLogLevel(level, "info"); } function normalizeConsoleStyle(style) { if (style === "compact" || style === "json" || style === "pretty") return style; if (!process.stdout.isTTY) return "compact"; return "pretty"; } function resolveConsoleSettings() { let cfg = loggingState.overrideSettings ?? readLoggingConfig(); if (!cfg) if (loggingState.resolvingConsoleSettings) cfg = void 0; else { loggingState.resolvingConsoleSettings = true; try { cfg = loadConfigFallback(); } finally { loggingState.resolvingConsoleSettings = false; } } return { level: normalizeConsoleLevel(cfg?.consoleLevel), style: normalizeConsoleStyle(cfg?.consoleStyle) }; } function consoleSettingsChanged(a, b) { if (!a) return true; return a.level !== b.level || a.style !== b.style; } function getConsoleSettings() { const settings = resolveConsoleSettings(); const cached = loggingState.cachedConsoleSettings; if (!cached || consoleSettingsChanged(cached, settings)) loggingState.cachedConsoleSettings = settings; return loggingState.cachedConsoleSettings; } function shouldLogSubsystemToConsole(subsystem) { const filter = loggingState.consoleSubsystemFilter; if (!filter || filter.length === 0) return true; return filter.some((prefix) => subsystem === prefix || subsystem.startsWith(`${prefix}/`)); } //#endregion //#region src/logging/subsystem.ts function shouldLogToConsole(level, settings) { if (settings.level === "silent")