From 78a61e0fb2a7b6799fd9550c2fc70027c3bb1058 Mon Sep 17 00:00:00 2001 From: zhi Date: Sat, 21 Mar 2026 16:07:01 +0000 Subject: [PATCH] Integrate plugin with local monitor bridge --- README.md | 15 ++++++- plugin/core/live-config.d.ts | 1 + plugin/core/live-config.d.ts.map | 2 +- plugin/core/live-config.js | 2 +- plugin/core/live-config.js.map | 2 +- plugin/core/live-config.ts | 3 +- plugin/core/monitor-bridge.d.ts | 41 +++++++++++++++++ plugin/core/monitor-bridge.d.ts.map | 1 + plugin/core/monitor-bridge.js | 44 ++++++++++++++++++ plugin/core/monitor-bridge.js.map | 1 + plugin/core/monitor-bridge.ts | 69 +++++++++++++++++++++++++++++ plugin/index.ts | 49 ++++++++++++++++++++ 12 files changed, 225 insertions(+), 5 deletions(-) create mode 100644 plugin/core/monitor-bridge.d.ts create mode 100644 plugin/core/monitor-bridge.d.ts.map create mode 100644 plugin/core/monitor-bridge.js create mode 100644 plugin/core/monitor-bridge.js.map create mode 100644 plugin/core/monitor-bridge.ts diff --git a/README.md b/README.md index d61b5dc..2c9bfdc 100644 --- a/README.md +++ b/README.md @@ -84,13 +84,14 @@ node scripts/install.mjs --verbose { "plugins": { "entries": { - "harborforge-monitor": { + "harbor-forge": { "enabled": true, "config": { "enabled": true, "backendUrl": "https://monitor.hangman-lab.top", "identifier": "my-server-01", "apiKey": "your-api-key-here", + "monitorPort": 9100, "reportIntervalSec": 30, "httpFallbackIntervalSec": 60, "logLevel": "info" @@ -115,10 +116,22 @@ openclaw gateway restart | `backendUrl` | string | `https://monitor.hangman-lab.top` | Monitor 后端地址 | | `identifier` | string | 自动检测 hostname | 服务器标识符 | | `apiKey` | string | 必填 | HarborForge Monitor 生成的服务器 API Key | +| `monitorPort` | number | `9100`(示例) | 本地桥接端口;插件通过 `127.0.0.1:` 与 HarborForge.Monitor 通信 | | `reportIntervalSec` | number | `30` | 报告间隔(秒) | | `httpFallbackIntervalSec` | number | `60` | HTTP 回退间隔(秒) | | `logLevel` | string | `"info"` | 日志级别: debug/info/warn/error | +## Monitor 本地桥接 + +当插件配置了 `monitorPort`,并且 HarborForge.Monitor 也以相同的 `MONITOR_PORT` 启动时: + +- Monitor 会在 `127.0.0.1:` 暴露本地桥接服务 +- 插件可探测 `GET /health` +- 插件工具 `harborforge_monitor_telemetry` 可读取 `GET /telemetry` +- 若桥接端口未配置或不可达,插件仍然正常工作,只是不会拿到 Monitor 的宿主机遥测补充数据 + +这条链路是**可选增强**,不是插件或 Monitor 心跳上报的前置依赖。 + ## 收集的指标 ### 系统指标 diff --git a/plugin/core/live-config.d.ts b/plugin/core/live-config.d.ts index 244eb91..e455432 100644 --- a/plugin/core/live-config.d.ts +++ b/plugin/core/live-config.d.ts @@ -3,6 +3,7 @@ export interface HarborForgeMonitorConfig { backendUrl?: string; identifier?: string; apiKey?: string; + monitorPort?: number; reportIntervalSec?: number; httpFallbackIntervalSec?: number; logLevel?: 'debug' | 'info' | 'warn' | 'error'; diff --git a/plugin/core/live-config.d.ts.map b/plugin/core/live-config.d.ts.map index 9188b63..26a5054 100644 --- a/plugin/core/live-config.d.ts.map +++ b/plugin/core/live-config.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"live-config.d.ts","sourceRoot":"","sources":["live-config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAED,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,qBAAqB,EAC1B,QAAQ,EAAE,wBAAwB,GACjC,wBAAwB,CAqB1B"} \ No newline at end of file +{"version":3,"file":"live-config.d.ts","sourceRoot":"","sources":["live-config.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;CAChD;AAED,UAAU,qBAAqB;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,qBAAqB,EAC1B,QAAQ,EAAE,wBAAwB,GACjC,wBAAwB,CAqB1B"} \ No newline at end of file diff --git a/plugin/core/live-config.js b/plugin/core/live-config.js index a4e990e..b3201d3 100644 --- a/plugin/core/live-config.js +++ b/plugin/core/live-config.js @@ -5,7 +5,7 @@ function getLivePluginConfig(api, fallback) { const root = api.config || {}; const plugins = root.plugins || {}; const entries = plugins.entries || {}; - const entry = entries['harborforge-monitor'] || {}; + const entry = entries['harbor-forge'] || {}; const cfg = entry.config || {}; if (Object.keys(cfg).length > 0 || Object.keys(entry).length > 0) { return { diff --git a/plugin/core/live-config.js.map b/plugin/core/live-config.js.map index 197010e..650d6ac 100644 --- a/plugin/core/live-config.js.map +++ b/plugin/core/live-config.js.map @@ -1 +1 @@ -{"version":3,"file":"live-config.js","sourceRoot":"","sources":["live-config.ts"],"names":[],"mappings":";;AAcA,kDAwBC;AAxBD,SAAgB,mBAAmB,CACjC,GAA0B,EAC1B,QAAkC;IAElC,MAAM,IAAI,GAAI,GAAG,CAAC,MAAkC,IAAI,EAAE,CAAC;IAC3D,MAAM,OAAO,GAAI,IAAI,CAAC,OAAmC,IAAI,EAAE,CAAC;IAChE,MAAM,OAAO,GAAI,OAAO,CAAC,OAAmC,IAAI,EAAE,CAAC;IACnE,MAAM,KAAK,GAAI,OAAO,CAAC,qBAAqB,CAA6B,IAAI,EAAE,CAAC;IAChF,MAAM,GAAG,GAAI,KAAK,CAAC,MAAkC,IAAI,EAAE,CAAC;IAE5D,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjE,OAAO;YACL,GAAG,QAAQ;YACX,GAAG,GAAG;YACN,OAAO,EACL,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS;gBAC9B,CAAC,CAAC,GAAG,CAAC,OAAO;gBACb,CAAC,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS;oBAClC,CAAC,CAAC,KAAK,CAAC,OAAO;oBACf,CAAC,CAAC,QAAQ,CAAC,OAAO;SACG,CAAC;IAChC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"} \ No newline at end of file +{"version":3,"file":"live-config.js","sourceRoot":"","sources":["live-config.ts"],"names":[],"mappings":";;AAeA,kDAwBC;AAxBD,SAAgB,mBAAmB,CACjC,GAA0B,EAC1B,QAAkC;IAElC,MAAM,IAAI,GAAI,GAAG,CAAC,MAAkC,IAAI,EAAE,CAAC;IAC3D,MAAM,OAAO,GAAI,IAAI,CAAC,OAAmC,IAAI,EAAE,CAAC;IAChE,MAAM,OAAO,GAAI,OAAO,CAAC,OAAmC,IAAI,EAAE,CAAC;IACnE,MAAM,KAAK,GAAI,OAAO,CAAC,cAAc,CAA6B,IAAI,EAAE,CAAC;IACzE,MAAM,GAAG,GAAI,KAAK,CAAC,MAAkC,IAAI,EAAE,CAAC;IAE5D,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjE,OAAO;YACL,GAAG,QAAQ;YACX,GAAG,GAAG;YACN,OAAO,EACL,OAAO,GAAG,CAAC,OAAO,KAAK,SAAS;gBAC9B,CAAC,CAAC,GAAG,CAAC,OAAO;gBACb,CAAC,CAAC,OAAO,KAAK,CAAC,OAAO,KAAK,SAAS;oBAClC,CAAC,CAAC,KAAK,CAAC,OAAO;oBACf,CAAC,CAAC,QAAQ,CAAC,OAAO;SACG,CAAC;IAChC,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"} \ No newline at end of file diff --git a/plugin/core/live-config.ts b/plugin/core/live-config.ts index 86f60ef..a3bc3c3 100644 --- a/plugin/core/live-config.ts +++ b/plugin/core/live-config.ts @@ -3,6 +3,7 @@ export interface HarborForgeMonitorConfig { backendUrl?: string; identifier?: string; apiKey?: string; + monitorPort?: number; reportIntervalSec?: number; httpFallbackIntervalSec?: number; logLevel?: 'debug' | 'info' | 'warn' | 'error'; @@ -19,7 +20,7 @@ export function getLivePluginConfig( const root = (api.config as Record) || {}; const plugins = (root.plugins as Record) || {}; const entries = (plugins.entries as Record) || {}; - const entry = (entries['harborforge-monitor'] as Record) || {}; + const entry = (entries['harbor-forge'] as Record) || {}; const cfg = (entry.config as Record) || {}; if (Object.keys(cfg).length > 0 || Object.keys(entry).length > 0) { diff --git a/plugin/core/monitor-bridge.d.ts b/plugin/core/monitor-bridge.d.ts new file mode 100644 index 0000000..84f626f --- /dev/null +++ b/plugin/core/monitor-bridge.d.ts @@ -0,0 +1,41 @@ +/** + * Monitor Bridge Client + * + * Queries the local HarborForge.Monitor bridge endpoint on MONITOR_PORT + * to enrich plugin telemetry with host/hardware data. + * + * If the bridge is unreachable, all methods return null gracefully — + * the plugin continues to function without Monitor data. + */ +export interface MonitorHealth { + status: string; + monitor_version: string; + identifier: string; +} +export interface MonitorTelemetryResponse { + status: string; + monitor_version: string; + identifier: string; + telemetry?: { + identifier: string; + plugin_version: string; + cpu_pct: number; + mem_pct: number; + disk_pct: number; + swap_pct: number; + load_avg: number[]; + uptime_seconds: number; + nginx_installed: boolean; + nginx_sites: string[]; + }; + last_updated?: string; +} +export declare class MonitorBridgeClient { + private baseUrl; + private timeoutMs; + constructor(port: number, timeoutMs?: number); + health(): Promise; + telemetry(): Promise; + private fetchJson; +} +//# sourceMappingURL=monitor-bridge.d.ts.map \ No newline at end of file diff --git a/plugin/core/monitor-bridge.d.ts.map b/plugin/core/monitor-bridge.d.ts.map new file mode 100644 index 0000000..bffa006 --- /dev/null +++ b/plugin/core/monitor-bridge.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"monitor-bridge.d.ts","sourceRoot":"","sources":["monitor-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE;QACV,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,OAAO,CAAC;QACzB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;IACF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;gBAEd,IAAI,EAAE,MAAM,EAAE,SAAS,SAAO;IAKpC,MAAM,IAAI,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAIvC,SAAS,IAAI,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC;YAI7C,SAAS;CAgBxB"} \ No newline at end of file diff --git a/plugin/core/monitor-bridge.js b/plugin/core/monitor-bridge.js new file mode 100644 index 0000000..5657649 --- /dev/null +++ b/plugin/core/monitor-bridge.js @@ -0,0 +1,44 @@ +"use strict"; +/** + * Monitor Bridge Client + * + * Queries the local HarborForge.Monitor bridge endpoint on MONITOR_PORT + * to enrich plugin telemetry with host/hardware data. + * + * If the bridge is unreachable, all methods return null gracefully — + * the plugin continues to function without Monitor data. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.MonitorBridgeClient = void 0; +class MonitorBridgeClient { + baseUrl; + timeoutMs; + constructor(port, timeoutMs = 3000) { + this.baseUrl = `http://127.0.0.1:${port}`; + this.timeoutMs = timeoutMs; + } + async health() { + return this.fetchJson('/health'); + } + async telemetry() { + return this.fetchJson('/telemetry'); + } + async fetchJson(path) { + try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), this.timeoutMs); + const response = await fetch(`${this.baseUrl}${path}`, { + signal: controller.signal, + }); + clearTimeout(timeout); + if (!response.ok) + return null; + return (await response.json()); + } + catch { + return null; + } + } +} +exports.MonitorBridgeClient = MonitorBridgeClient; +//# sourceMappingURL=monitor-bridge.js.map \ No newline at end of file diff --git a/plugin/core/monitor-bridge.js.map b/plugin/core/monitor-bridge.js.map new file mode 100644 index 0000000..cdeccf0 --- /dev/null +++ b/plugin/core/monitor-bridge.js.map @@ -0,0 +1 @@ +{"version":3,"file":"monitor-bridge.js","sourceRoot":"","sources":["monitor-bridge.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AA2BH,MAAa,mBAAmB;IACtB,OAAO,CAAS;IAChB,SAAS,CAAS;IAE1B,YAAY,IAAY,EAAE,SAAS,GAAG,IAAI;QACxC,IAAI,CAAC,OAAO,GAAG,oBAAoB,IAAI,EAAE,CAAC;QAC1C,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,MAAM;QACV,OAAO,IAAI,CAAC,SAAS,CAAgB,SAAS,CAAC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,SAAS;QACb,OAAO,IAAI,CAAC,SAAS,CAA2B,YAAY,CAAC,CAAC;IAChE,CAAC;IAEO,KAAK,CAAC,SAAS,CAAI,IAAY;QACrC,IAAI,CAAC;YACH,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;YACzC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YAErE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,EAAE;gBACrD,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,YAAY,CAAC,OAAO,CAAC,CAAC;YAEtB,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YAC9B,OAAO,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAM,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAjCD,kDAiCC"} \ No newline at end of file diff --git a/plugin/core/monitor-bridge.ts b/plugin/core/monitor-bridge.ts new file mode 100644 index 0000000..1da54e0 --- /dev/null +++ b/plugin/core/monitor-bridge.ts @@ -0,0 +1,69 @@ +/** + * Monitor Bridge Client + * + * Queries the local HarborForge.Monitor bridge endpoint on MONITOR_PORT + * to enrich plugin telemetry with host/hardware data. + * + * If the bridge is unreachable, all methods return null gracefully — + * the plugin continues to function without Monitor data. + */ + +export interface MonitorHealth { + status: string; + monitor_version: string; + identifier: string; +} + +export interface MonitorTelemetryResponse { + status: string; + monitor_version: string; + identifier: string; + telemetry?: { + identifier: string; + plugin_version: string; + cpu_pct: number; + mem_pct: number; + disk_pct: number; + swap_pct: number; + load_avg: number[]; + uptime_seconds: number; + nginx_installed: boolean; + nginx_sites: string[]; + }; + last_updated?: string; +} + +export class MonitorBridgeClient { + private baseUrl: string; + private timeoutMs: number; + + constructor(port: number, timeoutMs = 3000) { + this.baseUrl = `http://127.0.0.1:${port}`; + this.timeoutMs = timeoutMs; + } + + async health(): Promise { + return this.fetchJson('/health'); + } + + async telemetry(): Promise { + return this.fetchJson('/telemetry'); + } + + private async fetchJson(path: string): Promise { + try { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), this.timeoutMs); + + const response = await fetch(`${this.baseUrl}${path}`, { + signal: controller.signal, + }); + clearTimeout(timeout); + + if (!response.ok) return null; + return (await response.json()) as T; + } catch { + return null; + } + } +} diff --git a/plugin/index.ts b/plugin/index.ts index 5fc2481..7781950 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -10,6 +10,7 @@ */ import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os'; import { getLivePluginConfig, type HarborForgeMonitorConfig } from './core/live-config'; +import { MonitorBridgeClient } from './core/monitor-bridge'; interface PluginAPI { logger: { @@ -50,6 +51,16 @@ export default { return getLivePluginConfig(api, baseConfig); } + /** + * Get the monitor bridge client if monitorPort is configured. + */ + function getBridgeClient(): MonitorBridgeClient | null { + const live = resolveConfig() as any; + const port = live.monitorPort; + if (!port || port <= 0) return null; + return new MonitorBridgeClient(port); + } + /** * Collect current system telemetry snapshot. * This data is exposed to the Monitor bridge when it queries the plugin. @@ -99,6 +110,16 @@ export default { }, async execute() { const live = resolveConfig(); + const bridgeClient = getBridgeClient(); + let monitorBridge = null; + + if (bridgeClient) { + const health = await bridgeClient.health(); + monitorBridge = health + ? { connected: true, ...health } + : { connected: false, error: 'Monitor bridge unreachable' }; + } + return { enabled: live.enabled !== false, config: { @@ -108,6 +129,7 @@ export default { reportIntervalSec: live.reportIntervalSec, hasApiKey: Boolean(live.apiKey), }, + monitorBridge, telemetry: collectTelemetry(), }; }, @@ -126,6 +148,33 @@ export default { }, })); + // Tool: query Monitor bridge for host hardware telemetry + api.registerTool(() => ({ + name: 'harborforge_monitor_telemetry', + description: 'Query HarborForge Monitor bridge for host hardware telemetry (CPU, memory, disk, etc.)', + parameters: { + type: 'object', + properties: {}, + }, + async execute() { + const bridgeClient = getBridgeClient(); + if (!bridgeClient) { + return { + error: 'Monitor bridge not configured (monitorPort not set or 0)', + }; + } + + const data = await bridgeClient.telemetry(); + if (!data) { + return { + error: 'Monitor bridge unreachable', + }; + } + + return data; + }, + })); + logger.info('HarborForge plugin registered (id: harbor-forge)'); }, };