import fs from 'node:fs' import os from 'node:os' import path from 'node:path' /** Clamp a number to [min, max], falling back to `fallback` if NaN. */ function clampInt(value: number, min: number, max: number, fallback: number): number { if (isNaN(value)) return fallback return Math.max(min, Math.min(max, Math.floor(value))) } const isBuildPhase = process.env.NEXT_PHASE === 'phase-production-build' const defaultDataDir = path.join(process.cwd(), '.data') const configuredDataDir = process.env.MISSION_CONTROL_DATA_DIR || defaultDataDir const buildScratchRoot = process.env.MISSION_CONTROL_BUILD_DATA_DIR || path.join(os.tmpdir(), 'mission-control-build') const resolvedDataDir = isBuildPhase ? path.join(buildScratchRoot, `worker-${process.pid}`) : configuredDataDir const resolvedDbPath = isBuildPhase ? (process.env.MISSION_CONTROL_BUILD_DB_PATH || path.join(resolvedDataDir, 'mission-control.db')) : (process.env.MISSION_CONTROL_DB_PATH || path.join(resolvedDataDir, 'mission-control.db')) const resolvedTokensPath = isBuildPhase ? (process.env.MISSION_CONTROL_BUILD_TOKENS_PATH || path.join(resolvedDataDir, 'mission-control-tokens.json')) : (process.env.MISSION_CONTROL_TOKENS_PATH || path.join(resolvedDataDir, 'mission-control-tokens.json')) const defaultOpenClawStateDir = path.join(os.homedir(), '.openclaw') const explicitOpenClawConfigPath = process.env.OPENCLAW_CONFIG_PATH || process.env.MISSION_CONTROL_OPENCLAW_CONFIG_PATH || '' const legacyOpenClawHome = process.env.OPENCLAW_HOME || process.env.CLAWDBOT_HOME || process.env.MISSION_CONTROL_OPENCLAW_HOME || '' const openclawStateDir = process.env.OPENCLAW_STATE_DIR || process.env.CLAWDBOT_STATE_DIR || legacyOpenClawHome || (explicitOpenClawConfigPath ? path.dirname(explicitOpenClawConfigPath) : defaultOpenClawStateDir) const openclawConfigPath = explicitOpenClawConfigPath || path.join(openclawStateDir, 'openclaw.json') const openclawWorkspaceDir = process.env.OPENCLAW_WORKSPACE_DIR || process.env.MISSION_CONTROL_WORKSPACE_DIR || (openclawStateDir ? path.join(openclawStateDir, 'workspace') : '') const defaultMemoryDir = (() => { if (process.env.OPENCLAW_MEMORY_DIR) return process.env.OPENCLAW_MEMORY_DIR // Prefer OpenClaw workspace memory context (daily notes + knowledge-base) // when available; fallback to legacy sqlite memory path. if ( openclawWorkspaceDir && (fs.existsSync(path.join(openclawWorkspaceDir, 'memory')) || fs.existsSync(path.join(openclawWorkspaceDir, 'knowledge-base'))) ) { return openclawWorkspaceDir } return (openclawStateDir ? path.join(openclawStateDir, 'memory') : '') || path.join(defaultDataDir, 'memory') })() const resolvedGnapRepoPath = process.env.GNAP_REPO_PATH || path.join(configuredDataDir, '.gnap') export const config = { claudeHome: process.env.MC_CLAUDE_HOME || path.join(os.homedir(), '.claude'), dataDir: resolvedDataDir, dbPath: resolvedDbPath, tokensPath: resolvedTokensPath, // Keep openclawHome as a legacy alias for existing code paths. openclawHome: openclawStateDir, openclawStateDir, openclawConfigPath, openclawBin: process.env.OPENCLAW_BIN || 'openclaw', clawdbotBin: process.env.CLAWDBOT_BIN || 'clawdbot', gatewayHost: process.env.OPENCLAW_GATEWAY_HOST || '127.0.0.1', gatewayPort: clampInt(Number(process.env.OPENCLAW_GATEWAY_PORT || '18789'), 1, 65535, 18789), logsDir: process.env.OPENCLAW_LOG_DIR || (openclawStateDir ? path.join(openclawStateDir, 'logs') : ''), tempLogsDir: process.env.CLAWDBOT_TMP_LOG_DIR || '', memoryDir: defaultMemoryDir, memoryAllowedPrefixes: defaultMemoryDir === openclawWorkspaceDir ? ['memory/', 'knowledge-base/'] : [], soulTemplatesDir: process.env.OPENCLAW_SOUL_TEMPLATES_DIR || (openclawStateDir ? path.join(openclawStateDir, 'templates', 'souls') : ''), homeDir: os.homedir(), gnap: { enabled: process.env.GNAP_ENABLED === 'true', repoPath: resolvedGnapRepoPath, autoSync: process.env.GNAP_AUTO_SYNC !== 'false', remoteUrl: process.env.GNAP_REMOTE_URL || '', }, // Data retention (days). 0 = keep forever. Negative values are clamped to 0. retention: { activities: clampInt(Number(process.env.MC_RETAIN_ACTIVITIES_DAYS || '90'), 0, 3650, 90), auditLog: clampInt(Number(process.env.MC_RETAIN_AUDIT_DAYS || '365'), 0, 3650, 365), logs: clampInt(Number(process.env.MC_RETAIN_LOGS_DAYS || '30'), 0, 3650, 30), notifications: clampInt(Number(process.env.MC_RETAIN_NOTIFICATIONS_DAYS || '60'), 0, 3650, 60), pipelineRuns: clampInt(Number(process.env.MC_RETAIN_PIPELINE_RUNS_DAYS || '90'), 0, 3650, 90), tokenUsage: clampInt(Number(process.env.MC_RETAIN_TOKEN_USAGE_DAYS || '90'), 0, 3650, 90), gatewaySessions: clampInt(Number(process.env.MC_RETAIN_GATEWAY_SESSIONS_DAYS || '90'), 0, 3650, 90), }, } export function ensureDirExists(dirPath: string) { if (!dirPath) return if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }) } }