UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

160 lines (159 loc) 7.9 kB
import { a as uniqueStrings, i as normalizeStringEntries, r as normalizeOptionalString, t as normalizeLowercaseStringOrEmpty } from "./string-utils-BtCofrRx.js"; import "./fs-utils-BCGk0EZn.js"; import fs from "node:fs"; import path from "node:path"; import os from "node:os"; //#region packages/memory-host-sdk/src/host/config-utils.ts /** Root memory filename used in agent workspaces. */ const CANONICAL_ROOT_MEMORY_FILENAME = "MEMORY.md"; const DEFAULT_AGENT_ID = "main"; const VALID_ID_RE = /^[a-z0-9][a-z0-9_-]{0,63}$/i; const INVALID_CHARS_RE = /[^a-z0-9_-]+/g; const LEADING_DASH_RE = /^-+/; const TRAILING_DASH_RE = /-+$/; const LEGACY_STATE_DIRNAMES = [".clawdbot"]; const NEW_STATE_DIRNAME = ".openclaw"; const DURATION_MULTIPLIERS = { ms: 1, s: 1e3, m: 6e4, h: 36e5, d: 864e5 }; /** Round parsed durations and reject values outside the safe integer range. */ function roundDurationMs(raw, value) { const rounded = Math.round(value); if (!Number.isSafeInteger(rounded)) throw new Error(`invalid duration: ${raw}`); return rounded; } /** Normalize user or config agent ids to the filesystem-safe canonical form. */ function normalizeAgentId(value) { const trimmed = (value ?? "").trim(); if (!trimmed) return DEFAULT_AGENT_ID; const normalized = normalizeLowercaseStringOrEmpty(trimmed); if (VALID_ID_RE.test(trimmed)) return normalized; return normalized.replace(INVALID_CHARS_RE, "-").replace(LEADING_DASH_RE, "").replace(TRAILING_DASH_RE, "").slice(0, 64) || DEFAULT_AGENT_ID; } /** Treat shell-placeholder home values as absent. */ function normalizeHomeValue(value) { const trimmed = normalizeOptionalString(value); if (!trimmed || trimmed === "undefined" || trimmed === "null") return; return trimmed; } /** Resolve the underlying OS home before applying OpenClaw-specific overrides. */ function resolveRawOsHomeDir(env, homedir) { return normalizeHomeValue(env.HOME) ?? normalizeHomeValue(env.USERPROFILE) ?? normalizeHomeValue(homedir()); } /** Resolve OPENCLAW_HOME or the OS home, falling back to cwd for hermetic tests. */ function resolveRequiredHomeDir(env = process.env, homedir = os.homedir) { const explicitHome = normalizeHomeValue(env.OPENCLAW_HOME); const rawHome = explicitHome ? explicitHome.replace(/^~(?=$|[\\/])/, resolveRawOsHomeDir(env, homedir) ?? "") : resolveRawOsHomeDir(env, homedir); return rawHome ? path.resolve(rawHome) : path.resolve(process.cwd()); } /** Resolve absolute user paths, including "~" against the effective OpenClaw home. */ function resolveUserPath(input, env = process.env, homedir = os.homedir) { const trimmed = input.trim(); if (!trimmed) return trimmed; if (trimmed.startsWith("~")) return path.resolve(trimmed.replace(/^~(?=$|[\\/])/, resolveRequiredHomeDir(env, homedir))); return path.resolve(trimmed); } /** Return legacy state roots in priority order. */ function legacyStateDirs(homedir) { return LEGACY_STATE_DIRNAMES.map((dir) => path.join(homedir(), dir)); } /** Resolve the current state root while preserving shipped legacy installs when present. */ function resolveStateDir(env = process.env, homedir = os.homedir) { const override = env.OPENCLAW_STATE_DIR?.trim(); if (override) return resolveUserPath(override, env, homedir); const effectiveHome = () => resolveRequiredHomeDir(env, homedir); const nextDir = path.join(effectiveHome(), NEW_STATE_DIRNAME); if (env.OPENCLAW_TEST_FAST === "1" || fs.existsSync(nextDir)) return nextDir; return legacyStateDirs(effectiveHome).find((dir) => { try { return fs.existsSync(dir); } catch { return false; } }) ?? nextDir; } /** Resolve the default agent workspace, partitioned by OPENCLAW_PROFILE when set. */ function resolveDefaultAgentWorkspaceDir(env = process.env) { const home = resolveRequiredHomeDir(env, os.homedir); const profile = env.OPENCLAW_PROFILE?.trim(); if (profile && normalizeLowercaseStringOrEmpty(profile) !== "default") return path.join(home, ".openclaw", `workspace-${profile}`); return path.join(home, ".openclaw", "workspace"); } /** Return configured agent entries after dropping nullish placeholders. */ function listAgentEntries(cfg) { return Array.isArray(cfg.agents?.list) ? cfg.agents.list.filter((entry) => Boolean(entry)) : []; } /** Resolve the default agent id from explicit default marker or first agent entry. */ function resolveDefaultAgentId(cfg) { const agents = listAgentEntries(cfg); if (agents.length === 0) return DEFAULT_AGENT_ID; const chosen = (agents.find((agent) => agent.default) ?? agents[0])?.id; return normalizeAgentId(chosen || DEFAULT_AGENT_ID); } /** Find one agent config by canonical id. */ function resolveAgentConfig(cfg, agentId) { const id = normalizeAgentId(agentId); return listAgentEntries(cfg).find((entry) => normalizeAgentId(entry.id) === id); } /** Remove null bytes before paths are handed to filesystem APIs. */ function stripNullBytes(value) { return value.replaceAll("\0", ""); } /** Resolve the workspace directory for an agent id and config defaults. */ function resolveAgentWorkspaceDir(cfg, agentId, env = process.env) { const id = normalizeAgentId(agentId); const configured = resolveAgentConfig(cfg, id)?.workspace?.trim(); if (configured) return stripNullBytes(resolveUserPath(configured, env)); const fallback = cfg.agents?.defaults?.workspace?.trim(); if (id === resolveDefaultAgentId(cfg)) return stripNullBytes(fallback ? resolveUserPath(fallback, env) : resolveDefaultAgentWorkspaceDir(env)); if (fallback) return stripNullBytes(path.join(resolveUserPath(fallback, env), id)); return stripNullBytes(path.join(resolveStateDir(env), `workspace-${id}`)); } /** Resolve context limits for an agent with defaults fallback. */ function resolveAgentContextLimits(cfg, agentId) { const defaults = cfg?.agents?.defaults?.contextLimits; if (!cfg || !agentId) return defaults; return resolveAgentConfig(cfg, agentId)?.contextLimits ?? defaults; } /** Resolve enabled memory search config plus deduplicated extra paths for an agent. */ function resolveMemorySearchConfig(cfg, agentId) { const defaults = cfg.agents?.defaults?.memorySearch; const overrides = resolveAgentConfig(cfg, agentId)?.memorySearch; const enabled = overrides?.enabled ?? defaults?.enabled ?? true; if (!enabled) return null; return { enabled, extraPaths: uniqueStrings(normalizeStringEntries([...defaults?.extraPaths ?? [], ...overrides?.extraPaths ?? []])) }; } /** Parse compact duration strings such as "500ms", "5s", or "1h30m" into milliseconds. */ function parseDurationMs(raw, opts) { const trimmed = normalizeLowercaseStringOrEmpty(normalizeOptionalString(raw) ?? ""); if (!trimmed) throw new Error("invalid duration (empty)"); const single = /^(\d+(?:\.\d+)?)(ms|s|m|h|d)?$/.exec(trimmed); if (single) { const value = Number(single[1]); if (!Number.isFinite(value) || value < 0) throw new Error(`invalid duration: ${raw}`); return roundDurationMs(raw, value * (DURATION_MULTIPLIERS[single[2] ?? opts?.defaultUnit ?? "ms"] ?? 1)); } let totalMs = 0; let consumed = 0; for (const match of trimmed.matchAll(/(\d+(?:\.\d+)?)(ms|s|m|h|d)/g)) { const [full, valueRaw, unitRaw] = match; const index = match.index ?? -1; if (!full || !valueRaw || !unitRaw || index !== consumed) throw new Error(`invalid duration: ${raw}`); const value = Number(valueRaw); const multiplier = DURATION_MULTIPLIERS[unitRaw]; if (!Number.isFinite(value) || value < 0 || !multiplier) throw new Error(`invalid duration: ${raw}`); totalMs += value * multiplier; consumed += full.length; } if (consumed !== trimmed.length || consumed === 0) throw new Error(`invalid duration: ${raw}`); return roundDurationMs(raw, totalMs); } //#endregion export { resolveAgentWorkspaceDir as a, resolveAgentContextLimits as i, normalizeAgentId as n, resolveMemorySearchConfig as o, parseDurationMs as r, resolveUserPath as s, CANONICAL_ROOT_MEMORY_FILENAME as t };