Files
MATTERMOST_OPENCLAW_LIVESTATUS/src/config.js
sol 0d0e6e9d90 fix: resolve DM channel for agent:main:main sessions
The main agent session uses key 'agent:main:main' which doesn't
contain a channel ID. The session monitor now falls back to reading
deliveryContext/lastTo from sessions.json and resolves 'user:XXXX'
format via the Mattermost direct channel API.

Fixes: status watcher not tracking the main agent's active transcript
2026-03-07 22:35:40 +00:00

123 lines
3.5 KiB
JavaScript

'use strict';
/**
* config.js — Centralized env-var config with validation.
* All config is read from environment variables.
* Throws on missing required variables at startup.
*/
function getEnv(name, defaultValue, required = false) {
const val = process.env[name];
if (val === undefined || val === '') {
if (required) {
throw new Error(`Required environment variable ${name} is not set`);
}
return defaultValue;
}
return val;
}
function getEnvInt(name, defaultValue, required = false) {
const val = getEnv(name, undefined, required);
if (val === undefined) return defaultValue;
const n = parseInt(val, 10);
if (isNaN(n)) throw new Error(`Environment variable ${name} must be an integer, got: ${val}`);
return n;
}
function getEnvBool(name, defaultValue) {
const val = process.env[name];
if (val === undefined || val === '') return defaultValue;
return val === '1' || val.toLowerCase() === 'true' || val.toLowerCase() === 'yes';
}
/**
* Build and validate the config object.
* Called once at startup; throws on invalid config.
*/
function buildConfig() {
const config = {
// Mattermost API
mm: {
token: getEnv('MM_BOT_TOKEN', null, true),
baseUrl: getEnv('MM_BASE_URL', 'https://slack.solio.tech'),
maxSockets: getEnvInt('MM_MAX_SOCKETS', 4),
botUserId: getEnv('MM_BOT_USER_ID', null),
},
// Transcript directory (OpenClaw agents)
transcriptDir: getEnv('TRANSCRIPT_DIR', '/home/node/.openclaw/agents'),
// Timing
throttleMs: getEnvInt('THROTTLE_MS', 500),
idleTimeoutS: getEnvInt('IDLE_TIMEOUT_S', 60),
sessionPollMs: getEnvInt('SESSION_POLL_MS', 2000),
// Limits
maxActiveSessions: getEnvInt('MAX_ACTIVE_SESSIONS', 20),
maxMessageChars: getEnvInt('MAX_MESSAGE_CHARS', 15000),
maxStatusLines: getEnvInt('MAX_STATUS_LINES', 20),
maxRetries: getEnvInt('MAX_RETRIES', 3),
// Circuit breaker
circuitBreakerThreshold: getEnvInt('CIRCUIT_BREAKER_THRESHOLD', 5),
circuitBreakerCooldownS: getEnvInt('CIRCUIT_BREAKER_COOLDOWN_S', 30),
// Health check
healthPort: getEnvInt('HEALTH_PORT', 9090),
// Logging
logLevel: getEnv('LOG_LEVEL', 'info'),
// PID file
pidFile: getEnv('PID_FILE', '/tmp/status-watcher.pid'),
// Offset persistence
offsetFile: getEnv('OFFSET_FILE', '/tmp/status-watcher-offsets.json'),
// Optional external tool labels override
toolLabelsFile: getEnv('TOOL_LABELS_FILE', null),
// Fallback channel for non-MM sessions (null = skip)
defaultChannel: getEnv('DEFAULT_CHANNEL', null),
// Feature flags
enableFsWatch: getEnvBool('ENABLE_FS_WATCH', true),
// Mattermost plugin integration (optional)
// When configured, updates are sent to the plugin instead of using PUT on posts
plugin: {
url: getEnv('PLUGIN_URL', null), // e.g. https://slack.solio.tech/plugins/com.openclaw.livestatus
secret: getEnv('PLUGIN_SECRET', null),
enabled: getEnvBool('PLUGIN_ENABLED', true),
detectIntervalMs: getEnvInt('PLUGIN_DETECT_INTERVAL_MS', 60000),
},
};
// Validate MM base URL
try {
new URL(config.mm.baseUrl);
} catch (_e) {
throw new Error(`MM_BASE_URL is not a valid URL: ${config.mm.baseUrl}`);
}
return config;
}
// Singleton — built once, exported
let _config = null;
function getConfig() {
if (!_config) {
_config = buildConfig();
}
return _config;
}
// Allow resetting config in tests
function resetConfig() {
_config = null;
}
module.exports = { getConfig, resetConfig, buildConfig };