From 038862ef8cb95ceb1c8e2d548693c2b39357155e Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 00:44:33 +0000 Subject: [PATCH] refactor: manage monitor via gateway hooks --- .gitignore | 9 +- plugin/calendar/calendar-bridge.d.ts | 124 ---- plugin/calendar/calendar-bridge.d.ts.map | 1 - plugin/calendar/calendar-bridge.js | 214 ------- plugin/calendar/calendar-bridge.js.map | 1 - plugin/calendar/calendar-bridge.ts | 7 +- plugin/calendar/index.d.ts | 33 - plugin/calendar/index.d.ts.map | 1 - plugin/calendar/index.js | 49 -- plugin/calendar/index.js.map | 1 - plugin/calendar/scheduler.d.ts | 235 ------- plugin/calendar/scheduler.d.ts.map | 1 - plugin/calendar/scheduler.js | 755 ----------------------- plugin/calendar/scheduler.js.map | 1 - plugin/calendar/types.d.ts | 171 ----- plugin/calendar/types.d.ts.map | 1 - plugin/calendar/types.js | 58 -- plugin/calendar/types.js.map | 1 - plugin/core/config.ts | 35 ++ plugin/core/live-config.d.ts | 17 - plugin/core/live-config.d.ts.map | 1 - plugin/core/live-config.js | 32 - plugin/core/live-config.js.map | 1 - plugin/core/live-config.ts | 52 -- plugin/core/managed-monitor.ts | 51 ++ plugin/core/monitor-bridge.d.ts | 55 -- plugin/core/monitor-bridge.d.ts.map | 1 - plugin/core/monitor-bridge.js | 66 -- plugin/core/monitor-bridge.js.map | 1 - plugin/hooks/gateway-start.ts | 33 + plugin/hooks/gateway-stop.ts | 23 + plugin/index.ts | 60 +- plugin/openclaw.plugin.json | 6 +- plugin/package.json | 5 +- plugin/tsconfig.json | 7 +- scripts/install.mjs | 67 +- 36 files changed, 244 insertions(+), 1932 deletions(-) delete mode 100644 plugin/calendar/calendar-bridge.d.ts delete mode 100644 plugin/calendar/calendar-bridge.d.ts.map delete mode 100644 plugin/calendar/calendar-bridge.js delete mode 100644 plugin/calendar/calendar-bridge.js.map delete mode 100644 plugin/calendar/index.d.ts delete mode 100644 plugin/calendar/index.d.ts.map delete mode 100644 plugin/calendar/index.js delete mode 100644 plugin/calendar/index.js.map delete mode 100644 plugin/calendar/scheduler.d.ts delete mode 100644 plugin/calendar/scheduler.d.ts.map delete mode 100644 plugin/calendar/scheduler.js delete mode 100644 plugin/calendar/scheduler.js.map delete mode 100644 plugin/calendar/types.d.ts delete mode 100644 plugin/calendar/types.d.ts.map delete mode 100644 plugin/calendar/types.js delete mode 100644 plugin/calendar/types.js.map create mode 100644 plugin/core/config.ts delete mode 100644 plugin/core/live-config.d.ts delete mode 100644 plugin/core/live-config.d.ts.map delete mode 100644 plugin/core/live-config.js delete mode 100644 plugin/core/live-config.js.map delete mode 100644 plugin/core/live-config.ts create mode 100644 plugin/core/managed-monitor.ts delete mode 100644 plugin/core/monitor-bridge.d.ts delete mode 100644 plugin/core/monitor-bridge.d.ts.map delete mode 100644 plugin/core/monitor-bridge.js delete mode 100644 plugin/core/monitor-bridge.js.map create mode 100644 plugin/hooks/gateway-start.ts create mode 100644 plugin/hooks/gateway-stop.ts diff --git a/.gitignore b/.gitignore index 3146d6e..9b618a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ plugin/node_modules/ -plugin/*.js -plugin/*.js.map -plugin/*.d.ts -plugin/*.d.ts.map +plugin/dist/ +plugin/**/*.js +plugin/**/*.js.map +plugin/**/*.d.ts +plugin/**/*.d.ts.map diff --git a/plugin/calendar/calendar-bridge.d.ts b/plugin/calendar/calendar-bridge.d.ts deleted file mode 100644 index cae6d50..0000000 --- a/plugin/calendar/calendar-bridge.d.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * HarborForge Calendar Bridge Client - * - * PLG-CAL-001: Handles HTTP communication between the OpenClaw plugin - * and the HarborForge backend for Calendar heartbeat and slot updates. - * - * Request authentication: - * • X-Agent-ID header — set to process.env.AGENT_ID - * • X-Claw-Identifier header — set to the server's claw_identifier - * (from plugin config or hostname fallback) - * - * Base URL: - * Derived from plugin config: backendUrl + "/calendar" - * Default backendUrl: "https://monitor.hangman-lab.top" - * - * Endpoints used: - * GET /calendar/agent/heartbeat — fetch pending slots - * PATCH /calendar/slots/{id}/agent-update — update real slot status - * PATCH /calendar/slots/virtual/{vid}/agent-update — update virtual slot status - * - * References: - * • NEXT_WAVE_DEV_DIRECTION.md §6.1 (Heartbeat flow) - * • HarborForge.Backend/app/services/agent_heartbeat.py (BE-AGT-001) - */ -import { CalendarHeartbeatResponse, CalendarSlotResponse, SlotAgentUpdate } from './types'; -export interface CalendarBridgeConfig { - /** HarborForge backend base URL (e.g. "https://monitor.hangman-lab.top") */ - backendUrl: string; - /** Server/claw identifier (from plugin config or hostname fallback) */ - clawIdentifier: string; - /** OpenClaw agent ID ($AGENT_ID), set at agent startup */ - agentId: string; - /** HTTP request timeout in milliseconds (default: 5000) */ - timeoutMs?: number; -} -export declare class CalendarBridgeClient { - private baseUrl; - private config; - private timeoutMs; - constructor(config: CalendarBridgeConfig); - /** - * Fetch today's pending calendar slots for this agent. - * - * Heartbeat flow (§6.1): - * 1. Plugin sends heartbeat every minute - * 2. Backend returns slots where status is NotStarted or Deferred - * AND scheduled_at <= now - * 3. Plugin selects highest-priority slot (if any) - * 4. For remaining slots, plugin sets status = Deferred + priority += 1 - * - * @returns CalendarHeartbeatResponse or null if the backend is unreachable - */ - heartbeat(): Promise; - /** - * Update a real (materialized) slot's status after agent execution. - * - * Used by the plugin to report: - * - Slot attended (attended=true, started_at=now, status=Ongoing) - * - Slot finished (actual_duration set, status=Finished) - * - Slot deferred (status=Deferred, priority += 1) - * - Slot aborted (status=Aborted) - * - * @param slotId Real slot DB id - * @param update Status update payload - * @returns true on success, false on failure - */ - updateSlot(slotId: number, update: SlotAgentUpdate): Promise; - /** - * Update a virtual (plan-generated) slot's status after agent execution. - * - * When updating a virtual slot, the backend first materializes it - * (creates a real TimeSlot row), then applies the update. - * The returned slot will have a real id on subsequent calls. - * - * @param virtualId Virtual slot id in format "plan-{plan_id}-{date}" - * @param update Status update payload - * @returns Updated CalendarSlotResponse on success, null on failure - */ - updateVirtualSlot(virtualId: string, update: SlotAgentUpdate): Promise; - /** - * Report the agent's current runtime status to HarborForge. - * - * Used to push agent status transitions: - * idle → busy / on_call (when starting a slot) - * busy / on_call → idle (when finishing a slot) - * → exhausted (on rate-limit / billing error, with recovery_at) - * → offline (after 2 min with no heartbeat) - * - * @param status New agent status - * @param recoveryAt ISO timestamp for expected Exhausted recovery (optional) - * @param exhaustReason "rate_limit" | "billing" (required if status=exhausted) - */ - reportAgentStatus(params: { - status: 'idle' | 'on_call' | 'busy' | 'exhausted' | 'offline'; - recoveryAt?: string; - exhaustReason?: 'rate_limit' | 'billing'; - }): Promise; - private fetchJson; - private postBoolean; -} -export interface CalendarPluginConfig { - /** Backend URL for calendar API (overrides monitor backendUrl) */ - calendarBackendUrl?: string; - /** Server identifier (overrides auto-detected hostname) */ - identifier?: string; - /** Agent ID from OpenClaw ($AGENT_ID) */ - agentId: string; - /** HTTP timeout for calendar API calls (default: 5000) */ - timeoutMs?: number; -} -/** - * Build a CalendarBridgeClient from the OpenClaw plugin API context. - * - * @param api OpenClaw plugin API (register() receives this) - * @param fallbackUrl Fallback backend URL if not configured - * @param agentId $AGENT_ID from OpenClaw environment - */ -export declare function createCalendarBridgeClient(api: { - config?: Record; - logger?: { - debug?: (...args: unknown[]) => void; - }; -}, fallbackUrl: string, agentId: string): CalendarBridgeClient; -//# sourceMappingURL=calendar-bridge.d.ts.map \ No newline at end of file diff --git a/plugin/calendar/calendar-bridge.d.ts.map b/plugin/calendar/calendar-bridge.d.ts.map deleted file mode 100644 index d83c24e..0000000 --- a/plugin/calendar/calendar-bridge.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"calendar-bridge.d.ts","sourceRoot":"","sources":["calendar-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAEL,yBAAyB,EACzB,oBAAoB,EACpB,eAAe,EAEhB,MAAM,SAAS,CAAC;AAEjB,MAAM,WAAW,oBAAoB;IACnC,4EAA4E;IAC5E,UAAU,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,oBAAoB;IASxC;;;;;;;;;;;OAWG;IACG,SAAS,IAAI,OAAO,CAAC,yBAAyB,GAAG,IAAI,CAAC;IAwB5D;;;;;;;;;;;;OAYG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,OAAO,CAAC;IAK3E;;;;;;;;;;OAUG;IACG,iBAAiB,CACrB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC;IAkBvC;;;;;;;;;;;;OAYG;IACG,iBAAiB,CAAC,MAAM,EAAE;QAC9B,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;QAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,aAAa,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC;KAC1C,GAAG,OAAO,CAAC,OAAO,CAAC;YAcN,SAAS;YAsBT,WAAW;CAsB1B;AASD,MAAM,WAAW,oBAAoB;IACnC,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAC;IAChB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACxC,GAAG,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;KAAE,CAAA;CAAE,EAC5F,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,oBAAoB,CActB"} \ No newline at end of file diff --git a/plugin/calendar/calendar-bridge.js b/plugin/calendar/calendar-bridge.js deleted file mode 100644 index 3ac6578..0000000 --- a/plugin/calendar/calendar-bridge.js +++ /dev/null @@ -1,214 +0,0 @@ -"use strict"; -/** - * HarborForge Calendar Bridge Client - * - * PLG-CAL-001: Handles HTTP communication between the OpenClaw plugin - * and the HarborForge backend for Calendar heartbeat and slot updates. - * - * Request authentication: - * • X-Agent-ID header — set to process.env.AGENT_ID - * • X-Claw-Identifier header — set to the server's claw_identifier - * (from plugin config or hostname fallback) - * - * Base URL: - * Derived from plugin config: backendUrl + "/calendar" - * Default backendUrl: "https://monitor.hangman-lab.top" - * - * Endpoints used: - * GET /calendar/agent/heartbeat — fetch pending slots - * PATCH /calendar/slots/{id}/agent-update — update real slot status - * PATCH /calendar/slots/virtual/{vid}/agent-update — update virtual slot status - * - * References: - * • NEXT_WAVE_DEV_DIRECTION.md §6.1 (Heartbeat flow) - * • HarborForge.Backend/app/services/agent_heartbeat.py (BE-AGT-001) - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CalendarBridgeClient = void 0; -exports.createCalendarBridgeClient = createCalendarBridgeClient; -class CalendarBridgeClient { - baseUrl; - config; - timeoutMs; - constructor(config) { - this.baseUrl = config.backendUrl.replace(/\/$/, ''); // strip trailing slash - this.config = { - timeoutMs: 5000, - ...config, - }; - this.timeoutMs = this.config.timeoutMs; - } - /** - * Fetch today's pending calendar slots for this agent. - * - * Heartbeat flow (§6.1): - * 1. Plugin sends heartbeat every minute - * 2. Backend returns slots where status is NotStarted or Deferred - * AND scheduled_at <= now - * 3. Plugin selects highest-priority slot (if any) - * 4. For remaining slots, plugin sets status = Deferred + priority += 1 - * - * @returns CalendarHeartbeatResponse or null if the backend is unreachable - */ - async heartbeat() { - const url = `${this.baseUrl}/calendar/agent/heartbeat`; - const body = { - claw_identifier: this.config.clawIdentifier, - agent_id: this.config.agentId, - }; - try { - const response = await this.fetchJson(url, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'X-Agent-ID': this.config.agentId, - 'X-Claw-Identifier': this.config.clawIdentifier, - }, - body: JSON.stringify(body), - }); - return response; - } - catch (err) { - // Non-fatal: backend unreachable — return null, plugin continues - return null; - } - } - /** - * Update a real (materialized) slot's status after agent execution. - * - * Used by the plugin to report: - * - Slot attended (attended=true, started_at=now, status=Ongoing) - * - Slot finished (actual_duration set, status=Finished) - * - Slot deferred (status=Deferred, priority += 1) - * - Slot aborted (status=Aborted) - * - * @param slotId Real slot DB id - * @param update Status update payload - * @returns true on success, false on failure - */ - async updateSlot(slotId, update) { - const url = `${this.baseUrl}/calendar/slots/${slotId}/agent-update`; - return this.postBoolean(url, update); - } - /** - * Update a virtual (plan-generated) slot's status after agent execution. - * - * When updating a virtual slot, the backend first materializes it - * (creates a real TimeSlot row), then applies the update. - * The returned slot will have a real id on subsequent calls. - * - * @param virtualId Virtual slot id in format "plan-{plan_id}-{date}" - * @param update Status update payload - * @returns Updated CalendarSlotResponse on success, null on failure - */ - async updateVirtualSlot(virtualId, update) { - const url = `${this.baseUrl}/calendar/slots/virtual/${encodeURIComponent(virtualId)}/agent-update`; - try { - const response = await this.fetchJson(url, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - 'X-Agent-ID': this.config.agentId, - 'X-Claw-Identifier': this.config.clawIdentifier, - }, - body: JSON.stringify(update), - }); - return response?.slot ?? null; - } - catch { - return null; - } - } - /** - * Report the agent's current runtime status to HarborForge. - * - * Used to push agent status transitions: - * idle → busy / on_call (when starting a slot) - * busy / on_call → idle (when finishing a slot) - * → exhausted (on rate-limit / billing error, with recovery_at) - * → offline (after 2 min with no heartbeat) - * - * @param status New agent status - * @param recoveryAt ISO timestamp for expected Exhausted recovery (optional) - * @param exhaustReason "rate_limit" | "billing" (required if status=exhausted) - */ - async reportAgentStatus(params) { - const url = `${this.baseUrl}/calendar/agent/status`; - const body = { - agent_id: this.config.agentId, - claw_identifier: this.config.clawIdentifier, - ...params, - }; - return this.postBoolean(url, body); - } - // ------------------------------------------------------------------------- - // Internal helpers - // ------------------------------------------------------------------------- - async fetchJson(url, init) { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), this.timeoutMs); - try { - const response = await fetch(url, { - ...init, - signal: controller.signal, - }); - clearTimeout(timeout); - if (!response.ok) - return null; - return (await response.json()); - } - catch { - clearTimeout(timeout); - return null; - } - } - async postBoolean(url, body) { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), this.timeoutMs); - try { - const response = await fetch(url, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Agent-ID': this.config.agentId, - 'X-Claw-Identifier': this.config.clawIdentifier, - }, - body: JSON.stringify(body), - signal: controller.signal, - }); - clearTimeout(timeout); - return response.ok; - } - catch { - clearTimeout(timeout); - return false; - } - } -} -exports.CalendarBridgeClient = CalendarBridgeClient; -// --------------------------------------------------------------------------- -// Utility: build CalendarBridgeConfig from plugin API context -// --------------------------------------------------------------------------- -const os_1 = require("os"); -const live_config_1 = require("../core/live-config"); -/** - * Build a CalendarBridgeClient from the OpenClaw plugin API context. - * - * @param api OpenClaw plugin API (register() receives this) - * @param fallbackUrl Fallback backend URL if not configured - * @param agentId $AGENT_ID from OpenClaw environment - */ -function createCalendarBridgeClient(api, fallbackUrl, agentId) { - const baseConfig = (0, live_config_1.getLivePluginConfig)(api, { - backendUrl: fallbackUrl, - identifier: (0, os_1.hostname)(), - }); - const clawIdentifier = baseConfig.identifier || (0, os_1.hostname)(); - return new CalendarBridgeClient({ - backendUrl: baseConfig.backendUrl || fallbackUrl, - clawIdentifier, - agentId, - timeoutMs: 5000, - }); -} -//# sourceMappingURL=calendar-bridge.js.map \ No newline at end of file diff --git a/plugin/calendar/calendar-bridge.js.map b/plugin/calendar/calendar-bridge.js.map deleted file mode 100644 index f62de25..0000000 --- a/plugin/calendar/calendar-bridge.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"calendar-bridge.js","sourceRoot":"","sources":["calendar-bridge.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugin/calendar/calendar-bridge.ts b/plugin/calendar/calendar-bridge.ts index 71ffce1..07644ce 100644 --- a/plugin/calendar/calendar-bridge.ts +++ b/plugin/calendar/calendar-bridge.ts @@ -224,7 +224,7 @@ export class CalendarBridgeClient { // --------------------------------------------------------------------------- import { hostname } from 'os'; -import { getLivePluginConfig, type HarborForgeMonitorConfig } from '../core/live-config'; +import { getPluginConfig } from '../core/config'; export interface CalendarPluginConfig { /** Backend URL for calendar API (overrides monitor backendUrl) */ @@ -249,10 +249,7 @@ export function createCalendarBridgeClient( fallbackUrl: string, agentId: string ): CalendarBridgeClient { - const baseConfig = getLivePluginConfig(api, { - backendUrl: fallbackUrl, - identifier: hostname(), - } as HarborForgeMonitorConfig) as HarborForgeMonitorConfig; + const baseConfig = getPluginConfig(api as any); const clawIdentifier = baseConfig.identifier || hostname(); diff --git a/plugin/calendar/index.d.ts b/plugin/calendar/index.d.ts deleted file mode 100644 index 583f0fc..0000000 --- a/plugin/calendar/index.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * HarborForge Calendar — Plugin Module - * - * PLG-CAL-001: Calendar heartbeat request/response format definition. - * PLG-CAL-002: Plugin-side slot execution scheduler and agent wakeup. - * - * Exports: - * • Types for heartbeat request/response and slot update - * • CalendarBridgeClient — HTTP client for backend communication - * • createCalendarBridgeClient — factory from plugin API context - * • CalendarScheduler — manages periodic heartbeat and slot execution - * • createCalendarScheduler — factory for scheduler - * • AgentWakeContext — context passed to agent when waking - * - * Usage in plugin/index.ts: - * import { createCalendarBridgeClient, createCalendarScheduler } from './calendar'; - * - * const agentId = process.env.AGENT_ID || 'unknown'; - * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); - * - * const scheduler = createCalendarScheduler({ - * bridge: calendar, - * getAgentStatus: async () => { ... }, - * wakeAgent: async (context) => { ... }, - * logger: api.logger, - * }); - * - * scheduler.start(); - */ -export * from './types'; -export * from './calendar-bridge'; -export * from './scheduler'; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/plugin/calendar/index.d.ts.map b/plugin/calendar/index.d.ts.map deleted file mode 100644 index 1848e1e..0000000 --- a/plugin/calendar/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC"} \ No newline at end of file diff --git a/plugin/calendar/index.js b/plugin/calendar/index.js deleted file mode 100644 index 914a672..0000000 --- a/plugin/calendar/index.js +++ /dev/null @@ -1,49 +0,0 @@ -"use strict"; -/** - * HarborForge Calendar — Plugin Module - * - * PLG-CAL-001: Calendar heartbeat request/response format definition. - * PLG-CAL-002: Plugin-side slot execution scheduler and agent wakeup. - * - * Exports: - * • Types for heartbeat request/response and slot update - * • CalendarBridgeClient — HTTP client for backend communication - * • createCalendarBridgeClient — factory from plugin API context - * • CalendarScheduler — manages periodic heartbeat and slot execution - * • createCalendarScheduler — factory for scheduler - * • AgentWakeContext — context passed to agent when waking - * - * Usage in plugin/index.ts: - * import { createCalendarBridgeClient, createCalendarScheduler } from './calendar'; - * - * const agentId = process.env.AGENT_ID || 'unknown'; - * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); - * - * const scheduler = createCalendarScheduler({ - * bridge: calendar, - * getAgentStatus: async () => { ... }, - * wakeAgent: async (context) => { ... }, - * logger: api.logger, - * }); - * - * scheduler.start(); - */ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -__exportStar(require("./types"), exports); -__exportStar(require("./calendar-bridge"), exports); -__exportStar(require("./scheduler"), exports); -//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/plugin/calendar/index.js.map b/plugin/calendar/index.js.map deleted file mode 100644 index 802d83b..0000000 --- a/plugin/calendar/index.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;;;;;;;;;;;;;;AAEH,0CAAwB;AACxB,oDAAkC;AAClC,8CAA4B"} \ No newline at end of file diff --git a/plugin/calendar/scheduler.d.ts b/plugin/calendar/scheduler.d.ts deleted file mode 100644 index 5a89747..0000000 --- a/plugin/calendar/scheduler.d.ts +++ /dev/null @@ -1,235 +0,0 @@ -/** - * HarborForge Calendar Scheduler - * - * PLG-CAL-002: Plugin-side handling for pending slot execution. - * PLG-CAL-004: ScheduledGatewayRestart event handling with state persistence. - * - * Responsibilities: - * - Run calendar heartbeat every minute - * - Detect when agent is Idle and slots are pending - * - Wake agent with task context - * - Handle slot status transitions (attended, ongoing, deferred) - * - Manage agent status transitions (idle → busy/on_call) - * - Persist state on ScheduledGatewayRestart and restore on startup - * - Send final heartbeat before graceful shutdown - * - * Design reference: NEXT_WAVE_DEV_DIRECTION.md §6 (Agent wakeup mechanism) - */ -import { CalendarBridgeClient } from './calendar-bridge'; -import { CalendarSlotResponse, AgentStatusValue } from './types'; -export interface CalendarSchedulerConfig { - /** Calendar bridge client for backend communication */ - bridge: CalendarBridgeClient; - /** Function to get current agent status from backend */ - getAgentStatus: () => Promise; - /** Function to wake/spawn agent with task context */ - wakeAgent: (context: AgentWakeContext) => Promise; - /** Logger instance */ - logger: { - info: (...args: any[]) => void; - error: (...args: any[]) => void; - debug: (...args: any[]) => void; - warn: (...args: any[]) => void; - }; - /** Heartbeat interval in milliseconds (default: 60000) */ - heartbeatIntervalMs?: number; - /** Enable verbose debug logging */ - debug?: boolean; - /** Directory for state persistence (default: plugin data dir) */ - stateDir?: string; -} -/** - * Context passed to agent when waking for slot execution. - * This is the payload the agent receives to understand what to do. - */ -export interface AgentWakeContext { - /** The slot to execute */ - slot: CalendarSlotResponse; - /** Human-readable task description */ - taskDescription: string; - /** Prompt/instructions for the agent */ - prompt: string; - /** Whether this is a virtual slot (needs materialization) */ - isVirtual: boolean; -} -/** - * Current execution state tracked by the scheduler. - */ -interface SchedulerState { - /** Whether scheduler is currently running */ - isRunning: boolean; - /** Currently executing slot (null if idle) */ - currentSlot: CalendarSlotResponse | null; - /** Last heartbeat timestamp */ - lastHeartbeatAt: Date | null; - /** Interval handle for cleanup */ - intervalHandle: ReturnType | null; - /** Set of slot IDs that have been deferred in current session */ - deferredSlotIds: Set; - /** Whether agent is currently processing a slot */ - isProcessing: boolean; - /** Whether a gateway restart is scheduled/pending */ - isRestartPending: boolean; -} -/** - * CalendarScheduler manages the periodic heartbeat and slot execution lifecycle. - */ -export declare class CalendarScheduler { - private config; - private state; - private stateFilePath; - constructor(config: CalendarSchedulerConfig); - /** - * Get default state directory (plugin data directory or temp fallback). - */ - private getDefaultStateDir; - /** - * Persist current state to disk for recovery after restart. - */ - private persistState; - /** - * Restore state from disk if available. - */ - private restoreState; - /** - * Clear persisted state file after successful restore. - */ - private clearPersistedState; - /** - * Send a final heartbeat to the backend before shutdown. - */ - private sendFinalHeartbeat; - /** - * Handle ScheduledGatewayRestart event. - * PLG-CAL-004: Persist state, send final heartbeat, pause scheduled tasks. - */ - private handleScheduledGatewayRestart; - /** - * Start the calendar scheduler. - * Begins periodic heartbeat to check for pending slots. - */ - start(): void; - /** - * Stop the calendar scheduler. - * Cleans up intervals and resets state. - */ - stop(): void; - /** - * Execute a single heartbeat cycle. - * Fetches pending slots and handles execution logic. - */ - runHeartbeat(): Promise; - /** - * Handle slots when agent is not idle. - * Defer all pending slots with priority boost. - */ - private handleNonIdleAgent; - /** - * Handle slots when agent is idle. - * Select highest priority slot and wake agent. - */ - private handleIdleAgent; - /** - * Check if a slot is a ScheduledGatewayRestart system event. - */ - private isScheduledGatewayRestart; - /** - * Execute a slot by waking the agent. - */ - private executeSlot; - /** - * Build the wake context for an agent based on slot details. - */ - private buildWakeContext; - /** - * Build prompt for job-type slots. - */ - private buildJobPrompt; - /** - * Build prompt for system event slots. - */ - private buildSystemPrompt; - /** - * Build prompt for entertainment slots. - */ - private buildEntertainmentPrompt; - /** - * Build generic prompt for slots without specific event data. - */ - private buildGenericPrompt; - /** - * Mark a slot as deferred with priority boost. - */ - private deferSlot; - /** - * Revert a slot to not_started status after failed execution attempt. - */ - private revertSlot; - /** - * Complete the current slot execution. - * Call this when the agent finishes the task. - */ - completeCurrentSlot(actualDurationMinutes: number): Promise; - /** - * Abort the current slot execution. - * Call this when the agent cannot complete the task. - */ - abortCurrentSlot(reason?: string): Promise; - /** - * Pause the current slot execution. - * Call this when the agent needs to temporarily pause. - */ - pauseCurrentSlot(): Promise; - /** - * Resume a paused slot. - */ - resumeCurrentSlot(): Promise; - /** - * Trigger an immediate replanning pass after the current slot lifecycle ends. - * This lets previously deferred/not-started slots compete again as soon as - * the agent becomes idle. - */ - private triggerReplan; - /** - * Get a stable ID for a slot (real or virtual). - */ - private getSlotId; - /** - * Format a Date as ISO time string (HH:MM:SS). - */ - private formatTime; - /** - * Debug logging helper. - */ - private logDebug; - /** - * Get current scheduler state (for introspection). - */ - getState(): Readonly; - /** - * Check if scheduler is running. - */ - isRunning(): boolean; - /** - * Check if currently processing a slot. - */ - isProcessing(): boolean; - /** - * Get the current slot being executed (if any). - */ - getCurrentSlot(): CalendarSlotResponse | null; - /** - * Check if a gateway restart is pending. - */ - isRestartPending(): boolean; - /** - * Get the path to the state file. - */ - getStateFilePath(): string; -} -/** - * Factory function to create a CalendarScheduler from plugin context. - */ -export declare function createCalendarScheduler(config: CalendarSchedulerConfig): CalendarScheduler; -export {}; -//# sourceMappingURL=scheduler.d.ts.map \ No newline at end of file diff --git a/plugin/calendar/scheduler.d.ts.map b/plugin/calendar/scheduler.d.ts.map deleted file mode 100644 index c946280..0000000 --- a/plugin/calendar/scheduler.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["scheduler.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugin/calendar/scheduler.js b/plugin/calendar/scheduler.js deleted file mode 100644 index 794e246..0000000 --- a/plugin/calendar/scheduler.js +++ /dev/null @@ -1,755 +0,0 @@ -"use strict"; -/** - * HarborForge Calendar Scheduler - * - * PLG-CAL-002: Plugin-side handling for pending slot execution. - * PLG-CAL-004: ScheduledGatewayRestart event handling with state persistence. - * - * Responsibilities: - * - Run calendar heartbeat every minute - * - Detect when agent is Idle and slots are pending - * - Wake agent with task context - * - Handle slot status transitions (attended, ongoing, deferred) - * - Manage agent status transitions (idle → busy/on_call) - * - Persist state on ScheduledGatewayRestart and restore on startup - * - Send final heartbeat before graceful shutdown - * - * Design reference: NEXT_WAVE_DEV_DIRECTION.md §6 (Agent wakeup mechanism) - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.CalendarScheduler = void 0; -exports.createCalendarScheduler = createCalendarScheduler; -const fs_1 = require("fs"); -const path_1 = require("path"); -const types_1 = require("./types"); -/** State file name */ -const STATE_FILENAME = 'calendar-scheduler-state.json'; -/** State file version for migration compatibility */ -const STATE_VERSION = 1; -/** - * CalendarScheduler manages the periodic heartbeat and slot execution lifecycle. - */ -class CalendarScheduler { - config; - state; - stateFilePath; - constructor(config) { - this.config = { - heartbeatIntervalMs: 60000, // 1 minute default - debug: false, - stateDir: this.getDefaultStateDir(), - ...config, - }; - this.stateFilePath = (0, path_1.join)(this.config.stateDir, STATE_FILENAME); - this.state = { - isRunning: false, - currentSlot: null, - lastHeartbeatAt: null, - intervalHandle: null, - deferredSlotIds: new Set(), - isProcessing: false, - isRestartPending: false, - }; - // Attempt to restore state from previous persistence - this.restoreState(); - } - /** - * Get default state directory (plugin data directory or temp fallback). - */ - getDefaultStateDir() { - // Try to use the plugin's directory or a standard data location - const candidates = [ - process.env.OPENCLAW_PLUGIN_DATA_DIR, - process.env.HARBORFORGE_PLUGIN_DIR, - (0, path_1.join)(process.cwd(), '.harborforge'), - (0, path_1.join)(process.cwd(), 'data'), - '/tmp/harborforge', - ]; - for (const dir of candidates) { - if (dir) { - try { - if (!(0, fs_1.existsSync)(dir)) { - (0, fs_1.mkdirSync)(dir, { recursive: true }); - } - // Test write access - const testFile = (0, path_1.join)(dir, '.write-test'); - (0, fs_1.writeFileSync)(testFile, '', { flag: 'w' }); - return dir; - } - catch { - continue; - } - } - } - // Fallback to current working directory - return process.cwd(); - } - /** - * Persist current state to disk for recovery after restart. - */ - persistState(reason) { - try { - const persistedState = { - version: STATE_VERSION, - persistedAt: new Date().toISOString(), - reason, - currentSlot: this.state.currentSlot, - deferredSlotIds: Array.from(this.state.deferredSlotIds), - isProcessing: this.state.isProcessing, - agentStatus: null, // Will be determined at restore time - }; - (0, fs_1.writeFileSync)(this.stateFilePath, JSON.stringify(persistedState, null, 2)); - this.config.logger.info(`[PLG-CAL-004] State persisted to ${this.stateFilePath} (reason: ${reason})`); - } - catch (err) { - this.config.logger.error('[PLG-CAL-004] Failed to persist state:', err); - } - } - /** - * Restore state from disk if available. - */ - restoreState() { - try { - if (!(0, fs_1.existsSync)(this.stateFilePath)) { - return; - } - const data = (0, fs_1.readFileSync)(this.stateFilePath, 'utf-8'); - const persisted = JSON.parse(data); - // Validate version - if (persisted.version !== STATE_VERSION) { - this.config.logger.warn(`[PLG-CAL-004] State version mismatch: ${persisted.version} vs ${STATE_VERSION}`); - this.clearPersistedState(); - return; - } - // Restore deferred slot IDs - if (persisted.deferredSlotIds && persisted.deferredSlotIds.length > 0) { - this.state.deferredSlotIds = new Set(persisted.deferredSlotIds); - this.config.logger.info(`[PLG-CAL-004] Restored ${persisted.deferredSlotIds.length} deferred slot(s)`); - } - // If there was a slot in progress, mark it for replanning - if (persisted.isProcessing && persisted.currentSlot) { - this.config.logger.warn(`[PLG-CAL-004] Previous session had in-progress slot: ${this.getSlotId(persisted.currentSlot)}`); - // The slot will be picked up by the next heartbeat and can be resumed or deferred - } - this.config.logger.info(`[PLG-CAL-004] State restored from ${persisted.persistedAt} (reason: ${persisted.reason})`); - // Clear the persisted state after successful restore - this.clearPersistedState(); - } - catch (err) { - this.config.logger.error('[PLG-CAL-004] Failed to restore state:', err); - } - } - /** - * Clear persisted state file after successful restore. - */ - clearPersistedState() { - try { - if ((0, fs_1.existsSync)(this.stateFilePath)) { - // In a real implementation, we might want to archive instead of delete - // For now, we'll just clear the content to mark as processed - (0, fs_1.writeFileSync)(this.stateFilePath, JSON.stringify({ restored: true, at: new Date().toISOString() })); - } - } - catch (err) { - this.config.logger.error('[PLG-CAL-004] Failed to clear persisted state:', err); - } - } - /** - * Send a final heartbeat to the backend before shutdown. - */ - async sendFinalHeartbeat(reason) { - try { - this.config.logger.info(`[PLG-CAL-004] Sending final heartbeat (reason: ${reason})`); - // Send agent status update indicating we're going offline - await this.config.bridge.reportAgentStatus({ status: 'offline' }); - this.config.logger.info('[PLG-CAL-004] Final heartbeat sent successfully'); - } - catch (err) { - this.config.logger.error('[PLG-CAL-004] Failed to send final heartbeat:', err); - } - } - /** - * Handle ScheduledGatewayRestart event. - * PLG-CAL-004: Persist state, send final heartbeat, pause scheduled tasks. - */ - async handleScheduledGatewayRestart(slot) { - this.config.logger.info('[PLG-CAL-004] Handling ScheduledGatewayRestart event'); - // 1. Mark restart as pending to prevent new slot processing - this.state.isRestartPending = true; - // 2. Persist current state - this.persistState('ScheduledGatewayRestart'); - // 3. If there's a current slot, pause it gracefully - if (this.state.isProcessing && this.state.currentSlot) { - this.config.logger.info('[PLG-CAL-004] Pausing current slot before restart'); - await this.pauseCurrentSlot(); - } - // 4. Send final heartbeat - await this.sendFinalHeartbeat('ScheduledGatewayRestart'); - // 5. Stop the scheduler (pause scheduled tasks) - this.config.logger.info('[PLG-CAL-004] Stopping scheduler due to gateway restart'); - this.stop(); - // 6. Mark the slot as finished (since we've handled the restart) - const update = { - status: types_1.SlotStatus.FINISHED, - actual_duration: 0, // Restart preparation doesn't take time - }; - try { - if (slot.id) { - await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - } - } - catch (err) { - this.config.logger.error('[PLG-CAL-004] Failed to mark restart slot as finished:', err); - } - } - /** - * Start the calendar scheduler. - * Begins periodic heartbeat to check for pending slots. - */ - start() { - if (this.state.isRunning) { - this.config.logger.warn('Calendar scheduler already running'); - return; - } - this.state.isRunning = true; - this.state.isRestartPending = false; - this.config.logger.info('Calendar scheduler started'); - // Run initial heartbeat immediately - this.runHeartbeat(); - // Schedule periodic heartbeats - this.state.intervalHandle = setInterval(() => this.runHeartbeat(), this.config.heartbeatIntervalMs); - } - /** - * Stop the calendar scheduler. - * Cleans up intervals and resets state. - */ - stop() { - this.state.isRunning = false; - if (this.state.intervalHandle) { - clearInterval(this.state.intervalHandle); - this.state.intervalHandle = null; - } - this.config.logger.info('Calendar scheduler stopped'); - } - /** - * Execute a single heartbeat cycle. - * Fetches pending slots and handles execution logic. - */ - async runHeartbeat() { - if (!this.state.isRunning) { - return; - } - // Skip heartbeat if restart is pending - if (this.state.isRestartPending) { - this.logDebug('Heartbeat skipped: gateway restart pending'); - return; - } - this.state.lastHeartbeatAt = new Date(); - try { - // Fetch pending slots from backend - const response = await this.config.bridge.heartbeat(); - if (!response) { - this.logDebug('Heartbeat: backend unreachable'); - return; - } - this.logDebug(`Heartbeat: ${response.slots.length} slots pending, agent_status=${response.agent_status}`); - // If agent is not idle, defer all pending slots - if (response.agent_status !== 'idle') { - await this.handleNonIdleAgent(response.slots, response.agent_status); - return; - } - // Agent is idle again - previously deferred slots should become eligible - // for selection in the next planning pass. - if (this.state.deferredSlotIds.size > 0) { - this.logDebug(`Agent returned to idle; clearing ${this.state.deferredSlotIds.size} deferred slot marker(s) for replanning`); - this.state.deferredSlotIds.clear(); - } - // Agent is idle - handle pending slots - await this.handleIdleAgent(response.slots); - } - catch (err) { - this.config.logger.error('Heartbeat error:', err); - } - } - /** - * Handle slots when agent is not idle. - * Defer all pending slots with priority boost. - */ - async handleNonIdleAgent(slots, agentStatus) { - if (slots.length === 0) { - return; - } - this.config.logger.info(`Agent not idle (status=${agentStatus}), deferring ${slots.length} slot(s)`); - for (const slot of slots) { - const slotId = this.getSlotId(slot); - // Skip if already deferred this session - if (this.state.deferredSlotIds.has(slotId)) { - continue; - } - // Mark slot as deferred with priority boost (+1) - await this.deferSlot(slot); - this.state.deferredSlotIds.add(slotId); - } - } - /** - * Handle slots when agent is idle. - * Select highest priority slot and wake agent. - */ - async handleIdleAgent(slots) { - if (slots.length === 0) { - return; - } - // Filter out already deferred slots in this session - const eligibleSlots = slots.filter((s) => !this.state.deferredSlotIds.has(this.getSlotId(s))); - if (eligibleSlots.length === 0) { - this.logDebug('All pending slots have been deferred this session'); - return; - } - // Select highest priority slot (backend already sorts by priority DESC) - const [selectedSlot, ...remainingSlots] = eligibleSlots; - this.config.logger.info(`Selected slot for execution: id=${this.getSlotId(selectedSlot)}, ` + - `type=${selectedSlot.slot_type}, priority=${selectedSlot.priority}`); - // Mark remaining slots as deferred - for (const slot of remainingSlots) { - await this.deferSlot(slot); - this.state.deferredSlotIds.add(this.getSlotId(slot)); - } - // Check if this is a ScheduledGatewayRestart event - if (this.isScheduledGatewayRestart(selectedSlot)) { - await this.handleScheduledGatewayRestart(selectedSlot); - return; - } - // Wake agent to execute selected slot - await this.executeSlot(selectedSlot); - } - /** - * Check if a slot is a ScheduledGatewayRestart system event. - */ - isScheduledGatewayRestart(slot) { - if (slot.event_type !== 'system_event' || !slot.event_data) { - return false; - } - const sysData = slot.event_data; - return sysData.event === 'ScheduledGatewayRestart'; - } - /** - * Execute a slot by waking the agent. - */ - async executeSlot(slot) { - if (this.state.isProcessing) { - this.config.logger.warn('Already processing a slot, deferring new slot'); - await this.deferSlot(slot); - return; - } - this.state.isProcessing = true; - this.state.currentSlot = slot; - try { - // Mark slot as attended and ongoing before waking agent - const update = { - status: types_1.SlotStatus.ONGOING, - started_at: this.formatTime(new Date()), - }; - let updateSuccess; - if (slot.id) { - updateSuccess = await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - const updated = await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - updateSuccess = updated !== null; - // Update slot reference if materialized - if (updated) { - this.state.currentSlot = updated; - } - } - else { - updateSuccess = false; - } - if (!updateSuccess) { - this.config.logger.error('Failed to update slot status before execution'); - this.state.isProcessing = false; - this.state.currentSlot = null; - return; - } - // Report agent status change to backend - const newAgentStatus = slot.slot_type === 'on_call' ? 'on_call' : 'busy'; - await this.config.bridge.reportAgentStatus({ status: newAgentStatus }); - // Build wake context for agent - const wakeContext = this.buildWakeContext(slot); - // Wake the agent - const wakeSuccess = await this.config.wakeAgent(wakeContext); - if (!wakeSuccess) { - this.config.logger.error('Failed to wake agent for slot execution'); - // Revert slot to not_started status - await this.revertSlot(slot); - await this.config.bridge.reportAgentStatus({ status: 'idle' }); - this.state.isProcessing = false; - this.state.currentSlot = null; - await this.triggerReplan('wake failure'); - return; - } - // Note: isProcessing remains true until agent signals completion - // This is handled by external completion callback - } - catch (err) { - this.config.logger.error('Error executing slot:', err); - this.state.isProcessing = false; - this.state.currentSlot = null; - } - } - /** - * Build the wake context for an agent based on slot details. - */ - buildWakeContext(slot) { - const isVirtual = slot.virtual_id !== null; - const slotId = this.getSlotId(slot); - // Build task description based on event type - let taskDescription; - let prompt; - if (slot.event_type === 'job' && slot.event_data) { - const jobData = slot.event_data; - taskDescription = `${jobData.type} ${jobData.code}`; - prompt = this.buildJobPrompt(slot, jobData); - } - else if (slot.event_type === 'system_event' && slot.event_data) { - const sysData = slot.event_data; - taskDescription = `System Event: ${sysData.event}`; - prompt = this.buildSystemPrompt(slot, sysData); - } - else if (slot.event_type === 'entertainment') { - taskDescription = 'Entertainment slot'; - prompt = this.buildEntertainmentPrompt(slot); - } - else { - taskDescription = `Generic ${slot.slot_type} slot`; - prompt = this.buildGenericPrompt(slot); - } - return { - slot, - taskDescription, - prompt, - isVirtual, - }; - } - /** - * Build prompt for job-type slots. - */ - buildJobPrompt(slot, jobData) { - const duration = slot.estimated_duration; - const type = jobData.type; - const code = jobData.code; - return `You have a scheduled ${type} job to work on. - -Task Code: ${code} -Estimated Duration: ${duration} minutes -Slot Type: ${slot.slot_type} -Priority: ${slot.priority} - -Please focus on this task for the allocated time. When you finish or need to pause, -report your progress back to the calendar system. - -Working sessions: ${jobData.working_sessions?.join(', ') || 'none recorded'} - -Start working on ${code} now.`; - } - /** - * Build prompt for system event slots. - */ - buildSystemPrompt(slot, sysData) { - switch (sysData.event) { - case 'ScheduleToday': - return `System Event: Schedule Today - -Please review today's calendar and schedule any pending tasks or planning activities. -Estimated time: ${slot.estimated_duration} minutes. - -Check your calendar and plan the day's work.`; - case 'SummaryToday': - return `System Event: Daily Summary - -Please provide a summary of today's activities and progress. -Estimated time: ${slot.estimated_duration} minutes. - -Review what was accomplished and prepare end-of-day notes.`; - case 'ScheduledGatewayRestart': - return `System Event: Scheduled Gateway Restart - -The OpenClaw gateway is scheduled to restart soon. -Please: -1. Persist any important state -2. Complete or gracefully pause current tasks -3. Prepare for restart - -Time remaining: ${slot.estimated_duration} minutes.`; - default: - return `System Event: ${sysData.event} - -A system event has been scheduled. Please handle accordingly. -Estimated time: ${slot.estimated_duration} minutes.`; - } - } - /** - * Build prompt for entertainment slots. - */ - buildEntertainmentPrompt(slot) { - return `Scheduled Entertainment Break - -Duration: ${slot.estimated_duration} minutes - -Take a break and enjoy some leisure time. This slot is reserved for non-work activities - to help maintain work-life balance.`; - } - /** - * Build generic prompt for slots without specific event data. - */ - buildGenericPrompt(slot) { - return `Scheduled Calendar Slot - -Type: ${slot.slot_type} -Duration: ${slot.estimated_duration} minutes -Priority: ${slot.priority} - -Please use this time for the scheduled activity.`; - } - /** - * Mark a slot as deferred with priority boost. - */ - async deferSlot(slot) { - const update = { - status: types_1.SlotStatus.DEFERRED, - }; - try { - if (slot.id) { - await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - } - this.logDebug(`Deferred slot: ${this.getSlotId(slot)}`); - } - catch (err) { - this.config.logger.error('Failed to defer slot:', err); - } - } - /** - * Revert a slot to not_started status after failed execution attempt. - */ - async revertSlot(slot) { - const update = { - status: types_1.SlotStatus.NOT_STARTED, - started_at: undefined, - }; - try { - if (slot.id) { - await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - } - } - catch (err) { - this.config.logger.error('Failed to revert slot:', err); - } - } - /** - * Complete the current slot execution. - * Call this when the agent finishes the task. - */ - async completeCurrentSlot(actualDurationMinutes) { - if (!this.state.currentSlot) { - this.config.logger.warn('No current slot to complete'); - return; - } - const slot = this.state.currentSlot; - const update = { - status: types_1.SlotStatus.FINISHED, - actual_duration: actualDurationMinutes, - }; - try { - if (slot.id) { - await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - } - // Report agent back to idle - await this.config.bridge.reportAgentStatus({ status: 'idle' }); - this.config.logger.info(`Completed slot ${this.getSlotId(slot)}, actual_duration=${actualDurationMinutes}min`); - } - catch (err) { - this.config.logger.error('Failed to complete slot:', err); - } - finally { - this.state.isProcessing = false; - this.state.currentSlot = null; - await this.triggerReplan('slot completion'); - } - } - /** - * Abort the current slot execution. - * Call this when the agent cannot complete the task. - */ - async abortCurrentSlot(reason) { - if (!this.state.currentSlot) { - this.config.logger.warn('No current slot to abort'); - return; - } - const slot = this.state.currentSlot; - const update = { - status: types_1.SlotStatus.ABORTED, - }; - try { - if (slot.id) { - await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - } - // Report agent back to idle - await this.config.bridge.reportAgentStatus({ status: 'idle' }); - this.config.logger.info(`Aborted slot ${this.getSlotId(slot)}${reason ? `: ${reason}` : ''}`); - } - catch (err) { - this.config.logger.error('Failed to abort slot:', err); - } - finally { - this.state.isProcessing = false; - this.state.currentSlot = null; - await this.triggerReplan('slot abort'); - } - } - /** - * Pause the current slot execution. - * Call this when the agent needs to temporarily pause. - */ - async pauseCurrentSlot() { - if (!this.state.currentSlot) { - this.config.logger.warn('No current slot to pause'); - return; - } - const slot = this.state.currentSlot; - const update = { - status: types_1.SlotStatus.PAUSED, - }; - try { - if (slot.id) { - await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - } - this.config.logger.info(`Paused slot ${this.getSlotId(slot)}`); - } - catch (err) { - this.config.logger.error('Failed to pause slot:', err); - } - } - /** - * Resume a paused slot. - */ - async resumeCurrentSlot() { - if (!this.state.currentSlot) { - this.config.logger.warn('No current slot to resume'); - return; - } - const slot = this.state.currentSlot; - const update = { - status: types_1.SlotStatus.ONGOING, - }; - try { - if (slot.id) { - await this.config.bridge.updateSlot(slot.id, update); - } - else if (slot.virtual_id) { - await this.config.bridge.updateVirtualSlot(slot.virtual_id, update); - } - this.config.logger.info(`Resumed slot ${this.getSlotId(slot)}`); - } - catch (err) { - this.config.logger.error('Failed to resume slot:', err); - } - } - /** - * Trigger an immediate replanning pass after the current slot lifecycle ends. - * This lets previously deferred/not-started slots compete again as soon as - * the agent becomes idle. - */ - async triggerReplan(reason) { - if (!this.state.isRunning) { - return; - } - this.logDebug(`Triggering immediate replanning after ${reason}`); - try { - await this.runHeartbeat(); - } - catch (err) { - this.config.logger.error(`Failed to trigger replanning after ${reason}:`, err); - } - } - /** - * Get a stable ID for a slot (real or virtual). - */ - getSlotId(slot) { - return slot.id?.toString() || slot.virtual_id || 'unknown'; - } - /** - * Format a Date as ISO time string (HH:MM:SS). - */ - formatTime(date) { - return date.toTimeString().split(' ')[0]; - } - /** - * Debug logging helper. - */ - logDebug(message) { - if (this.config.debug) { - this.config.logger.debug(`[CalendarScheduler] ${message}`); - } - } - /** - * Get current scheduler state (for introspection). - */ - getState() { - return { ...this.state }; - } - /** - * Check if scheduler is running. - */ - isRunning() { - return this.state.isRunning; - } - /** - * Check if currently processing a slot. - */ - isProcessing() { - return this.state.isProcessing; - } - /** - * Get the current slot being executed (if any). - */ - getCurrentSlot() { - return this.state.currentSlot; - } - /** - * Check if a gateway restart is pending. - */ - isRestartPending() { - return this.state.isRestartPending; - } - /** - * Get the path to the state file. - */ - getStateFilePath() { - return this.stateFilePath; - } -} -exports.CalendarScheduler = CalendarScheduler; -/** - * Factory function to create a CalendarScheduler from plugin context. - */ -function createCalendarScheduler(config) { - return new CalendarScheduler(config); -} -//# sourceMappingURL=scheduler.js.map \ No newline at end of file diff --git a/plugin/calendar/scheduler.js.map b/plugin/calendar/scheduler.js.map deleted file mode 100644 index de36aaf..0000000 --- a/plugin/calendar/scheduler.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["scheduler.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/plugin/calendar/types.d.ts b/plugin/calendar/types.d.ts deleted file mode 100644 index 4e19b4a..0000000 --- a/plugin/calendar/types.d.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * HarborForge Calendar — Plugin-side type definitions - * - * PLG-CAL-001: Define the Calendar heartbeat request/response format - * between the OpenClaw plugin and HarborForge backend. - * - * Request flow (plugin → backend): - * POST /calendar/agent/heartbeat - * Headers: - * X-Agent-ID: — OpenClaw $AGENT_ID of the calling agent - * X-Claw-Identifier: — HarborForge server identifier - * Body (JSON): - * { "claw_identifier": "...", "agent_id": "..." } - * - * Response flow (backend → plugin): - * Returns list of TimeSlots pending execution for today. - * The plugin uses slot.id / slot.virtual_id to update slot status - * via subsequent API calls. - * - * References: - * • NEXT_WAVE_DEV_DIRECTION.md §6 (Agent wakeup mechanism) - * • HarborForge.Backend/app/models/calendar.py — TimeSlot DB model - * • HarborForge.Backend/app/schemas/calendar.py — TimeSlot schemas - * • HarborForge.Backend/app/services/agent_heartbeat.py — BE-AGT-001 - */ -/** Slot type — mirrors backend SlotType enum */ -export declare enum SlotType { - WORK = "work", - ON_CALL = "on_call", - ENTERTAINMENT = "entertainment", - SYSTEM = "system" -} -/** Slot lifecycle status — mirrors backend SlotStatus enum */ -export declare enum SlotStatus { - NOT_STARTED = "not_started", - ONGOING = "ongoing", - DEFERRED = "deferred", - SKIPPED = "skipped", - PAUSED = "paused", - FINISHED = "finished", - ABORTED = "aborted" -} -/** High-level event category — mirrors backend EventType enum */ -export declare enum EventType { - JOB = "job", - ENTERTAINMENT = "entertainment", - SYSTEM_EVENT = "system_event" -} -/** - * Calendar heartbeat request body sent by the plugin to HarborForge backend. - * - * How claw_identifier is determined: - * 1. Read from plugin config: `config.backendUrl` is the base URL. - * 2. If not set, fall back to `os.hostname()` (plugin machine hostname). - * - * How agent_id is determined: - * - Read from OpenClaw environment variable: `process.env.AGENT_ID` - * - This is set by OpenClaw at agent startup and uniquely identifies - * the running agent instance within a single OpenClaw gateway. - */ -export interface CalendarHeartbeatRequest { - /** HarborForge server/claw identifier (matches MonitoredServer.identifier) */ - claw_identifier: string; - /** OpenClaw agent ID ($AGENT_ID) for this agent session */ - agent_id: string; -} -/** - * A single calendar slot returned in the heartbeat response. - * - * For **real** (materialized) slots: `id` is set, `virtual_id` is null. - * For **virtual** (plan-generated) slots: `id` is null, `virtual_id` - * is the `plan-{plan_id}-{date}` identifier. - * - * Key fields the plugin uses: - * - `id` / `virtual_id` — to update slot status after execution - * - `event_type` — to determine what action to take - * - `event_data` — job details / system event type - * - `slot_type` — work vs on_call (affects agent status transition) - * - `scheduled_at` — planned start time (HH:MM:SS) - * - `estimated_duration` — expected minutes (for time-tracking) - * - `priority` — for multi-slot competition logic - * - `status` — current status (NotStarted / Deferred) - */ -export interface CalendarSlotResponse { - /** Real slot DB id. Null for virtual slots. */ - id: number | null; - /** Virtual slot id (plan-{plan_id}-{date}). Null for real slots. */ - virtual_id: string | null; - /** Owner user id */ - user_id: number; - /** Calendar date */ - date: string; - /** Slot type */ - slot_type: SlotType; - /** Estimated duration in minutes (1-50) */ - estimated_duration: number; - /** Planned start time (ISO time string: "HH:MM:SS") */ - scheduled_at: string; - /** Actual start time, set when slot begins (null until started) */ - started_at: string | null; - /** Whether the slot has been attended */ - attended: boolean; - /** Actual duration in minutes (set when slot finishes) */ - actual_duration: number | null; - /** Event category */ - event_type: EventType | null; - /** Event details JSON — structure depends on event_type (see below) */ - event_data: CalendarEventData | null; - /** Priority 0-99, higher = more urgent */ - priority: number; - /** Current lifecycle status */ - status: SlotStatus; - /** Source plan id if materialized from a SchedulePlan; null otherwise */ - plan_id: number | null; -} -/** - * Event data stored inside CalendarSlotResponse.event_data. - * The shape depends on event_type. - * - * When event_type == "job": - * { "type": "Task|Support|Meeting|Essential", "code": "TASK-42", "working_sessions": ["..."] } - * - * When event_type == "system_event": - * { "event": "ScheduleToday|SummaryToday|ScheduledGatewayRestart" } - * - * When event_type == "entertainment": - * { /* TBD /\ } - */ -export interface CalendarEventDataJob { - type: 'Task' | 'Support' | 'Meeting' | 'Essential'; - code: string; - working_sessions?: string[]; -} -export interface CalendarEventDataSystemEvent { - event: 'ScheduleToday' | 'SummaryToday' | 'ScheduledGatewayRestart'; -} -export type CalendarEventData = CalendarEventDataJob | CalendarEventDataSystemEvent | Record; -/** - * Full heartbeat response returned by GET /calendar/agent/heartbeat - * - * Fields: - * slots — list of pending TimeSlots for today (sorted by priority desc) - * agent_status — current agent status from the backend's perspective - * (idle | on_call | busy | exhausted | offline) - */ -export interface CalendarHeartbeatResponse { - /** Pending slots for today — sorted by priority descending */ - slots: CalendarSlotResponse[]; - /** Current agent status in HarborForge */ - agent_status: AgentStatusValue; - /** Human-readable message (optional) */ - message?: string; -} -/** Agent status values — mirrors backend AgentStatus enum */ -export type AgentStatusValue = 'idle' | 'on_call' | 'busy' | 'exhausted' | 'offline'; -/** - * Request body for updating a real slot's status after agent execution. - * Called by the plugin after attending / finishing / deferring a slot. - * - * Endpoint: PATCH /calendar/slots/{slot_id}/agent-update - * (Plugin-facing variant that bypasses some user-level guards) - */ -export interface SlotAgentUpdate { - /** New status to set */ - status: SlotStatus; - /** Actual start time (ISO time string HH:MM:SS), required when attending */ - started_at?: string; - /** Actual duration in minutes, set when finishing */ - actual_duration?: number; -} -//# sourceMappingURL=types.d.ts.map \ No newline at end of file diff --git a/plugin/calendar/types.d.ts.map b/plugin/calendar/types.d.ts.map deleted file mode 100644 index 2866d75..0000000 --- a/plugin/calendar/types.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAMH,gDAAgD;AAChD,oBAAY,QAAQ;IAClB,IAAI,SAAS;IACb,OAAO,YAAY;IACnB,aAAa,kBAAkB;IAC/B,MAAM,WAAW;CAClB;AAED,8DAA8D;AAC9D,oBAAY,UAAU;IACpB,WAAW,gBAAgB;IAC3B,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,OAAO,YAAY;IACnB,MAAM,WAAW;IACjB,QAAQ,aAAa;IACrB,OAAO,YAAY;CACpB;AAED,iEAAiE;AACjE,oBAAY,SAAS;IACnB,GAAG,QAAQ;IACX,aAAa,kBAAkB;IAC/B,YAAY,iBAAiB;CAC9B;AAMD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,wBAAwB;IACvC,8EAA8E;IAC9E,eAAe,EAAE,MAAM,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,oBAAoB;IACnC,+CAA+C;IAC/C,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,oEAAoE;IACpE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oBAAoB;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB;IAChB,SAAS,EAAE,QAAQ,CAAC;IACpB,2CAA2C;IAC3C,kBAAkB,EAAE,MAAM,CAAC;IAC3B,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,yCAAyC;IACzC,QAAQ,EAAE,OAAO,CAAC;IAClB,0DAA0D;IAC1D,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,qBAAqB;IACrB,UAAU,EAAE,SAAS,GAAG,IAAI,CAAC;IAC7B,uEAAuE;IACvE,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACrC,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,yEAAyE;IACzE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,eAAe,GAAG,cAAc,GAAG,yBAAyB,CAAC;CACrE;AAGD,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,GAAG,4BAA4B,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE1G;;;;;;;GAOG;AACH,MAAM,WAAW,yBAAyB;IACxC,8DAA8D;IAC9D,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,0CAA0C;IAC1C,YAAY,EAAE,gBAAgB,CAAC;IAC/B,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,CAAC;AAMrF;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,wBAAwB;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B"} \ No newline at end of file diff --git a/plugin/calendar/types.js b/plugin/calendar/types.js deleted file mode 100644 index 2fefa40..0000000 --- a/plugin/calendar/types.js +++ /dev/null @@ -1,58 +0,0 @@ -"use strict"; -/** - * HarborForge Calendar — Plugin-side type definitions - * - * PLG-CAL-001: Define the Calendar heartbeat request/response format - * between the OpenClaw plugin and HarborForge backend. - * - * Request flow (plugin → backend): - * POST /calendar/agent/heartbeat - * Headers: - * X-Agent-ID: — OpenClaw $AGENT_ID of the calling agent - * X-Claw-Identifier: — HarborForge server identifier - * Body (JSON): - * { "claw_identifier": "...", "agent_id": "..." } - * - * Response flow (backend → plugin): - * Returns list of TimeSlots pending execution for today. - * The plugin uses slot.id / slot.virtual_id to update slot status - * via subsequent API calls. - * - * References: - * • NEXT_WAVE_DEV_DIRECTION.md §6 (Agent wakeup mechanism) - * • HarborForge.Backend/app/models/calendar.py — TimeSlot DB model - * • HarborForge.Backend/app/schemas/calendar.py — TimeSlot schemas - * • HarborForge.Backend/app/services/agent_heartbeat.py — BE-AGT-001 - */ -Object.defineProperty(exports, "__esModule", { value: true }); -exports.EventType = exports.SlotStatus = exports.SlotType = void 0; -// --------------------------------------------------------------------------- -// Enums (mirror backend enums) -// --------------------------------------------------------------------------- -/** Slot type — mirrors backend SlotType enum */ -var SlotType; -(function (SlotType) { - SlotType["WORK"] = "work"; - SlotType["ON_CALL"] = "on_call"; - SlotType["ENTERTAINMENT"] = "entertainment"; - SlotType["SYSTEM"] = "system"; -})(SlotType || (exports.SlotType = SlotType = {})); -/** Slot lifecycle status — mirrors backend SlotStatus enum */ -var SlotStatus; -(function (SlotStatus) { - SlotStatus["NOT_STARTED"] = "not_started"; - SlotStatus["ONGOING"] = "ongoing"; - SlotStatus["DEFERRED"] = "deferred"; - SlotStatus["SKIPPED"] = "skipped"; - SlotStatus["PAUSED"] = "paused"; - SlotStatus["FINISHED"] = "finished"; - SlotStatus["ABORTED"] = "aborted"; -})(SlotStatus || (exports.SlotStatus = SlotStatus = {})); -/** High-level event category — mirrors backend EventType enum */ -var EventType; -(function (EventType) { - EventType["JOB"] = "job"; - EventType["ENTERTAINMENT"] = "entertainment"; - EventType["SYSTEM_EVENT"] = "system_event"; -})(EventType || (exports.EventType = EventType = {})); -//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/plugin/calendar/types.js.map b/plugin/calendar/types.js.map deleted file mode 100644 index 5c04e3e..0000000 --- a/plugin/calendar/types.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;;AAEH,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AAE9E,gDAAgD;AAChD,IAAY,QAKX;AALD,WAAY,QAAQ;IAClB,yBAAa,CAAA;IACb,+BAAmB,CAAA;IACnB,2CAA+B,CAAA;IAC/B,6BAAiB,CAAA;AACnB,CAAC,EALW,QAAQ,wBAAR,QAAQ,QAKnB;AAED,8DAA8D;AAC9D,IAAY,UAQX;AARD,WAAY,UAAU;IACpB,yCAA2B,CAAA;IAC3B,iCAAmB,CAAA;IACnB,mCAAqB,CAAA;IACrB,iCAAmB,CAAA;IACnB,+BAAiB,CAAA;IACjB,mCAAqB,CAAA;IACrB,iCAAmB,CAAA;AACrB,CAAC,EARW,UAAU,0BAAV,UAAU,QAQrB;AAED,iEAAiE;AACjE,IAAY,SAIX;AAJD,WAAY,SAAS;IACnB,wBAAW,CAAA;IACX,4CAA+B,CAAA;IAC/B,0CAA6B,CAAA;AAC/B,CAAC,EAJW,SAAS,yBAAT,SAAS,QAIpB"} \ No newline at end of file diff --git a/plugin/core/config.ts b/plugin/core/config.ts new file mode 100644 index 0000000..a6c6a01 --- /dev/null +++ b/plugin/core/config.ts @@ -0,0 +1,35 @@ +import { hostname } from 'os'; + +export interface HarborForgePluginConfig { + enabled?: boolean; + backendUrl?: string; + identifier?: string; + apiKey?: string; + monitor_port?: number; + reportIntervalSec?: number; + httpFallbackIntervalSec?: number; + logLevel?: 'debug' | 'info' | 'warn' | 'error'; + calendarEnabled?: boolean; + calendarHeartbeatIntervalSec?: number; + calendarApiKey?: string; + managedMonitor?: string; +} + +interface PluginApiLike { + pluginConfig?: Record; +} + +export function getPluginConfig(api: PluginApiLike): HarborForgePluginConfig { + const cfg = (api.pluginConfig || {}) as HarborForgePluginConfig; + return { + enabled: true, + backendUrl: 'https://monitor.hangman-lab.top', + identifier: hostname(), + reportIntervalSec: 30, + httpFallbackIntervalSec: 60, + logLevel: 'info', + calendarEnabled: true, + calendarHeartbeatIntervalSec: 60, + ...cfg, + }; +} diff --git a/plugin/core/live-config.d.ts b/plugin/core/live-config.d.ts deleted file mode 100644 index 3f78c43..0000000 --- a/plugin/core/live-config.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -export interface HarborForgeMonitorConfig { - enabled?: boolean; - backendUrl?: string; - identifier?: string; - apiKey?: string; - monitor_port?: number; - monitorPort?: number; - reportIntervalSec?: number; - httpFallbackIntervalSec?: number; - logLevel?: 'debug' | 'info' | 'warn' | 'error'; -} -interface OpenClawPluginApiLike { - config?: Record; -} -export declare function getLivePluginConfig(api: OpenClawPluginApiLike, fallback: HarborForgeMonitorConfig): HarborForgeMonitorConfig; -export {}; -//# sourceMappingURL=live-config.d.ts.map \ No newline at end of file diff --git a/plugin/core/live-config.d.ts.map b/plugin/core/live-config.d.ts.map deleted file mode 100644 index 074ffc5..0000000 --- a/plugin/core/live-config.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"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,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,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,CAgC1B"} \ No newline at end of file diff --git a/plugin/core/live-config.js b/plugin/core/live-config.js deleted file mode 100644 index 463dc7c..0000000 --- a/plugin/core/live-config.js +++ /dev/null @@ -1,32 +0,0 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getLivePluginConfig = getLivePluginConfig; -function getLivePluginConfig(api, fallback) { - const root = api.config || {}; - const plugins = root.plugins || {}; - const entries = plugins.entries || {}; - const entry = entries['harbor-forge'] || {}; - const cfg = entry.config || {}; - if (Object.keys(cfg).length > 0 || Object.keys(entry).length > 0) { - const monitorPort = typeof cfg.monitor_port === 'number' - ? cfg.monitor_port - : typeof cfg.monitorPort === 'number' - ? cfg.monitorPort - : typeof fallback.monitor_port === 'number' - ? fallback.monitor_port - : fallback.monitorPort; - return { - ...fallback, - ...cfg, - monitor_port: monitorPort, - monitorPort, - enabled: typeof cfg.enabled === 'boolean' - ? cfg.enabled - : typeof entry.enabled === 'boolean' - ? entry.enabled - : fallback.enabled, - }; - } - return fallback; -} -//# sourceMappingURL=live-config.js.map \ No newline at end of file diff --git a/plugin/core/live-config.js.map b/plugin/core/live-config.js.map deleted file mode 100644 index e0573e7..0000000 --- a/plugin/core/live-config.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"live-config.js","sourceRoot":"","sources":["live-config.ts"],"names":[],"mappings":";;AAgBA,kDAmCC;AAnCD,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,MAAM,WAAW,GACf,OAAO,GAAG,CAAC,YAAY,KAAK,QAAQ;YAClC,CAAC,CAAC,GAAG,CAAC,YAAY;YAClB,CAAC,CAAC,OAAO,GAAG,CAAC,WAAW,KAAK,QAAQ;gBACnC,CAAC,CAAC,GAAG,CAAC,WAAW;gBACjB,CAAC,CAAC,OAAO,QAAQ,CAAC,YAAY,KAAK,QAAQ;oBACzC,CAAC,CAAC,QAAQ,CAAC,YAAY;oBACvB,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QAE/B,OAAO;YACL,GAAG,QAAQ;YACX,GAAG,GAAG;YACN,YAAY,EAAE,WAAW;YACzB,WAAW;YACX,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 deleted file mode 100644 index 6e3de73..0000000 --- a/plugin/core/live-config.ts +++ /dev/null @@ -1,52 +0,0 @@ -export interface HarborForgeMonitorConfig { - enabled?: boolean; - backendUrl?: string; - identifier?: string; - apiKey?: string; - monitor_port?: number; - monitorPort?: number; - reportIntervalSec?: number; - httpFallbackIntervalSec?: number; - logLevel?: 'debug' | 'info' | 'warn' | 'error'; -} - -interface OpenClawPluginApiLike { - config?: Record; -} - -export function getLivePluginConfig( - api: OpenClawPluginApiLike, - fallback: HarborForgeMonitorConfig -): HarborForgeMonitorConfig { - const root = (api.config as Record) || {}; - const plugins = (root.plugins as Record) || {}; - const entries = (plugins.entries 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) { - const monitorPort = - typeof cfg.monitor_port === 'number' - ? cfg.monitor_port - : typeof cfg.monitorPort === 'number' - ? cfg.monitorPort - : typeof fallback.monitor_port === 'number' - ? fallback.monitor_port - : fallback.monitorPort; - - return { - ...fallback, - ...cfg, - monitor_port: monitorPort, - monitorPort, - enabled: - typeof cfg.enabled === 'boolean' - ? cfg.enabled - : typeof entry.enabled === 'boolean' - ? entry.enabled - : fallback.enabled, - } as HarborForgeMonitorConfig; - } - - return fallback; -} diff --git a/plugin/core/managed-monitor.ts b/plugin/core/managed-monitor.ts new file mode 100644 index 0000000..c2c3a67 --- /dev/null +++ b/plugin/core/managed-monitor.ts @@ -0,0 +1,51 @@ +import { spawn, type ChildProcess } from 'child_process'; +import { existsSync } from 'fs'; + +export interface ManagedMonitorConfig { + managedMonitor?: string; + monitor_port?: number; + logLevel?: string; +} + +let monitorProcess: ChildProcess | null = null; + +export function startManagedMonitor( + logger: { info: (...args: any[]) => void; warn: (...args: any[]) => void; error: (...args: any[]) => void }, + config: ManagedMonitorConfig, +): void { + if (!config.managedMonitor) return; + if (monitorProcess) { + logger.info('HarborForge managed monitor already running'); + return; + } + if (!existsSync(config.managedMonitor)) { + logger.warn(`HarborForge managed monitor path not found: ${config.managedMonitor}`); + return; + } + + const args: string[] = []; + if (config.monitor_port) args.push('--port', String(config.monitor_port)); + if (config.logLevel) args.push('--log-level', String(config.logLevel)); + + monitorProcess = spawn(config.managedMonitor, args, { + stdio: 'inherit', + detached: false, + }); + + monitorProcess.on('exit', (code, signal) => { + logger.warn(`HarborForge managed monitor exited code=${code ?? 'null'} signal=${signal ?? 'null'}`); + monitorProcess = null; + }); + + logger.info(`HarborForge managed monitor started: ${config.managedMonitor}`); +} + +export function stopManagedMonitor( + logger: { info: (...args: any[]) => void; warn: (...args: any[]) => void }, +): void { + if (!monitorProcess) return; + const pid = monitorProcess.pid; + monitorProcess.kill('SIGTERM'); + monitorProcess = null; + logger.info(`HarborForge managed monitor stopped pid=${pid ?? 'unknown'}`); +} diff --git a/plugin/core/monitor-bridge.d.ts b/plugin/core/monitor-bridge.d.ts deleted file mode 100644 index afa27f9..0000000 --- a/plugin/core/monitor-bridge.d.ts +++ /dev/null @@ -1,55 +0,0 @@ -/** - * 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; - /** - * POST OpenClaw metadata to the Monitor bridge so it can enrich - * its heartbeat uploads with OpenClaw version, plugin version, - * and agent information. - */ - pushOpenClawMeta(meta: OpenClawMeta): Promise; - private fetchJson; -} -/** - * OpenClaw metadata payload sent to the Monitor bridge. - */ -export interface OpenClawMeta { - version: string; - plugin_version: string; - agents?: any[]; -} -//# 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 deleted file mode 100644 index d170573..0000000 --- a/plugin/core/monitor-bridge.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"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;IAI3D;;;;OAIG;IACG,gBAAgB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;YAmB9C,SAAS;CAgBxB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;CAChB"} \ No newline at end of file diff --git a/plugin/core/monitor-bridge.js b/plugin/core/monitor-bridge.js deleted file mode 100644 index 144b4e4..0000000 --- a/plugin/core/monitor-bridge.js +++ /dev/null @@ -1,66 +0,0 @@ -"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'); - } - /** - * POST OpenClaw metadata to the Monitor bridge so it can enrich - * its heartbeat uploads with OpenClaw version, plugin version, - * and agent information. - */ - async pushOpenClawMeta(meta) { - try { - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), this.timeoutMs); - const response = await fetch(`${this.baseUrl}/openclaw`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(meta), - signal: controller.signal, - }); - clearTimeout(timeout); - return response.ok; - } - catch { - return false; - } - } - 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 deleted file mode 100644 index 174276a..0000000 --- a/plugin/core/monitor-bridge.js.map +++ /dev/null @@ -1 +0,0 @@ -{"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;IAED;;;;OAIG;IACH,KAAK,CAAC,gBAAgB,CAAC,IAAkB;QACvC,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,WAAW,EAAE;gBACvD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,UAAU,CAAC,MAAM;aAC1B,CAAC,CAAC;YACH,YAAY,CAAC,OAAO,CAAC,CAAC;YAEtB,OAAO,QAAQ,CAAC,EAAE,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,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;AAzDD,kDAyDC"} \ No newline at end of file diff --git a/plugin/hooks/gateway-start.ts b/plugin/hooks/gateway-start.ts new file mode 100644 index 0000000..9573b3d --- /dev/null +++ b/plugin/hooks/gateway-start.ts @@ -0,0 +1,33 @@ +import { hostname } from 'os'; +import { getPluginConfig } from '../core/config'; +import { startManagedMonitor } from '../core/managed-monitor'; + +export function registerGatewayStartHook(api: any, deps: { + logger: any; + pushMetaToMonitor: () => Promise; + startCalendarScheduler: () => void; + setMetaPushInterval: (handle: ReturnType) => void; +}) { + const { logger, pushMetaToMonitor, startCalendarScheduler, setMetaPushInterval } = deps; + + api.on('gateway_start', () => { + logger.info('HarborForge plugin active'); + const live = getPluginConfig(api); + + startManagedMonitor(logger, { + managedMonitor: live.managedMonitor, + monitor_port: live.monitor_port, + logLevel: live.logLevel, + }); + + const intervalSec = live.reportIntervalSec || 30; + setTimeout(() => void pushMetaToMonitor(), 2000); + setMetaPushInterval(setInterval(() => void pushMetaToMonitor(), intervalSec * 1000)); + + if (live.enabled !== false && live.calendarEnabled !== false) { + setTimeout(() => startCalendarScheduler(), 5000); + } + + logger.info(`HarborForge startup config identifier=${live.identifier || hostname()} backend=${live.backendUrl}`); + }); +} diff --git a/plugin/hooks/gateway-stop.ts b/plugin/hooks/gateway-stop.ts new file mode 100644 index 0000000..04dce16 --- /dev/null +++ b/plugin/hooks/gateway-stop.ts @@ -0,0 +1,23 @@ +import { stopManagedMonitor } from '../core/managed-monitor'; + +export function registerGatewayStopHook(api: any, deps: { + logger: any; + getMetaPushInterval: () => ReturnType | null; + clearMetaPushInterval: () => void; + stopCalendarScheduler: () => void; +}) { + const { logger, getMetaPushInterval, clearMetaPushInterval, stopCalendarScheduler } = deps; + + api.on('gateway_stop', () => { + logger.info('HarborForge plugin stopping'); + + const handle = getMetaPushInterval(); + if (handle) { + clearInterval(handle); + clearMetaPushInterval(); + } + + stopCalendarScheduler(); + stopManagedMonitor(logger); + }); +} diff --git a/plugin/index.ts b/plugin/index.ts index 0e50d97..864f3ee 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -12,8 +12,10 @@ * local monitor_port communication path. */ import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os'; -import { getLivePluginConfig, type HarborForgeMonitorConfig } from './core/live-config'; +import { getPluginConfig } from './core/config'; import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge'; +import { registerGatewayStartHook } from './hooks/gateway-start'; +import { registerGatewayStopHook } from './hooks/gateway-stop'; import { createCalendarBridgeClient, createCalendarScheduler, @@ -55,18 +57,8 @@ export default { warn: (...args: any[]) => console.warn('[HarborForge]', ...args), }; - const baseConfig: HarborForgeMonitorConfig = { - enabled: true, - backendUrl: 'https://monitor.hangman-lab.top', - identifier: '', - reportIntervalSec: 30, - httpFallbackIntervalSec: 60, - logLevel: 'info', - ...(api.pluginConfig || {}), - }; - function resolveConfig() { - return getLivePluginConfig(api, baseConfig); + return getPluginConfig(api); } /** @@ -304,38 +296,24 @@ export default { } } - api.on('gateway_start', () => { - logger.info('HarborForge plugin active'); - - // Push metadata to Monitor bridge on startup and periodically. - // Interval aligns with typical Monitor heartbeat cycle (30s). - // If Monitor bridge is unreachable, pushes silently fail. - const live = resolveConfig(); - const intervalSec = live.reportIntervalSec || 30; - - // Initial push (delayed 2s to let Monitor bridge start) - setTimeout(() => pushMetaToMonitor(), 2000); - - metaPushInterval = setInterval( - () => pushMetaToMonitor(), - intervalSec * 1000, - ); - - // Start calendar scheduler (delayed to let everything initialize) - if (live.enabled !== false) { - setTimeout(() => startCalendarScheduler(), 5000); - } + registerGatewayStartHook(api, { + logger, + pushMetaToMonitor, + startCalendarScheduler, + setMetaPushInterval(handle) { + metaPushInterval = handle; + }, }); - api.on('gateway_stop', () => { - logger.info('HarborForge plugin stopping'); - - if (metaPushInterval) { - clearInterval(metaPushInterval); + registerGatewayStopHook(api, { + logger, + getMetaPushInterval() { + return metaPushInterval; + }, + clearMetaPushInterval() { metaPushInterval = null; - } - - stopCalendarScheduler(); + }, + stopCalendarScheduler, }); // Tool: plugin status diff --git a/plugin/openclaw.plugin.json b/plugin/openclaw.plugin.json index 30a3e60..186c986 100644 --- a/plugin/openclaw.plugin.json +++ b/plugin/openclaw.plugin.json @@ -3,7 +3,7 @@ "name": "HarborForge", "version": "0.2.0", "description": "HarborForge plugin for OpenClaw - project management, monitoring, and CLI integration", - "entry": "./index.js", + "entry": "./dist/index.js", "configSchema": { "type": "object", "additionalProperties": false, @@ -59,6 +59,10 @@ "calendarApiKey": { "type": "string", "description": "API key for Calendar API authentication. If not set, uses apiKey or plugin auto-authentication via X-Agent-ID header." + }, + "managedMonitor": { + "type": "string", + "description": "Absolute path to an installed HarborForge.Monitor binary managed by this plugin installer. If set, gateway_start/gateway_stop hooks will start/stop the monitor process automatically." } } } diff --git a/plugin/package.json b/plugin/package.json index 0e17992..c7ba015 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -2,9 +2,10 @@ "name": "harbor-forge-plugin", "version": "0.2.0", "description": "OpenClaw plugin for HarborForge monitor bridge and CLI integration", - "main": "index.js", + "main": "dist/index.js", "scripts": { - "build": "tsc", + "clean": "rm -rf dist", + "build": "npm run clean && tsc", "watch": "tsc --watch" }, "devDependencies": { diff --git a/plugin/tsconfig.json b/plugin/tsconfig.json index 3d63e25..b095193 100644 --- a/plugin/tsconfig.json +++ b/plugin/tsconfig.json @@ -6,12 +6,11 @@ "esModuleInterop": true, "strict": true, "skipLibCheck": true, - "outDir": "./", + "outDir": "./dist", "rootDir": "./", - "declaration": true, - "declarationMap": true, + "declaration": false, "sourceMap": true }, "include": ["**/*.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "dist"] } diff --git a/scripts/install.mjs b/scripts/install.mjs index 12a9bfb..4bddec3 100644 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -40,6 +40,8 @@ const options = { uninstall: args.includes('--uninstall'), installOnly: args.includes('--install'), installCli: args.includes('--install-cli'), + installMonitor: 'no', + monitorBranch: 'main', }; const profileIdx = args.indexOf('--openclaw-profile-path'); @@ -47,6 +49,16 @@ if (profileIdx !== -1 && args[profileIdx + 1]) { options.openclawProfilePath = resolve(args[profileIdx + 1]); } +const installMonitorIdx = args.indexOf('--install-monitor'); +if (installMonitorIdx !== -1 && args[installMonitorIdx + 1]) { + options.installMonitor = String(args[installMonitorIdx + 1]).toLowerCase(); +} + +const monitorBranchIdx = args.indexOf('--monitor-branch'); +if (monitorBranchIdx !== -1 && args[monitorBranchIdx + 1]) { + options.monitorBranch = String(args[monitorBranchIdx + 1]); +} + function resolveOpenclawPath() { if (options.openclawProfilePath) return options.openclawProfilePath; if (process.env.OPENCLAW_PATH) return resolve(process.env.OPENCLAW_PATH); @@ -164,6 +176,38 @@ async function build() { logOk('plugin compiled'); } +function findMonitorSource(projectRoot) { + const candidates = [ + join(projectRoot, 'HarborForge.Monitor'), + resolve(projectRoot, '..', 'HarborForge.Monitor'), + ]; + return candidates.find((p) => existsSync(p)) || null; +} + +function installManagedMonitor(openclawPath) { + if (options.installMonitor !== 'yes') return null; + const projectRoot = resolve(__dirname, '..'); + const monitorSrc = findMonitorSource(projectRoot); + if (!monitorSrc) { + logWarn('HarborForge.Monitor source not found; skipping managed monitor install'); + return null; + } + const monitorDestDir = join(openclawPath, 'managed', 'harborforge-monitor'); + rmSync(monitorDestDir, { recursive: true, force: true }); + mkdirSync(monitorDestDir, { recursive: true }); + copyDir(monitorSrc, monitorDestDir, { exclude: ['.git', 'node_modules', 'bin', 'obj'] }); + const binaryPath = join(monitorDestDir, 'HarborForge.Monitor'); + try { + exec(`go build -o ${binaryPath} .`, { cwd: monitorDestDir, silent: !options.verbose }); + chmodSync(binaryPath, 0o755); + logOk(`Managed monitor installed → ${binaryPath} (branch hint: ${options.monitorBranch})`); + return binaryPath; + } catch (err) { + logWarn(`Managed monitor build failed: ${err.message}`); + return null; + } +} + function clearInstallTargets(openclawPath) { // Remove new plugin dir const destDir = join(openclawPath, 'plugins', PLUGIN_NAME); @@ -244,7 +288,7 @@ async function install() { // Copy compiled plugin (no server directory — sidecar removed) mkdirSync(destDir, { recursive: true }); - copyDir(PLUGIN_SRC_DIR, destDir); + copyDir(PLUGIN_SRC_DIR, destDir, { exclude: ['node_modules', '.git', '*.ts'] }); logOk(`Plugin files → ${destDir}`); // Copy skills (exclude hf/ unless --install-cli) @@ -263,7 +307,8 @@ async function install() { exec('npm install --omit=dev', { cwd: destDir, silent: !options.verbose }); logOk('Runtime deps installed'); - return { destDir }; + const managedMonitorPath = installManagedMonitor(openclawPath); + return { destDir, managedMonitorPath }; } async function installCli() { @@ -329,6 +374,18 @@ async function configure() { setOpenclawConfig('plugins.allow', allow); } logOk(`plugins.allow includes ${PLUGIN_NAME}`); + + if (options.installMonitor === 'yes') { + const projectRoot = resolve(__dirname, '..'); + const binaryPath = join(openclawPath, 'managed', 'harborforge-monitor', 'HarborForge.Monitor'); + const entry = getOpenclawConfig(`plugins.entries.${PLUGIN_NAME}`, {}) || {}; + const config = entry.config || {}; + config.managedMonitor = binaryPath; + entry.config = config; + entry.enabled = true; + setOpenclawConfig(`plugins.entries.${PLUGIN_NAME}`, entry); + logOk(`managedMonitor configured → ${binaryPath}`); + } logOk('Plugin configured (remember to set apiKey in plugins.entries.harbor-forge.config)'); @@ -390,6 +447,12 @@ async function uninstall() { rmSync(hfBinary, { force: true }); logOk('Removed hf CLI binary'); } + + const managedDir = join(openclawPath, 'managed', 'harborforge-monitor'); + if (existsSync(managedDir)) { + rmSync(managedDir, { recursive: true, force: true }); + logOk('Removed managed HarborForge monitor'); + } log('\nRun: openclaw gateway restart', 'yellow'); }