/** * HarborForge Monitor Plugin for OpenClaw * * Manages sidecar lifecycle and provides monitor-related tools. */ import { spawn } from 'child_process'; import { join } from 'path'; import { existsSync } from 'fs'; import { getLivePluginConfig, type HarborForgeMonitorConfig } from './core/live-config'; interface PluginAPI { logger: { info: (...args: any[]) => void; error: (...args: any[]) => void; debug: (...args: any[]) => void; warn: (...args: any[]) => void; }; version?: string; config?: Record; pluginConfig?: Record; on: (event: string, handler: () => void) => void; registerTool: (factory: (ctx: any) => any) => void; } export default { id: 'harborforge-monitor', name: 'HarborForge Monitor', register(api: PluginAPI) { const logger = api.logger || { info: (...args: any[]) => console.log('[HF-Monitor]', ...args), error: (...args: any[]) => console.error('[HF-Monitor]', ...args), debug: (...args: any[]) => console.debug('[HF-Monitor]', ...args), warn: (...args: any[]) => console.warn('[HF-Monitor]', ...args), }; const baseConfig: HarborForgeMonitorConfig = { enabled: true, backendUrl: 'https://monitor.hangman-lab.top', identifier: '', reportIntervalSec: 30, httpFallbackIntervalSec: 60, logLevel: 'info', ...(api.pluginConfig || {}), }; const serverPath = join(__dirname, 'server', 'telemetry.mjs'); let sidecar: ReturnType | null = null; function resolveConfig() { return getLivePluginConfig(api, baseConfig); } function startSidecar() { const live = resolveConfig(); const enabled = live.enabled !== false; logger.info('HarborForge Monitor plugin config resolved', { enabled, hasApiKey: Boolean(live.apiKey), backendUrl: live.backendUrl ?? null, identifier: live.identifier ?? null, }); if (!enabled) { logger.info('HarborForge Monitor plugin disabled'); return; } if (sidecar) { logger.debug('Sidecar already running'); return; } if (!live.apiKey) { logger.warn('Missing config: apiKey'); logger.warn('API authentication will fail. Generate apiKey from HarborForge Monitor admin.'); } if (!existsSync(serverPath)) { logger.error('Telemetry server not found:', serverPath); return; } logger.info('Starting HarborForge Monitor telemetry server...'); const env = { ...process.env, HF_MONITOR_BACKEND_URL: live.backendUrl || 'https://monitor.hangman-lab.top', HF_MONITOR_IDENTIFIER: live.identifier || '', HF_MONITOR_API_KEY: live.apiKey || '', HF_MONITOR_REPORT_INTERVAL: String(live.reportIntervalSec || 30), HF_MONITOR_HTTP_FALLBACK_INTERVAL: String(live.httpFallbackIntervalSec || 60), HF_MONITOR_LOG_LEVEL: live.logLevel || 'info', OPENCLAW_PATH: process.env.OPENCLAW_PATH || join(process.env.HOME || '/root', '.openclaw'), HF_MONITOR_PLUGIN_VERSION: api.version || 'unknown', }; sidecar = spawn('node', [serverPath], { env, detached: false, stdio: ['ignore', 'pipe', 'pipe'], }); sidecar.stdout?.on('data', (data: Buffer) => { logger.info('[telemetry]', data.toString().trim()); }); sidecar.stderr?.on('data', (data: Buffer) => { logger.error('[telemetry]', data.toString().trim()); }); sidecar.on('exit', (code, signal) => { logger.info(`Telemetry server exited (code: ${code}, signal: ${signal})`); sidecar = null; }); sidecar.on('error', (err: Error) => { logger.error('Failed to start telemetry server:', err.message); sidecar = null; }); logger.info('Telemetry server started with PID:', sidecar.pid); } function stopSidecar() { if (!sidecar) { logger.debug('Telemetry server not running'); return; } logger.info('Stopping HarborForge Monitor telemetry server...'); sidecar.kill('SIGTERM'); const timeout = setTimeout(() => { if (sidecar && !sidecar.killed) { logger.warn('Telemetry server did not exit gracefully, forcing kill'); sidecar.kill('SIGKILL'); } }, 5000); sidecar.on('exit', () => { clearTimeout(timeout); }); } api.on('gateway_start', () => { logger.info('gateway_start received, starting telemetry server...'); startSidecar(); }); api.on('gateway_stop', () => { logger.info('gateway_stop received, stopping telemetry server...'); stopSidecar(); }); process.on('SIGTERM', stopSidecar); process.on('SIGINT', stopSidecar); api.registerTool(() => ({ name: 'harborforge_monitor_status', description: 'Get HarborForge Monitor plugin status', parameters: { type: 'object', properties: {}, }, async execute() { const live = resolveConfig(); return { enabled: live.enabled !== false, sidecarRunning: sidecar !== null && sidecar.exitCode === null, pid: sidecar?.pid || null, config: { backendUrl: live.backendUrl, identifier: live.identifier || 'auto-detected', reportIntervalSec: live.reportIntervalSec, hasApiKey: Boolean(live.apiKey), }, }; }, })); logger.info('HarborForge Monitor plugin registered'); }, };