From 55d7d11a5202b87b9f4e4bbb7575730d0e121ab3 Mon Sep 17 00:00:00 2001 From: zhi Date: Wed, 1 Apr 2026 07:51:39 +0000 Subject: [PATCH 01/15] feat(plugin): PLG-CAL-001 - define Calendar heartbeat request/response format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add plugin/calendar/types.ts: TypeScript interfaces for heartbeat request/response (CalendarHeartbeatRequest/Response, CalendarSlotResponse, SlotAgentUpdate, all enums: SlotType, SlotStatus, EventType) - Add plugin/calendar/calendar-bridge.ts: CalendarBridgeClient HTTP client with heartbeat(), updateSlot(), updateVirtualSlot(), reportAgentStatus() - Add plugin/calendar/index.ts: module entry point exporting all public types - Add docs/PLG-CAL-001-calendar-heartbeat-format.md: full specification documenting claw_identifier and agent_id determination, request/response shapes, error handling, and endpoint summary - Update plugin/openclaw.plugin.json: add calendarEnabled, calendarHeartbeatIntervalSec, calendarApiKey config options; clarify identifier description as claw_identifier Refs: HarborForge.NEXT_WAVE_DEV_DIRECTION.md §6, BE-AGT-001 --- docs/PLG-CAL-001-calendar-heartbeat-format.md | 261 +++++++++++++++++ package-lock.json | 17 ++ 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 | 265 ++++++++++++++++++ plugin/calendar/index.d.ts | 23 ++ plugin/calendar/index.d.ts.map | 1 + plugin/calendar/index.js | 39 +++ plugin/calendar/index.js.map | 1 + plugin/calendar/index.ts | 23 ++ 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/calendar/types.ts | 198 +++++++++++++ plugin/openclaw.plugin.json | 46 +-- 18 files changed, 1429 insertions(+), 16 deletions(-) create mode 100644 docs/PLG-CAL-001-calendar-heartbeat-format.md create mode 100644 package-lock.json create mode 100644 plugin/calendar/calendar-bridge.d.ts create mode 100644 plugin/calendar/calendar-bridge.d.ts.map create mode 100644 plugin/calendar/calendar-bridge.js create mode 100644 plugin/calendar/calendar-bridge.js.map create mode 100644 plugin/calendar/calendar-bridge.ts create mode 100644 plugin/calendar/index.d.ts create mode 100644 plugin/calendar/index.d.ts.map create mode 100644 plugin/calendar/index.js create mode 100644 plugin/calendar/index.js.map create mode 100644 plugin/calendar/index.ts create mode 100644 plugin/calendar/types.d.ts create mode 100644 plugin/calendar/types.d.ts.map create mode 100644 plugin/calendar/types.js create mode 100644 plugin/calendar/types.js.map create mode 100644 plugin/calendar/types.ts diff --git a/docs/PLG-CAL-001-calendar-heartbeat-format.md b/docs/PLG-CAL-001-calendar-heartbeat-format.md new file mode 100644 index 0000000..687fd5e --- /dev/null +++ b/docs/PLG-CAL-001-calendar-heartbeat-format.md @@ -0,0 +1,261 @@ +# PLG-CAL-001 — Calendar Heartbeat Format Specification + +> **Task:** HarborForge OpenclawPlugin / Monitor 联动 +> **Subtask:** PLG-CAL-001 — 插件侧定义 Calendar 心跳请求格式 +> **Status:** ✅ Implemented (types + client + spec) +> **Date:** 2026-04-01 + +--- + +## Overview + +This document specifies the request/response format for the Calendar heartbeat +communication between the OpenClaw HarborForge plugin and the HarborForge backend. + +Heartbeat direction: **Plugin → Backend** + +The plugin sends a heartbeat every minute (aligned with the existing Monitor +heartbeat interval). The backend returns today's pending TimeSlots for the agent. + +--- + +## 1. How `claw_identifier` is Determined + +`claw_identifier` identifies the server/claw instance. It is the same value used +in the Monitor heartbeat system (`MonitoredServer.identifier`). + +**Priority order:** + +1. **`config.identifier`** — if set in the plugin config (`harbor-forge.identifier`) +2. **`os.hostname()`** — auto-detected from the machine hostname (fallback) + +```typescript +// In plugin/calendar/calendar-bridge.ts +const clawIdentifier = baseConfig.identifier || hostname(); +``` + +--- + +## 2. How `agent_id` is Determined + +`agent_id` is the OpenClaw agent identifier (`$AGENT_ID`). + +- Set by OpenClaw at agent startup as an environment variable +- The plugin reads it via `process.env.AGENT_ID` +- Globally unique within a single OpenClaw gateway deployment + +```typescript +// In plugin/index.ts (caller) +const agentId = process.env.AGENT_ID || 'unknown'; +``` + +--- + +## 3. Heartbeat Request + +**Endpoint:** `GET /calendar/agent/heartbeat` + +### Headers + +| Header | Value | Notes | +|--------|-------|-------| +| `Content-Type` | `application/json` | Always set | +| `X-Agent-ID` | `$AGENT_ID` | OpenClaw agent identifier | +| `X-Claw-Identifier` | `claw_identifier` | Server identifier | + +### Request Body (JSON) + +```json +{ + "claw_identifier": "srv1390517", + "agent_id": "developer" +} +``` + +### Field Definitions + +| Field | Type | Source | Notes | +|-------|------|--------|-------| +| `claw_identifier` | string | Plugin config or `hostname()` | Identifies the OpenClaw server instance | +| `agent_id` | string | `process.env.AGENT_ID` | Identifies the agent session | + +--- + +## 4. Heartbeat Response + +### Success (HTTP 200) + +```json +{ + "slots": [ + { + "id": 42, + "virtual_id": null, + "user_id": 1, + "date": "2026-04-01", + "slot_type": "work", + "estimated_duration": 30, + "scheduled_at": "09:00:00", + "started_at": null, + "attended": false, + "actual_duration": null, + "event_type": "job", + "event_data": { + "type": "Task", + "code": "TASK-123" + }, + "priority": 50, + "status": "not_started", + "plan_id": null + } + ], + "agent_status": "idle", + "message": "2 slots pending" +} +``` + +### Field Definitions + +| Field | Type | Notes | +|-------|------|-------| +| `slots` | `CalendarSlotResponse[]` | Pending slots, sorted by `priority` DESC | +| `agent_status` | `AgentStatusValue` | Current backend-observed agent status | +| `message` | string (optional) | Human-readable summary | + +### `CalendarSlotResponse` Fields + +| Field | Type | Notes | +|-------|------|-------| +| `id` | `number \| null` | Real slot DB id. `null` for virtual slots. | +| `virtual_id` | `string \| null` | `plan-{plan_id}-{date}`. `null` for real slots. | +| `user_id` | number | Owner HarborForge user id | +| `date` | string | ISO date `YYYY-MM-DD` | +| `slot_type` | `SlotType` | `work \| on_call \| entertainment \| system` | +| `estimated_duration` | number | Minutes (1-50) | +| `scheduled_at` | string | ISO time `HH:MM:SS` | +| `started_at` | `string \| null` | Actual start time when slot begins | +| `attended` | boolean | `true` once agent begins the slot | +| `actual_duration` | `number \| null` | Real minutes when slot finishes | +| `event_type` | `EventType \| null` | `job \| entertainment \| system_event` | +| `event_data` | object \| null | See §4a below | +| `priority` | number | 0-99, higher = more urgent | +| `status` | `SlotStatus` | `not_started \| deferred \| ...` | +| `plan_id` | `number \| null` | Source plan if materialized from SchedulePlan | + +### §4a — `event_data` Shapes + +**When `event_type == "job"`:** +```json +{ + "type": "Task", + "code": "TASK-42", + "working_sessions": ["session-id-1"] +} +``` + +**When `event_type == "system_event"`:** +```json +{ + "event": "ScheduleToday" +} +``` +Valid events: `ScheduleToday | SummaryToday | ScheduledGatewayRestart` + +--- + +## 5. Slot Update Requests (Plugin → Backend after execution) + +After attending / finishing / deferring a slot, the plugin calls: + +**Real slot:** `PATCH /calendar/slots/{slot_id}/agent-update` +**Virtual slot:** `PATCH /calendar/slots/virtual/{virtual_id}/agent-update` + +### Headers + +Same as heartbeat (see §3). + +### Request Body + +```json +{ + "status": "ongoing", + "started_at": "09:02:31", + "actual_duration": null +} +``` + +| Field | Type | Required | Notes | +|-------|------|----------|-------| +| `status` | `SlotStatus` | **Required** | New status after agent action | +| `started_at` | string | On attending | ISO time `HH:MM:SS` | +| `actual_duration` | number | On finishing | Real minutes | + +### Status Transition Values + +| Action | `status` value | +|--------|---------------| +| Agent begins slot | `ongoing` | +| Agent finishes slot | `finished` | +| Agent defers slot | `deferred` | +| Agent aborts slot | `aborted` | +| Agent pauses slot | `paused` | + +--- + +## 6. Backend Endpoint Summary + +| Method | Path | Auth | Description | +|--------|------|------|-------------| +| GET | `/calendar/agent/heartbeat` | X-Agent-ID + X-Claw-Identifier | Fetch pending slots for today | +| PATCH | `/calendar/slots/{id}/agent-update` | X-Agent-ID + X-Claw-Identifier | Update real slot status | +| PATCH | `/calendar/slots/virtual/{vid}/agent-update` | X-Agent-ID + X-Claw-Identifier | Update virtual slot status | +| POST | `/calendar/agent/status` | X-Agent-ID + X-Claw-Identifier | Report agent status change | + +--- + +## 7. Error Handling + +- **Backend unreachable:** Plugin logs warning, returns `null` from heartbeat. + Agent continues to operate without Calendar integration. +- **Invalid credentials (401/403):** Logged as error. No retry on same interval. +- **Rate limiting (429):** Plugin should mark agent as `Exhausted` and not retry + until the `Retry-After` header indicates. + +--- + +## 8. TypeScript Reference + +Full type definitions are in `plugin/calendar/types.ts`: + +```typescript +// Request +interface CalendarHeartbeatRequest { + claw_identifier: string; + agent_id: string; +} + +// Response +interface CalendarHeartbeatResponse { + slots: CalendarSlotResponse[]; + agent_status: AgentStatusValue; + message?: string; +} + +// Slot update +interface SlotAgentUpdate { + status: SlotStatus; + started_at?: string; // ISO time HH:MM:SS + actual_duration?: number; +} +``` + +--- + +## 9. Implementation Files + +| File | Purpose | +|------|---------| +| `plugin/calendar/types.ts` | TypeScript interfaces for all request/response shapes | +| `plugin/calendar/calendar-bridge.ts` | `CalendarBridgeClient` HTTP client | +| `plugin/calendar/index.ts` | Module entry point | +| `docs/PLG-CAL-001-calendar-heartbeat-format.md` | This specification | diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..60eb846 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "harbor-forge-openclaw-plugin", + "version": "0.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "harbor-forge-openclaw-plugin", + "version": "0.2.0", + "hasInstallScript": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + } + } +} diff --git a/plugin/calendar/calendar-bridge.d.ts b/plugin/calendar/calendar-bridge.d.ts new file mode 100644 index 0000000..cae6d50 --- /dev/null +++ b/plugin/calendar/calendar-bridge.d.ts @@ -0,0 +1,124 @@ +/** + * 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 new file mode 100644 index 0000000..d83c24e --- /dev/null +++ b/plugin/calendar/calendar-bridge.d.ts.map @@ -0,0 +1 @@ +{"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 new file mode 100644 index 0000000..3ac6578 --- /dev/null +++ b/plugin/calendar/calendar-bridge.js @@ -0,0 +1,214 @@ +"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 new file mode 100644 index 0000000..f62de25 --- /dev/null +++ b/plugin/calendar/calendar-bridge.js.map @@ -0,0 +1 @@ +{"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 new file mode 100644 index 0000000..71ffce1 --- /dev/null +++ b/plugin/calendar/calendar-bridge.ts @@ -0,0 +1,265 @@ +/** + * 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 { + CalendarHeartbeatRequest, + CalendarHeartbeatResponse, + CalendarSlotResponse, + SlotAgentUpdate, + SlotStatus, +} 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 class CalendarBridgeClient { + private baseUrl: string; + private config: Required; + private timeoutMs: number; + + constructor(config: CalendarBridgeConfig) { + 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(): Promise { + const url = `${this.baseUrl}/calendar/agent/heartbeat`; + const body: CalendarHeartbeatRequest = { + 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: number, update: SlotAgentUpdate): Promise { + 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: string, + update: SlotAgentUpdate + ): Promise { + const url = `${this.baseUrl}/calendar/slots/virtual/${encodeURIComponent(virtualId)}/agent-update`; + try { + const response = await this.fetchJson<{ slot: CalendarSlotResponse }>(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: { + status: 'idle' | 'on_call' | 'busy' | 'exhausted' | 'offline'; + recoveryAt?: string; + exhaustReason?: 'rate_limit' | 'billing'; + }): Promise { + 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 + // ------------------------------------------------------------------------- + + private async fetchJson( + url: string, + init: RequestInit + ): Promise { + 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()) as T; + } catch { + clearTimeout(timeout); + return null; + } + } + + private async postBoolean(url: string, body: unknown): Promise { + 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; + } + } +} + +// --------------------------------------------------------------------------- +// Utility: build CalendarBridgeConfig from plugin API context +// --------------------------------------------------------------------------- + +import { hostname } from 'os'; +import { getLivePluginConfig, type HarborForgeMonitorConfig } from '../core/live-config'; + +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 function createCalendarBridgeClient( + api: { config?: Record; logger?: { debug?: (...args: unknown[]) => void } }, + fallbackUrl: string, + agentId: string +): CalendarBridgeClient { + const baseConfig = getLivePluginConfig(api, { + backendUrl: fallbackUrl, + identifier: hostname(), + } as HarborForgeMonitorConfig) as HarborForgeMonitorConfig; + + const clawIdentifier = baseConfig.identifier || hostname(); + + return new CalendarBridgeClient({ + backendUrl: baseConfig.backendUrl || fallbackUrl, + clawIdentifier, + agentId, + timeoutMs: 5000, + }); +} diff --git a/plugin/calendar/index.d.ts b/plugin/calendar/index.d.ts new file mode 100644 index 0000000..7207fc6 --- /dev/null +++ b/plugin/calendar/index.d.ts @@ -0,0 +1,23 @@ +/** + * HarborForge Calendar — Plugin Module + * + * PLG-CAL-001: Calendar heartbeat request/response format definition. + * + * Exports: + * • Types for heartbeat request/response and slot update + * • CalendarBridgeClient — HTTP client for backend communication + * • createCalendarBridgeClient — factory from plugin API context + * + * Usage in plugin/index.ts: + * import { createCalendarBridgeClient } from './calendar'; + * + * const agentId = process.env.AGENT_ID || 'unknown'; + * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); + * + * // Inside gateway_start or heartbeat tick: + * const result = await calendar.heartbeat(); + * if (result?.slots.length) { /* handle pending slots /\ } + */ +export * from './types'; +export * from './calendar-bridge'; +//# 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 new file mode 100644 index 0000000..5762871 --- /dev/null +++ b/plugin/calendar/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC"} \ No newline at end of file diff --git a/plugin/calendar/index.js b/plugin/calendar/index.js new file mode 100644 index 0000000..a67b291 --- /dev/null +++ b/plugin/calendar/index.js @@ -0,0 +1,39 @@ +"use strict"; +/** + * HarborForge Calendar — Plugin Module + * + * PLG-CAL-001: Calendar heartbeat request/response format definition. + * + * Exports: + * • Types for heartbeat request/response and slot update + * • CalendarBridgeClient — HTTP client for backend communication + * • createCalendarBridgeClient — factory from plugin API context + * + * Usage in plugin/index.ts: + * import { createCalendarBridgeClient } from './calendar'; + * + * const agentId = process.env.AGENT_ID || 'unknown'; + * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); + * + * // Inside gateway_start or heartbeat tick: + * const result = await calendar.heartbeat(); + * if (result?.slots.length) { /* handle pending slots /\ } + */ +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); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/plugin/calendar/index.js.map b/plugin/calendar/index.js.map new file mode 100644 index 0000000..88da539 --- /dev/null +++ b/plugin/calendar/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;;;;;;;;;;;;;;AAEH,0CAAwB;AACxB,oDAAkC"} \ No newline at end of file diff --git a/plugin/calendar/index.ts b/plugin/calendar/index.ts new file mode 100644 index 0000000..e25866a --- /dev/null +++ b/plugin/calendar/index.ts @@ -0,0 +1,23 @@ +/** + * HarborForge Calendar — Plugin Module + * + * PLG-CAL-001: Calendar heartbeat request/response format definition. + * + * Exports: + * • Types for heartbeat request/response and slot update + * • CalendarBridgeClient — HTTP client for backend communication + * • createCalendarBridgeClient — factory from plugin API context + * + * Usage in plugin/index.ts: + * import { createCalendarBridgeClient } from './calendar'; + * + * const agentId = process.env.AGENT_ID || 'unknown'; + * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); + * + * // Inside gateway_start or heartbeat tick: + * const result = await calendar.heartbeat(); + * if (result?.slots.length) { /* handle pending slots /\ } + */ + +export * from './types'; +export * from './calendar-bridge'; diff --git a/plugin/calendar/types.d.ts b/plugin/calendar/types.d.ts new file mode 100644 index 0000000..4e19b4a --- /dev/null +++ b/plugin/calendar/types.d.ts @@ -0,0 +1,171 @@ +/** + * 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 new file mode 100644 index 0000000..2866d75 --- /dev/null +++ b/plugin/calendar/types.d.ts.map @@ -0,0 +1 @@ +{"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 new file mode 100644 index 0000000..2fefa40 --- /dev/null +++ b/plugin/calendar/types.js @@ -0,0 +1,58 @@ +"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 new file mode 100644 index 0000000..5c04e3e --- /dev/null +++ b/plugin/calendar/types.js.map @@ -0,0 +1 @@ +{"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/calendar/types.ts b/plugin/calendar/types.ts new file mode 100644 index 0000000..90f424e --- /dev/null +++ b/plugin/calendar/types.ts @@ -0,0 +1,198 @@ +/** + * 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 + */ + +// --------------------------------------------------------------------------- +// Enums (mirror backend enums) +// --------------------------------------------------------------------------- + +/** Slot type — mirrors backend SlotType enum */ +export enum SlotType { + WORK = 'work', + ON_CALL = 'on_call', + ENTERTAINMENT = 'entertainment', + SYSTEM = 'system', +} + +/** Slot lifecycle status — mirrors backend SlotStatus enum */ +export 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 enum EventType { + JOB = 'job', + ENTERTAINMENT = 'entertainment', + SYSTEM_EVENT = 'system_event', +} + +// --------------------------------------------------------------------------- +// Request types +// --------------------------------------------------------------------------- + +/** + * 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; +} + +// --------------------------------------------------------------------------- +// Response types +// --------------------------------------------------------------------------- + +/** + * 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; // ISO date string: "YYYY-MM-DD" + /** 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'; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +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'; + +// --------------------------------------------------------------------------- +// Slot update types (for post-execution status updates) +// --------------------------------------------------------------------------- + +/** + * 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; +} diff --git a/plugin/openclaw.plugin.json b/plugin/openclaw.plugin.json index fe9fbc9..30a3e60 100644 --- a/plugin/openclaw.plugin.json +++ b/plugin/openclaw.plugin.json @@ -8,43 +8,57 @@ "type": "object", "additionalProperties": false, "properties": { - "enabled": { - "type": "boolean", + "enabled": { + "type": "boolean", "default": true, "description": "Enable the HarborForge plugin" }, - "backendUrl": { - "type": "string", + "backendUrl": { + "type": "string", "default": "https://monitor.hangman-lab.top", - "description": "HarborForge Monitor backend URL" + "description": "HarborForge backend base URL (shared by Monitor and Calendar API)" }, - "identifier": { + "identifier": { "type": "string", - "description": "Server identifier (auto-detected from hostname if not set)" + "description": "Server/claw identifier. Used as claw_identifier in Calendar heartbeat and as MonitoredServer.identifier. Auto-detected from hostname if not set." }, - "apiKey": { + "apiKey": { "type": "string", - "description": "API Key from HarborForge Monitor admin panel (optional but required for authentication)" + "description": "API Key from HarborForge Monitor admin panel (optional but required for Monitor authentication)" }, "monitor_port": { "type": "number", "description": "Local port for communication between HarborForge Monitor and this plugin" }, - "reportIntervalSec": { - "type": "number", + "reportIntervalSec": { + "type": "number", "default": 30, "description": "How often to report metrics (seconds)" }, - "httpFallbackIntervalSec": { - "type": "number", + "httpFallbackIntervalSec": { + "type": "number", "default": 60, "description": "HTTP heartbeat interval when WS unavailable" }, - "logLevel": { - "type": "string", - "enum": ["debug", "info", "warn", "error"], + "logLevel": { + "type": "string", + "enum": ["debug", "info", "warn", "error"], "default": "info", "description": "Logging level" + }, + "calendarEnabled": { + "type": "boolean", + "default": true, + "description": "Enable Calendar heartbeat integration (PLG-CAL-001). When enabled, plugin sends periodic heartbeat to /calendar/agent/heartbeat to receive pending TimeSlots." + }, + "calendarHeartbeatIntervalSec": { + "type": "number", + "default": 60, + "description": "How often to send Calendar heartbeat to backend (seconds). Defaults to 60s (1 minute)." + }, + "calendarApiKey": { + "type": "string", + "description": "API key for Calendar API authentication. If not set, uses apiKey or plugin auto-authentication via X-Agent-ID header." } } } From 97021f97c0bda010804224832ffb6cc3f68712a9 Mon Sep 17 00:00:00 2001 From: zhi Date: Wed, 1 Apr 2026 08:45:05 +0000 Subject: [PATCH 02/15] PLG-CAL-002: Implement calendar scheduler for agent slot wakeup - Add CalendarScheduler class to manage periodic heartbeat and slot execution - Implement agent wakeup logic when Idle and slots are pending - Handle slot status transitions (attended, ongoing, deferred) - Support both real and virtual slot materialization - Add task context building for different event types (job, system, entertainment) - Integrate scheduler into main plugin index.ts - Add new plugin tools: harborforge_calendar_status, complete, abort --- plugin/calendar/index.d.ts | 18 +- plugin/calendar/index.d.ts.map | 2 +- plugin/calendar/index.js | 18 +- plugin/calendar/index.js.map | 2 +- plugin/calendar/index.ts | 18 +- plugin/calendar/scheduler.d.ts | 184 ++++++++ plugin/calendar/scheduler.d.ts.map | 1 + plugin/calendar/scheduler.js | 526 ++++++++++++++++++++++ plugin/calendar/scheduler.js.map | 1 + plugin/calendar/scheduler.ts | 670 +++++++++++++++++++++++++++++ plugin/index.ts | 274 +++++++++++- 11 files changed, 1698 insertions(+), 16 deletions(-) create mode 100644 plugin/calendar/scheduler.d.ts create mode 100644 plugin/calendar/scheduler.d.ts.map create mode 100644 plugin/calendar/scheduler.js create mode 100644 plugin/calendar/scheduler.js.map create mode 100644 plugin/calendar/scheduler.ts diff --git a/plugin/calendar/index.d.ts b/plugin/calendar/index.d.ts index 7207fc6..583f0fc 100644 --- a/plugin/calendar/index.d.ts +++ b/plugin/calendar/index.d.ts @@ -2,22 +2,32 @@ * 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 } from './calendar'; + * import { createCalendarBridgeClient, createCalendarScheduler } from './calendar'; * * const agentId = process.env.AGENT_ID || 'unknown'; * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); * - * // Inside gateway_start or heartbeat tick: - * const result = await calendar.heartbeat(); - * if (result?.slots.length) { /* handle pending slots /\ } + * 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 index 5762871..1848e1e 100644 --- a/plugin/calendar/index.d.ts.map +++ b/plugin/calendar/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,mBAAmB,CAAC"} \ No newline at end of file +{"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 index a67b291..914a672 100644 --- a/plugin/calendar/index.js +++ b/plugin/calendar/index.js @@ -3,21 +3,30 @@ * 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 } from './calendar'; + * import { createCalendarBridgeClient, createCalendarScheduler } from './calendar'; * * const agentId = process.env.AGENT_ID || 'unknown'; * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); * - * // Inside gateway_start or heartbeat tick: - * const result = await calendar.heartbeat(); - * if (result?.slots.length) { /* handle pending slots /\ } + * 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; @@ -36,4 +45,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) { 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 index 88da539..802d83b 100644 --- a/plugin/calendar/index.js.map +++ b/plugin/calendar/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;GAmBG;;;;;;;;;;;;;;;;AAEH,0CAAwB;AACxB,oDAAkC"} \ No newline at end of file +{"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/index.ts b/plugin/calendar/index.ts index e25866a..8d2bed7 100644 --- a/plugin/calendar/index.ts +++ b/plugin/calendar/index.ts @@ -2,22 +2,32 @@ * 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 } from './calendar'; + * import { createCalendarBridgeClient, createCalendarScheduler } from './calendar'; * * const agentId = process.env.AGENT_ID || 'unknown'; * const calendar = createCalendarBridgeClient(api, 'https://monitor.hangman-lab.top', agentId); * - * // Inside gateway_start or heartbeat tick: - * const result = await calendar.heartbeat(); - * if (result?.slots.length) { /* handle pending slots /\ } + * 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'; diff --git a/plugin/calendar/scheduler.d.ts b/plugin/calendar/scheduler.d.ts new file mode 100644 index 0000000..8b5129c --- /dev/null +++ b/plugin/calendar/scheduler.d.ts @@ -0,0 +1,184 @@ +/** + * HarborForge Calendar Scheduler + * + * PLG-CAL-002: Plugin-side handling for pending slot execution. + * + * 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) + * + * 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; +} +/** + * 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; +} +/** + * CalendarScheduler manages the periodic heartbeat and slot execution lifecycle. + */ +export declare class CalendarScheduler { + private config; + private state; + constructor(config: CalendarSchedulerConfig); + /** + * 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; + /** + * 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; + /** + * 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; +} +/** + * 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 new file mode 100644 index 0000000..6229d0d --- /dev/null +++ b/plugin/calendar/scheduler.d.ts.map @@ -0,0 +1 @@ +{"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 new file mode 100644 index 0000000..dfc30fc --- /dev/null +++ b/plugin/calendar/scheduler.js @@ -0,0 +1,526 @@ +"use strict"; +/** + * HarborForge Calendar Scheduler + * + * PLG-CAL-002: Plugin-side handling for pending slot execution. + * + * 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) + * + * 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 types_1 = require("./types"); +/** + * CalendarScheduler manages the periodic heartbeat and slot execution lifecycle. + */ +class CalendarScheduler { + config; + state; + constructor(config) { + this.config = { + heartbeatIntervalMs: 60000, // 1 minute default + debug: false, + ...config, + }; + this.state = { + isRunning: false, + currentSlot: null, + lastHeartbeatAt: null, + intervalHandle: null, + deferredSlotIds: new Set(), + isProcessing: false, + }; + } + /** + * 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.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; + } + 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 - 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)); + } + // Wake agent to execute selected slot + await this.executeSlot(selectedSlot); + } + /** + * 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' }); + } + // 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; + } + } + /** + * 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; + } + } + /** + * 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); + } + } + /** + * 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; + } +} +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 new file mode 100644 index 0000000..d14968f --- /dev/null +++ b/plugin/calendar/scheduler.js.map @@ -0,0 +1 @@ +{"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/scheduler.ts b/plugin/calendar/scheduler.ts new file mode 100644 index 0000000..e243c55 --- /dev/null +++ b/plugin/calendar/scheduler.ts @@ -0,0 +1,670 @@ +/** + * HarborForge Calendar Scheduler + * + * PLG-CAL-002: Plugin-side handling for pending slot execution. + * + * 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) + * + * Design reference: NEXT_WAVE_DEV_DIRECTION.md §6 (Agent wakeup mechanism) + */ + +import { + CalendarBridgeClient, +} from './calendar-bridge'; +import { + CalendarSlotResponse, + SlotStatus, + AgentStatusValue, + SlotAgentUpdate, + CalendarEventDataJob, + CalendarEventDataSystemEvent, +} 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; +} + +/** + * 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; +} + +/** + * CalendarScheduler manages the periodic heartbeat and slot execution lifecycle. + */ +export class CalendarScheduler { + private config: Required; + private state: SchedulerState; + + constructor(config: CalendarSchedulerConfig) { + this.config = { + heartbeatIntervalMs: 60000, // 1 minute default + debug: false, + ...config, + }; + this.state = { + isRunning: false, + currentSlot: null, + lastHeartbeatAt: null, + intervalHandle: null, + deferredSlotIds: new Set(), + isProcessing: false, + }; + } + + /** + * Start the calendar scheduler. + * Begins periodic heartbeat to check for pending slots. + */ + start(): void { + if (this.state.isRunning) { + this.config.logger.warn('Calendar scheduler already running'); + return; + } + + this.state.isRunning = true; + 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(): void { + 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(): Promise { + if (!this.state.isRunning) { + 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 - 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. + */ + private async handleNonIdleAgent( + slots: CalendarSlotResponse[], + agentStatus: AgentStatusValue + ): Promise { + 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. + */ + private async handleIdleAgent(slots: CalendarSlotResponse[]): Promise { + 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)); + } + + // Wake agent to execute selected slot + await this.executeSlot(selectedSlot); + } + + /** + * Execute a slot by waking the agent. + */ + private async executeSlot(slot: CalendarSlotResponse): Promise { + 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: SlotAgentUpdate = { + status: SlotStatus.ONGOING, + started_at: this.formatTime(new Date()), + }; + + let updateSuccess: boolean; + 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' }); + } + + // 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. + */ + private buildWakeContext(slot: CalendarSlotResponse): AgentWakeContext { + const isVirtual = slot.virtual_id !== null; + const slotId = this.getSlotId(slot); + + // Build task description based on event type + let taskDescription: string; + let prompt: string; + + if (slot.event_type === 'job' && slot.event_data) { + const jobData = slot.event_data as CalendarEventDataJob; + 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 as CalendarEventDataSystemEvent; + 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. + */ + private buildJobPrompt(slot: CalendarSlotResponse, jobData: CalendarEventDataJob): string { + 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. + */ + private buildSystemPrompt( + slot: CalendarSlotResponse, + sysData: CalendarEventDataSystemEvent + ): string { + 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. + */ + private buildEntertainmentPrompt(slot: CalendarSlotResponse): string { + 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. + */ + private buildGenericPrompt(slot: CalendarSlotResponse): string { + 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. + */ + private async deferSlot(slot: CalendarSlotResponse): Promise { + const update: SlotAgentUpdate = { + status: 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. + */ + private async revertSlot(slot: CalendarSlotResponse): Promise { + const update: SlotAgentUpdate = { + status: 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: number): Promise { + if (!this.state.currentSlot) { + this.config.logger.warn('No current slot to complete'); + return; + } + + const slot = this.state.currentSlot; + const update: SlotAgentUpdate = { + status: 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; + } + } + + /** + * Abort the current slot execution. + * Call this when the agent cannot complete the task. + */ + async abortCurrentSlot(reason?: string): Promise { + if (!this.state.currentSlot) { + this.config.logger.warn('No current slot to abort'); + return; + } + + const slot = this.state.currentSlot; + const update: SlotAgentUpdate = { + status: 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; + } + } + + /** + * Pause the current slot execution. + * Call this when the agent needs to temporarily pause. + */ + async pauseCurrentSlot(): Promise { + if (!this.state.currentSlot) { + this.config.logger.warn('No current slot to pause'); + return; + } + + const slot = this.state.currentSlot; + const update: SlotAgentUpdate = { + status: 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(): Promise { + if (!this.state.currentSlot) { + this.config.logger.warn('No current slot to resume'); + return; + } + + const slot = this.state.currentSlot; + const update: SlotAgentUpdate = { + status: 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); + } + } + + /** + * Get a stable ID for a slot (real or virtual). + */ + private getSlotId(slot: CalendarSlotResponse): string { + return slot.id?.toString() || slot.virtual_id || 'unknown'; + } + + /** + * Format a Date as ISO time string (HH:MM:SS). + */ + private formatTime(date: Date): string { + return date.toTimeString().split(' ')[0]; + } + + /** + * Debug logging helper. + */ + private logDebug(message: string): void { + if (this.config.debug) { + this.config.logger.debug(`[CalendarScheduler] ${message}`); + } + } + + /** + * Get current scheduler state (for introspection). + */ + getState(): Readonly { + return { ...this.state }; + } + + /** + * Check if scheduler is running. + */ + isRunning(): boolean { + return this.state.isRunning; + } + + /** + * Check if currently processing a slot. + */ + isProcessing(): boolean { + return this.state.isProcessing; + } + + /** + * Get the current slot being executed (if any). + */ + getCurrentSlot(): CalendarSlotResponse | null { + return this.state.currentSlot; + } +} + +/** + * Factory function to create a CalendarScheduler from plugin context. + */ +export function createCalendarScheduler( + config: CalendarSchedulerConfig +): CalendarScheduler { + return new CalendarScheduler(config); +} diff --git a/plugin/index.ts b/plugin/index.ts index abef652..19a1b33 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -4,6 +4,9 @@ * Provides monitor-related tools and exposes OpenClaw metadata * for the HarborForge Monitor bridge (via monitor_port). * + * Also integrates with HarborForge Calendar system to wake agents + * for scheduled tasks (PLG-CAL-002). + * * Sidecar architecture has been removed. Telemetry data is now * served directly by the plugin when Monitor queries via the * local monitor_port communication path. @@ -11,6 +14,12 @@ import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os'; import { getLivePluginConfig, type HarborForgeMonitorConfig } from './core/live-config'; import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge'; +import { + createCalendarBridgeClient, + createCalendarScheduler, + CalendarScheduler, + AgentWakeContext, +} from './calendar'; interface PluginAPI { logger: { @@ -24,6 +33,15 @@ interface PluginAPI { pluginConfig?: Record; on: (event: string, handler: () => void) => void; registerTool: (factory: (ctx: any) => any) => void; + /** Spawn a sub-agent with task context (OpenClaw 2.1+) */ + spawn?: (options: { + agentId?: string; + task: string; + model?: string; + timeoutSeconds?: number; + }) => Promise<{ sessionId: string; status: string }>; + /** Get current agent status */ + getAgentStatus?: () => Promise<{ status: string } | null>; } export default { @@ -87,7 +105,7 @@ export default { }, openclaw: { version: api.version || 'unknown', - pluginVersion: '0.2.0', + pluginVersion: '0.3.0', }, timestamp: new Date().toISOString(), }; @@ -96,6 +114,9 @@ export default { // Periodic metadata push interval handle let metaPushInterval: ReturnType | null = null; + // Calendar scheduler instance + let calendarScheduler: CalendarScheduler | null = null; + /** * Push OpenClaw metadata to the Monitor bridge. * This enriches Monitor heartbeats with OpenClaw version/plugin/agent info. @@ -107,7 +128,7 @@ export default { const meta: OpenClawMeta = { version: api.version || 'unknown', - plugin_version: '0.2.0', + plugin_version: '0.3.0', agents: [], // TODO: populate from api agent list when available }; @@ -119,6 +140,170 @@ export default { } } + /** + * Get current agent status from OpenClaw. + * Falls back to querying backend if OpenClaw API unavailable. + */ + async function getAgentStatus(): Promise<'idle' | 'on_call' | 'busy' | 'exhausted' | 'offline' | null> { + // Try OpenClaw API first (if available) + if (api.getAgentStatus) { + try { + const status = await api.getAgentStatus(); + if (status?.status) { + return status.status as 'idle' | 'on_call' | 'busy' | 'exhausted' | 'offline'; + } + } catch (err) { + logger.debug('Failed to get agent status from OpenClaw API:', err); + } + } + + // Fallback: query backend for agent status + const live = resolveConfig(); + const agentId = process.env.AGENT_ID || 'unknown'; + try { + const response = await fetch(`${live.backendUrl}/calendar/agent/status?agent_id=${agentId}`, { + headers: { + 'X-Agent-ID': agentId, + 'X-Claw-Identifier': live.identifier || hostname(), + }, + }); + if (response.ok) { + const data = await response.json(); + return data.status; + } + } catch (err) { + logger.debug('Failed to get agent status from backend:', err); + } + + return null; + } + + /** + * Wake/spawn agent with task context for slot execution. + * This is the callback invoked by CalendarScheduler when a slot is ready. + */ + async function wakeAgent(context: AgentWakeContext): Promise { + logger.info(`Waking agent for slot: ${context.taskDescription}`); + + try { + // Method 1: Use OpenClaw spawn API if available (preferred) + if (api.spawn) { + const result = await api.spawn({ + task: context.prompt, + timeoutSeconds: context.slot.estimated_duration * 60, // Convert to seconds + }); + + if (result?.sessionId) { + logger.info(`Agent spawned for calendar slot: session=${result.sessionId}`); + + // Track session completion + trackSessionCompletion(result.sessionId, context); + return true; + } + } + + // Method 2: Send notification/alert to wake agent (fallback) + // This relies on the agent's heartbeat to check for notifications + logger.warn('OpenClaw spawn API not available, using notification fallback'); + + // Send calendar wakeup notification via backend + const live = resolveConfig(); + const agentId = process.env.AGENT_ID || 'unknown'; + + const notifyResponse = await fetch(`${live.backendUrl}/calendar/agent/notify`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Agent-ID': agentId, + 'X-Claw-Identifier': live.identifier || hostname(), + }, + body: JSON.stringify({ + agent_id: agentId, + message: context.prompt, + slot_id: context.slot.id || context.slot.virtual_id, + task_description: context.taskDescription, + }), + }); + + return notifyResponse.ok; + + } catch (err) { + logger.error('Failed to wake agent:', err); + return false; + } + } + + /** + * Track session completion and update slot status accordingly. + */ + function trackSessionCompletion(sessionId: string, context: AgentWakeContext): void { + // Poll for session completion (simplified approach) + // In production, this would use webhooks or event streaming + const pollInterval = 30000; // 30 seconds + const maxDuration = context.slot.estimated_duration * 60 * 1000; // Convert to ms + const startTime = Date.now(); + + const poll = async () => { + if (!calendarScheduler) return; + + const elapsed = Date.now() - startTime; + + // Check if session is complete (would use actual API in production) + // For now, estimate completion based on duration + if (elapsed >= maxDuration) { + // Assume completion + const actualMinutes = Math.round(elapsed / 60000); + await calendarScheduler.completeCurrentSlot(actualMinutes); + return; + } + + // Continue polling + setTimeout(poll, pollInterval); + }; + + // Start polling + setTimeout(poll, pollInterval); + } + + /** + * Initialize and start the calendar scheduler. + */ + function startCalendarScheduler(): void { + const live = resolveConfig(); + const agentId = process.env.AGENT_ID || 'unknown'; + + // Create calendar bridge client + const calendarBridge = createCalendarBridgeClient( + api, + live.backendUrl || 'https://monitor.hangman-lab.top', + agentId + ); + + // Create and start scheduler + calendarScheduler = createCalendarScheduler({ + bridge: calendarBridge, + getAgentStatus, + wakeAgent, + logger, + heartbeatIntervalMs: 60000, // 1 minute + debug: live.logLevel === 'debug', + }); + + calendarScheduler.start(); + logger.info('Calendar scheduler started'); + } + + /** + * Stop the calendar scheduler. + */ + function stopCalendarScheduler(): void { + if (calendarScheduler) { + calendarScheduler.stop(); + calendarScheduler = null; + logger.info('Calendar scheduler stopped'); + } + } + api.on('gateway_start', () => { logger.info('HarborForge plugin active'); @@ -135,14 +320,22 @@ export default { () => pushMetaToMonitor(), intervalSec * 1000, ); + + // Start calendar scheduler (delayed to let everything initialize) + if (live.enabled !== false) { + setTimeout(() => startCalendarScheduler(), 5000); + } }); api.on('gateway_stop', () => { logger.info('HarborForge plugin stopping'); + if (metaPushInterval) { clearInterval(metaPushInterval); metaPushInterval = null; } + + stopCalendarScheduler(); }); // Tool: plugin status @@ -165,6 +358,13 @@ export default { : { connected: false, error: 'Monitor bridge unreachable' }; } + // Get calendar scheduler status + const calendarStatus = calendarScheduler ? { + running: calendarScheduler.isRunning(), + processing: calendarScheduler.isProcessing(), + currentSlot: calendarScheduler.getCurrentSlot(), + } : null; + return { enabled: live.enabled !== false, config: { @@ -175,6 +375,7 @@ export default { hasApiKey: Boolean(live.apiKey), }, monitorBridge, + calendar: calendarStatus, telemetry: collectTelemetry(), }; }, @@ -220,6 +421,75 @@ export default { }, })); + // Tool: calendar slot management + api.registerTool(() => ({ + name: 'harborforge_calendar_status', + description: 'Get current calendar scheduler status and pending slots', + parameters: { + type: 'object', + properties: {}, + }, + async execute() { + if (!calendarScheduler) { + return { error: 'Calendar scheduler not running' }; + } + + return { + running: calendarScheduler.isRunning(), + processing: calendarScheduler.isProcessing(), + currentSlot: calendarScheduler.getCurrentSlot(), + state: calendarScheduler.getState(), + }; + }, + })); + + // Tool: complete current slot (for agent to report completion) + api.registerTool(() => ({ + name: 'harborforge_calendar_complete', + description: 'Complete the current calendar slot with actual duration', + parameters: { + type: 'object', + properties: { + actualDurationMinutes: { + type: 'number', + description: 'Actual time spent on the task in minutes', + }, + }, + required: ['actualDurationMinutes'], + }, + async execute(params: { actualDurationMinutes: number }) { + if (!calendarScheduler) { + return { error: 'Calendar scheduler not running' }; + } + + await calendarScheduler.completeCurrentSlot(params.actualDurationMinutes); + return { success: true, message: 'Slot completed' }; + }, + })); + + // Tool: abort current slot (for agent to report failure) + api.registerTool(() => ({ + name: 'harborforge_calendar_abort', + description: 'Abort the current calendar slot', + parameters: { + type: 'object', + properties: { + reason: { + type: 'string', + description: 'Reason for aborting', + }, + }, + }, + async execute(params: { reason?: string }) { + if (!calendarScheduler) { + return { error: 'Calendar scheduler not running' }; + } + + await calendarScheduler.abortCurrentSlot(params.reason); + return { success: true, message: 'Slot aborted' }; + }, + })); + logger.info('HarborForge plugin registered (id: harbor-forge)'); }, }; From 24c4a7ad1483893e8cf834585a133e7b7aee11c1 Mon Sep 17 00:00:00 2001 From: zhi Date: Wed, 1 Apr 2026 08:52:11 +0000 Subject: [PATCH 03/15] PLG-CAL-003 fix deferred slot replanning --- plugin/calendar/scheduler.d.ts | 6 ++++++ plugin/calendar/scheduler.d.ts.map | 2 +- plugin/calendar/scheduler.js | 29 +++++++++++++++++++++++++ plugin/calendar/scheduler.js.map | 2 +- plugin/calendar/scheduler.ts | 34 ++++++++++++++++++++++++++++++ 5 files changed, 71 insertions(+), 2 deletions(-) diff --git a/plugin/calendar/scheduler.d.ts b/plugin/calendar/scheduler.d.ts index 8b5129c..07d4075 100644 --- a/plugin/calendar/scheduler.d.ts +++ b/plugin/calendar/scheduler.d.ts @@ -147,6 +147,12 @@ export declare class CalendarScheduler { * 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). */ diff --git a/plugin/calendar/scheduler.d.ts.map b/plugin/calendar/scheduler.d.ts.map index 6229d0d..376a07e 100644 --- a/plugin/calendar/scheduler.d.ts.map +++ b/plugin/calendar/scheduler.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["scheduler.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"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 index dfc30fc..940c2fb 100644 --- a/plugin/calendar/scheduler.js +++ b/plugin/calendar/scheduler.js @@ -88,6 +88,12 @@ class CalendarScheduler { 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); } @@ -191,6 +197,10 @@ class CalendarScheduler { // 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 @@ -386,6 +396,7 @@ Please use this time for the scheduled activity.`; finally { this.state.isProcessing = false; this.state.currentSlot = null; + await this.triggerReplan('slot completion'); } } /** @@ -418,6 +429,7 @@ Please use this time for the scheduled activity.`; finally { this.state.isProcessing = false; this.state.currentSlot = null; + await this.triggerReplan('slot abort'); } } /** @@ -471,6 +483,23 @@ Please use this time for the scheduled activity.`; 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). */ diff --git a/plugin/calendar/scheduler.js.map b/plugin/calendar/scheduler.js.map index d14968f..af5353a 100644 --- a/plugin/calendar/scheduler.js.map +++ b/plugin/calendar/scheduler.js.map @@ -1 +1 @@ -{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["scheduler.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"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/scheduler.ts b/plugin/calendar/scheduler.ts index e243c55..718b79e 100644 --- a/plugin/calendar/scheduler.ts +++ b/plugin/calendar/scheduler.ts @@ -167,6 +167,15 @@ export class CalendarScheduler { 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); @@ -298,6 +307,10 @@ export class CalendarScheduler { // 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 @@ -511,6 +524,7 @@ Please use this time for the scheduled activity.`; } finally { this.state.isProcessing = false; this.state.currentSlot = null; + await this.triggerReplan('slot completion'); } } @@ -548,6 +562,7 @@ Please use this time for the scheduled activity.`; } finally { this.state.isProcessing = false; this.state.currentSlot = null; + await this.triggerReplan('slot abort'); } } @@ -608,6 +623,25 @@ Please use this time for the scheduled activity.`; } } + /** + * 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 async triggerReplan(reason: string): Promise { + 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). */ From 3b0ea0ad12dd94b55f35fe8db6e094e58c603df8 Mon Sep 17 00:00:00 2001 From: zhi Date: Wed, 1 Apr 2026 09:41:02 +0000 Subject: [PATCH 04/15] PLG-CAL-004: Implement ScheduledGatewayRestart handling in plugin - Add state persistence (persistState/restoreState) for recovery after restart - Add handleScheduledGatewayRestart method that: - Persists current scheduler state to disk - Sends final heartbeat to backend before shutdown - Stops the calendar scheduler (pauses scheduled tasks) - Add isRestartPending flag to prevent new slot processing during restart - Add isScheduledGatewayRestart helper to detect restart events - Update scheduler to detect and handle ScheduledGatewayRestart events - Add new tools: harborforge_restart_status, harborforge_calendar_pause/resume - Export isRestartPending and getStateFilePath methods - Bump plugin version to 0.3.1 --- plugin/calendar/scheduler.d.ts | 45 +++++ plugin/calendar/scheduler.d.ts.map | 2 +- plugin/calendar/scheduler.js | 202 ++++++++++++++++++++- plugin/calendar/scheduler.js.map | 2 +- plugin/calendar/scheduler.ts | 275 +++++++++++++++++++++++++++-- plugin/index.ts | 71 +++++++- 6 files changed, 578 insertions(+), 19 deletions(-) diff --git a/plugin/calendar/scheduler.d.ts b/plugin/calendar/scheduler.d.ts index 07d4075..5a89747 100644 --- a/plugin/calendar/scheduler.d.ts +++ b/plugin/calendar/scheduler.d.ts @@ -2,6 +2,7 @@ * 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 @@ -9,6 +10,8 @@ * - 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) */ @@ -32,6 +35,8 @@ export interface CalendarSchedulerConfig { 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. @@ -63,6 +68,8 @@ interface SchedulerState { 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. @@ -70,7 +77,33 @@ interface SchedulerState { 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. @@ -96,6 +129,10 @@ export declare class CalendarScheduler { * 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. */ @@ -181,6 +218,14 @@ export declare class CalendarScheduler { * 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. diff --git a/plugin/calendar/scheduler.d.ts.map b/plugin/calendar/scheduler.d.ts.map index 376a07e..c946280 100644 --- a/plugin/calendar/scheduler.d.ts.map +++ b/plugin/calendar/scheduler.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["scheduler.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"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 index 940c2fb..794e246 100644 --- a/plugin/calendar/scheduler.js +++ b/plugin/calendar/scheduler.js @@ -3,6 +3,7 @@ * 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 @@ -10,25 +11,36 @@ * - 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, @@ -36,7 +48,162 @@ class CalendarScheduler { 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. @@ -48,6 +215,7 @@ class CalendarScheduler { return; } this.state.isRunning = true; + this.state.isRestartPending = false; this.config.logger.info('Calendar scheduler started'); // Run initial heartbeat immediately this.runHeartbeat(); @@ -74,6 +242,11 @@ class CalendarScheduler { 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 @@ -130,7 +303,7 @@ class CalendarScheduler { return; } // Filter out already deferred slots in this session - const eligibleSlots = slots.filter(s => !this.state.deferredSlotIds.has(this.getSlotId(s))); + 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; @@ -144,9 +317,24 @@ class CalendarScheduler { 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. */ @@ -544,6 +732,18 @@ Please use this time for the scheduled activity.`; 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; /** diff --git a/plugin/calendar/scheduler.js.map b/plugin/calendar/scheduler.js.map index af5353a..de36aaf 100644 --- a/plugin/calendar/scheduler.js.map +++ b/plugin/calendar/scheduler.js.map @@ -1 +1 @@ -{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["scheduler.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"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/scheduler.ts b/plugin/calendar/scheduler.ts index 718b79e..bb0675f 100644 --- a/plugin/calendar/scheduler.ts +++ b/plugin/calendar/scheduler.ts @@ -2,6 +2,7 @@ * 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 @@ -9,13 +10,15 @@ * - 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 { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'; +import { join, dirname } from 'path'; +import { CalendarBridgeClient } from './calendar-bridge'; import { CalendarSlotResponse, SlotStatus, @@ -43,6 +46,8 @@ export interface CalendarSchedulerConfig { heartbeatIntervalMs?: number; /** Enable verbose debug logging */ debug?: boolean; + /** Directory for state persistence (default: plugin data dir) */ + stateDir?: string; } /** @@ -60,6 +65,26 @@ export interface AgentWakeContext { isVirtual: boolean; } +/** + * Persisted state structure for recovery after restart. + */ +interface PersistedState { + /** Version for migration compatibility */ + version: number; + /** When the state was persisted */ + persistedAt: string; + /** Reason for persistence (e.g., 'ScheduledGatewayRestart') */ + reason: string; + /** The slot that was being executed when persisted */ + currentSlot: CalendarSlotResponse | null; + /** Deferred slot IDs at persistence time */ + deferredSlotIds: string[]; + /** Whether a slot was in progress */ + isProcessing: boolean; + /** Agent status at persistence time */ + agentStatus: AgentStatusValue | null; +} + /** * Current execution state tracked by the scheduler. */ @@ -76,21 +101,33 @@ interface SchedulerState { deferredSlotIds: Set; /** Whether agent is currently processing a slot */ isProcessing: boolean; + /** Whether a gateway restart is scheduled/pending */ + isRestartPending: boolean; } +/** 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. */ export class CalendarScheduler { private config: Required; private state: SchedulerState; + private stateFilePath: string; constructor(config: CalendarSchedulerConfig) { this.config = { heartbeatIntervalMs: 60000, // 1 minute default debug: false, + stateDir: this.getDefaultStateDir(), ...config, }; + + this.stateFilePath = join(this.config.stateDir, STATE_FILENAME); + this.state = { isRunning: false, currentSlot: null, @@ -98,7 +135,182 @@ export class CalendarScheduler { 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). + */ + private getDefaultStateDir(): string { + // 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, + join(process.cwd(), '.harborforge'), + join(process.cwd(), 'data'), + '/tmp/harborforge', + ]; + + for (const dir of candidates) { + if (dir) { + try { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }); + } + // Test write access + const testFile = join(dir, '.write-test'); + 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. + */ + private persistState(reason: string): void { + try { + const persistedState: 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 + }; + + 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. + */ + private restoreState(): void { + try { + if (!existsSync(this.stateFilePath)) { + return; + } + + const data = readFileSync(this.stateFilePath, 'utf-8'); + const persisted: PersistedState = 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. + */ + private clearPersistedState(): void { + try { + if (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 + 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. + */ + private async sendFinalHeartbeat(reason: string): Promise { + 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. + */ + private async handleScheduledGatewayRestart(slot: CalendarSlotResponse): Promise { + 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: SlotAgentUpdate = { + status: 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); + } } /** @@ -112,6 +324,7 @@ export class CalendarScheduler { } this.state.isRunning = true; + this.state.isRestartPending = false; this.config.logger.info('Calendar scheduler started'); // Run initial heartbeat immediately @@ -148,6 +361,12 @@ export class CalendarScheduler { 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 { @@ -159,7 +378,9 @@ export class CalendarScheduler { return; } - this.logDebug(`Heartbeat: ${response.slots.length} slots pending, agent_status=${response.agent_status}`); + 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') { @@ -178,7 +399,6 @@ export class CalendarScheduler { // Agent is idle - handle pending slots await this.handleIdleAgent(response.slots); - } catch (err) { this.config.logger.error('Heartbeat error:', err); } @@ -225,7 +445,7 @@ export class CalendarScheduler { // Filter out already deferred slots in this session const eligibleSlots = slots.filter( - s => !this.state.deferredSlotIds.has(this.getSlotId(s)) + (s) => !this.state.deferredSlotIds.has(this.getSlotId(s)) ); if (eligibleSlots.length === 0) { @@ -238,7 +458,7 @@ export class CalendarScheduler { this.config.logger.info( `Selected slot for execution: id=${this.getSlotId(selectedSlot)}, ` + - `type=${selectedSlot.slot_type}, priority=${selectedSlot.priority}` + `type=${selectedSlot.slot_type}, priority=${selectedSlot.priority}` ); // Mark remaining slots as deferred @@ -247,10 +467,27 @@ export class CalendarScheduler { 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. + */ + private isScheduledGatewayRestart(slot: CalendarSlotResponse): boolean { + if (slot.event_type !== 'system_event' || !slot.event_data) { + return false; + } + const sysData = slot.event_data as CalendarEventDataSystemEvent; + return sysData.event === 'ScheduledGatewayRestart'; + } + /** * Execute a slot by waking the agent. */ @@ -315,7 +552,6 @@ export class CalendarScheduler { // 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; @@ -361,7 +597,10 @@ export class CalendarScheduler { /** * Build prompt for job-type slots. */ - private buildJobPrompt(slot: CalendarSlotResponse, jobData: CalendarEventDataJob): string { + private buildJobPrompt( + slot: CalendarSlotResponse, + jobData: CalendarEventDataJob + ): string { const duration = slot.estimated_duration; const type = jobData.type; const code = jobData.code; @@ -518,7 +757,6 @@ Please use this time for the scheduled activity.`; 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 { @@ -556,7 +794,6 @@ Please use this time for the scheduled activity.`; this.config.logger.info( `Aborted slot ${this.getSlotId(slot)}${reason ? `: ${reason}` : ''}` ); - } catch (err) { this.config.logger.error('Failed to abort slot:', err); } finally { @@ -589,7 +826,6 @@ Please use this time for the scheduled activity.`; } this.config.logger.info(`Paused slot ${this.getSlotId(slot)}`); - } catch (err) { this.config.logger.error('Failed to pause slot:', err); } @@ -617,7 +853,6 @@ Please use this time for the scheduled activity.`; } this.config.logger.info(`Resumed slot ${this.getSlotId(slot)}`); - } catch (err) { this.config.logger.error('Failed to resume slot:', err); } @@ -692,6 +927,20 @@ Please use this time for the scheduled activity.`; getCurrentSlot(): CalendarSlotResponse | null { return this.state.currentSlot; } + + /** + * Check if a gateway restart is pending. + */ + isRestartPending(): boolean { + return this.state.isRestartPending; + } + + /** + * Get the path to the state file. + */ + getStateFilePath(): string { + return this.stateFilePath; + } } /** diff --git a/plugin/index.ts b/plugin/index.ts index 19a1b33..0e50d97 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -5,7 +5,7 @@ * for the HarborForge Monitor bridge (via monitor_port). * * Also integrates with HarborForge Calendar system to wake agents - * for scheduled tasks (PLG-CAL-002). + * for scheduled tasks (PLG-CAL-002, PLG-CAL-004). * * Sidecar architecture has been removed. Telemetry data is now * served directly by the plugin when Monitor queries via the @@ -105,7 +105,7 @@ export default { }, openclaw: { version: api.version || 'unknown', - pluginVersion: '0.3.0', + pluginVersion: '0.3.1', // Bumped for PLG-CAL-004 }, timestamp: new Date().toISOString(), }; @@ -128,7 +128,7 @@ export default { const meta: OpenClawMeta = { version: api.version || 'unknown', - plugin_version: '0.3.0', + plugin_version: '0.3.1', agents: [], // TODO: populate from api agent list when available }; @@ -363,6 +363,7 @@ export default { running: calendarScheduler.isRunning(), processing: calendarScheduler.isProcessing(), currentSlot: calendarScheduler.getCurrentSlot(), + isRestartPending: calendarScheduler.isRestartPending(), } : null; return { @@ -439,6 +440,8 @@ export default { processing: calendarScheduler.isProcessing(), currentSlot: calendarScheduler.getCurrentSlot(), state: calendarScheduler.getState(), + isRestartPending: calendarScheduler.isRestartPending(), + stateFilePath: calendarScheduler.getStateFilePath(), }; }, })); @@ -490,6 +493,68 @@ export default { }, })); + // Tool: pause current slot + api.registerTool(() => ({ + name: 'harborforge_calendar_pause', + description: 'Pause the current calendar slot', + parameters: { + type: 'object', + properties: {}, + }, + async execute() { + if (!calendarScheduler) { + return { error: 'Calendar scheduler not running' }; + } + + await calendarScheduler.pauseCurrentSlot(); + return { success: true, message: 'Slot paused' }; + }, + })); + + // Tool: resume current slot + api.registerTool(() => ({ + name: 'harborforge_calendar_resume', + description: 'Resume the paused calendar slot', + parameters: { + type: 'object', + properties: {}, + }, + async execute() { + if (!calendarScheduler) { + return { error: 'Calendar scheduler not running' }; + } + + await calendarScheduler.resumeCurrentSlot(); + return { success: true, message: 'Slot resumed' }; + }, + })); + + // Tool: check ScheduledGatewayRestart status + api.registerTool(() => ({ + name: 'harborforge_restart_status', + description: 'Check if a gateway restart is pending (PLG-CAL-004)', + parameters: { + type: 'object', + properties: {}, + }, + async execute() { + if (!calendarScheduler) { + return { error: 'Calendar scheduler not running' }; + } + + const isPending = calendarScheduler.isRestartPending(); + const stateFilePath = calendarScheduler.getStateFilePath(); + + return { + isRestartPending: isPending, + stateFilePath: stateFilePath, + message: isPending + ? 'A gateway restart has been scheduled. The scheduler has been paused.' + : 'No gateway restart is pending.', + }; + }, + })); + logger.info('HarborForge plugin registered (id: harbor-forge)'); }, }; From 038862ef8cb95ceb1c8e2d548693c2b39357155e Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 00:44:33 +0000 Subject: [PATCH 05/15] 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'); } From e6e1c5395b59c7d2b7f0ffa36a66d6cc326f4565 Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 06:54:18 +0000 Subject: [PATCH 06/15] fix: install managed monitor from temp clone --- scripts/install.mjs | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/scripts/install.mjs b/scripts/install.mjs index 4bddec3..95a835c 100644 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -112,6 +112,10 @@ function copyDir(src, dest, { exclude = [] } = {}) { } } +function shellEscape(value) { + return `'${String(value).replace(/'/g, `'"'"'`)}'`; +} + function detectEnvironment() { const totalSteps = options.installCli ? 6 : 5; logStep(1, totalSteps, 'Detecting environment...'); @@ -176,35 +180,26 @@ 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 gitRoot = exec('git config --get remote.origin.url', { cwd: projectRoot, silent: true }).trim(); + const monitorRepo = gitRoot.replace(/HarborForge\.OpenclawPlugin(\.git)?$/, 'HarborForge.Monitor.git'); + const tmpDir = join('/tmp', `harborforge-monitor-${Date.now()}`); + const monitorDestDir = join(openclawPath, 'plugins', PLUGIN_NAME, 'bin'); const binaryPath = join(monitorDestDir, 'HarborForge.Monitor'); + mkdirSync(monitorDestDir, { recursive: true }); try { - exec(`go build -o ${binaryPath} .`, { cwd: monitorDestDir, silent: !options.verbose }); + exec(`git clone --branch ${shellEscape(options.monitorBranch)} ${shellEscape(monitorRepo)} ${shellEscape(tmpDir)}`, { silent: !options.verbose }); + exec(`go build -o ${shellEscape(binaryPath)} .`, { cwd: tmpDir, 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; + } finally { + rmSync(tmpDir, { recursive: true, force: true }); } } @@ -288,7 +283,7 @@ async function install() { // Copy compiled plugin (no server directory — sidecar removed) mkdirSync(destDir, { recursive: true }); - copyDir(PLUGIN_SRC_DIR, destDir, { exclude: ['node_modules', '.git', '*.ts'] }); + copyDir(PLUGIN_SRC_DIR, destDir, { exclude: ['node_modules', '.git'] }); logOk(`Plugin files → ${destDir}`); // Copy skills (exclude hf/ unless --install-cli) @@ -376,8 +371,7 @@ async function configure() { logOk(`plugins.allow includes ${PLUGIN_NAME}`); if (options.installMonitor === 'yes') { - const projectRoot = resolve(__dirname, '..'); - const binaryPath = join(openclawPath, 'managed', 'harborforge-monitor', 'HarborForge.Monitor'); + const binaryPath = join(openclawPath, 'plugins', PLUGIN_NAME, 'bin', 'HarborForge.Monitor'); const entry = getOpenclawConfig(`plugins.entries.${PLUGIN_NAME}`, {}) || {}; const config = entry.config || {}; config.managedMonitor = binaryPath; @@ -448,7 +442,7 @@ async function uninstall() { logOk('Removed hf CLI binary'); } - const managedDir = join(openclawPath, 'managed', 'harborforge-monitor'); + const managedDir = join(openclawPath, 'plugins', PLUGIN_NAME, 'bin'); if (existsSync(managedDir)) { rmSync(managedDir, { recursive: true, force: true }); logOk('Removed managed HarborForge monitor'); From 485f8e117b07758eea44745eb17b07e3f78d8fb7 Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 07:00:03 +0000 Subject: [PATCH 07/15] fix: build monitor from cmd entrypoint --- scripts/install.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install.mjs b/scripts/install.mjs index 95a835c..ec543d8 100644 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -191,7 +191,7 @@ function installManagedMonitor(openclawPath) { mkdirSync(monitorDestDir, { recursive: true }); try { exec(`git clone --branch ${shellEscape(options.monitorBranch)} ${shellEscape(monitorRepo)} ${shellEscape(tmpDir)}`, { silent: !options.verbose }); - exec(`go build -o ${shellEscape(binaryPath)} .`, { cwd: tmpDir, silent: !options.verbose }); + exec(`go build -o ${shellEscape(binaryPath)} ./cmd/harborforge-monitor`, { cwd: tmpDir, silent: !options.verbose }); chmodSync(binaryPath, 0o755); logOk(`Managed monitor installed → ${binaryPath} (branch hint: ${options.monitorBranch})`); return binaryPath; From ff5c07a38ce2254e64bb1ddafffbd7a78c28d75b Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 07:28:32 +0000 Subject: [PATCH 08/15] fix: use explicit monitor repository url --- scripts/install.mjs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/install.mjs b/scripts/install.mjs index ec543d8..1f6a703 100644 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -30,6 +30,7 @@ const PLUGIN_NAME = 'harbor-forge'; const OLD_PLUGIN_NAME = 'harborforge-monitor'; const PLUGIN_SRC_DIR = join(__dirname, 'plugin'); const SKILLS_SRC_DIR = join(__dirname, 'skills'); +const MONITOR_REPO_URL = 'https://git.hangman-lab.top/zhi/HarborForge.Monitor.git'; const args = process.argv.slice(2); const options = { @@ -182,15 +183,12 @@ async function build() { function installManagedMonitor(openclawPath) { if (options.installMonitor !== 'yes') return null; - const projectRoot = resolve(__dirname, '..'); - const gitRoot = exec('git config --get remote.origin.url', { cwd: projectRoot, silent: true }).trim(); - const monitorRepo = gitRoot.replace(/HarborForge\.OpenclawPlugin(\.git)?$/, 'HarborForge.Monitor.git'); const tmpDir = join('/tmp', `harborforge-monitor-${Date.now()}`); const monitorDestDir = join(openclawPath, 'plugins', PLUGIN_NAME, 'bin'); const binaryPath = join(monitorDestDir, 'HarborForge.Monitor'); mkdirSync(monitorDestDir, { recursive: true }); try { - exec(`git clone --branch ${shellEscape(options.monitorBranch)} ${shellEscape(monitorRepo)} ${shellEscape(tmpDir)}`, { silent: !options.verbose }); + exec(`git clone --branch ${shellEscape(options.monitorBranch)} ${shellEscape(MONITOR_REPO_URL)} ${shellEscape(tmpDir)}`, { silent: !options.verbose }); exec(`go build -o ${shellEscape(binaryPath)} ./cmd/harborforge-monitor`, { cwd: tmpDir, silent: !options.verbose }); chmodSync(binaryPath, 0o755); logOk(`Managed monitor installed → ${binaryPath} (branch hint: ${options.monitorBranch})`); From 2262f32a0b18e1058dbc4bb99f84eb10eae9b1ec Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 07:54:07 +0000 Subject: [PATCH 09/15] feat: pass config overrides to managed monitor --- plugin/core/managed-monitor.ts | 10 +++++++++- plugin/hooks/gateway-start.ts | 4 ++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/plugin/core/managed-monitor.ts b/plugin/core/managed-monitor.ts index c2c3a67..3f543e2 100644 --- a/plugin/core/managed-monitor.ts +++ b/plugin/core/managed-monitor.ts @@ -3,7 +3,11 @@ import { existsSync } from 'fs'; export interface ManagedMonitorConfig { managedMonitor?: string; + backendUrl?: string; + identifier?: string; + apiKey?: string; monitor_port?: number; + reportIntervalSec?: number; logLevel?: string; } @@ -24,7 +28,11 @@ export function startManagedMonitor( } const args: string[] = []; - if (config.monitor_port) args.push('--port', String(config.monitor_port)); + if (config.backendUrl) args.push('--backend-url', String(config.backendUrl)); + if (config.identifier) args.push('--identifier', String(config.identifier)); + if (config.apiKey) args.push('--api-key', String(config.apiKey)); + if (config.monitor_port) args.push('--monitor-port', String(config.monitor_port)); + if (config.reportIntervalSec) args.push('--report-interval', String(config.reportIntervalSec)); if (config.logLevel) args.push('--log-level', String(config.logLevel)); monitorProcess = spawn(config.managedMonitor, args, { diff --git a/plugin/hooks/gateway-start.ts b/plugin/hooks/gateway-start.ts index 9573b3d..26aee53 100644 --- a/plugin/hooks/gateway-start.ts +++ b/plugin/hooks/gateway-start.ts @@ -16,7 +16,11 @@ export function registerGatewayStartHook(api: any, deps: { startManagedMonitor(logger, { managedMonitor: live.managedMonitor, + backendUrl: live.backendUrl, + identifier: live.identifier, + apiKey: live.apiKey, monitor_port: live.monitor_port, + reportIntervalSec: live.reportIntervalSec, logLevel: live.logLevel, }); From 3f8859424c0f1f0ae4723863f403fb059a660323 Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 08:05:50 +0000 Subject: [PATCH 10/15] refactor: remove monitor legacy compatibility --- README.md | 8 +++++--- plugin/index.ts | 7 +++---- scripts/install.mjs | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 41ed680..27fc914 100644 --- a/README.md +++ b/README.md @@ -20,8 +20,12 @@ HarborForge.OpenclawPlugin/ │ ├── openclaw.plugin.json │ ├── index.ts │ ├── core/ -│ │ ├── live-config.ts +│ │ ├── config.ts +│ │ ├── managed-monitor.ts │ │ └── monitor-bridge.ts +│ ├── hooks/ +│ │ ├── gateway-start.ts +│ │ └── gateway-stop.ts │ └── package.json ├── skills/ │ └── hf/ @@ -98,8 +102,6 @@ node scripts/install.mjs --uninstall } ``` -> 说明:`monitor_port` 是当前主字段;为兼容旧配置,插件仍接受 `monitorPort`。 - 然后重启: ```bash diff --git a/plugin/index.ts b/plugin/index.ts index 864f3ee..62c69d4 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -63,11 +63,10 @@ export default { /** * Get the monitor bridge client if monitor_port is configured. - * Legacy alias monitorPort is still accepted. */ function getBridgeClient(): MonitorBridgeClient | null { - const live = resolveConfig() as any; - const port = live.monitor_port ?? live.monitorPort; + const live = resolveConfig(); + const port = live.monitor_port; if (!port || port <= 0) return null; return new MonitorBridgeClient(port); } @@ -349,7 +348,7 @@ export default { config: { backendUrl: live.backendUrl, identifier: live.identifier || hostname(), - monitorPort: (live as any).monitor_port ?? (live as any).monitorPort ?? null, + monitorPort: live.monitor_port ?? null, reportIntervalSec: live.reportIntervalSec, hasApiKey: Boolean(live.apiKey), }, diff --git a/scripts/install.mjs b/scripts/install.mjs index 1f6a703..68e83cc 100644 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -403,7 +403,7 @@ function summary() { log('Next steps:', 'blue'); log(' 1. Register server in HarborForge Monitor to get apiKey', 'cyan'); log(' 2. Edit ~/.openclaw/openclaw.json under plugins.entries.harbor-forge.config:', 'cyan'); - log(' (prefer monitor_port; legacy monitorPort is still accepted)', 'cyan'); + log(' (monitor_port is required for the local monitor bridge)', 'cyan'); log(' {', 'cyan'); log(' "plugins": {', 'cyan'); log(' "entries": {', 'cyan'); From 9b13c6b7aa5f2048fe409d07212a02b1fa9c9407 Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 08:33:38 +0000 Subject: [PATCH 11/15] fix: only fill missing plugin config values --- scripts/install.mjs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/scripts/install.mjs b/scripts/install.mjs index 68e83cc..f7dea8b 100644 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -368,14 +368,21 @@ async function configure() { } logOk(`plugins.allow includes ${PLUGIN_NAME}`); + const enabledKey = `plugins.entries.${PLUGIN_NAME}.enabled`; + const configEnabledKey = `plugins.entries.${PLUGIN_NAME}.config.enabled`; + if (getOpenclawConfig(enabledKey, undefined) === undefined) { + setOpenclawConfig(enabledKey, true); + } + if (getOpenclawConfig(configEnabledKey, undefined) === undefined) { + setOpenclawConfig(configEnabledKey, true); + } + if (options.installMonitor === 'yes') { const binaryPath = join(openclawPath, 'plugins', PLUGIN_NAME, 'bin', '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); + const managedMonitorKey = `plugins.entries.${PLUGIN_NAME}.config.managedMonitor`; + if (getOpenclawConfig(managedMonitorKey, undefined) === undefined) { + setOpenclawConfig(managedMonitorKey, binaryPath); + } logOk(`managedMonitor configured → ${binaryPath}`); } From bcd47d8b3943e5edb0885ef8dbd25be09183d118 Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 08:55:00 +0000 Subject: [PATCH 12/15] feat: populate monitor agents from openclaw list --- plugin/core/openclaw-agents.ts | 85 ++++++++++++++++++++++++++++++++++ plugin/index.ts | 3 +- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 plugin/core/openclaw-agents.ts diff --git a/plugin/core/openclaw-agents.ts b/plugin/core/openclaw-agents.ts new file mode 100644 index 0000000..b48a3c6 --- /dev/null +++ b/plugin/core/openclaw-agents.ts @@ -0,0 +1,85 @@ +import { execFile } from 'child_process'; +import { promisify } from 'util'; + +const execFileAsync = promisify(execFile); + +export interface OpenClawAgentInfo { + name: string; + isDefault?: boolean; + identity?: string; + workspace?: string; + agentDir?: string; + model?: string; + routingRules?: number; + routing?: string; +} + +export async function listOpenClawAgents(logger?: { debug?: (...args: any[]) => void; warn?: (...args: any[]) => void }): Promise { + try { + const { stdout } = await execFileAsync('openclaw', ['agents', 'list'], { + timeout: 15000, + maxBuffer: 1024 * 1024, + }); + return parseOpenClawAgents(stdout); + } catch (err) { + logger?.warn?.('Failed to run `openclaw agents list`', err); + return []; + } +} + +export function parseOpenClawAgents(text: string): OpenClawAgentInfo[] { + const lines = text.split(/\r?\n/); + const out: OpenClawAgentInfo[] = []; + let current: OpenClawAgentInfo | null = null; + + const push = () => { + if (current) out.push(current); + current = null; + }; + + for (const raw of lines) { + const line = raw.trimEnd(); + if (!line.trim() || line.startsWith('Agents:') || line.startsWith('Routing rules map') || line.startsWith('Channel status reflects')) continue; + if (line.startsWith('- ')) { + push(); + const m = line.match(/^-\s+(.+?)(?:\s+\((default)\))?$/); + current = { + name: m?.[1] || line.slice(2).trim(), + isDefault: m?.[2] === 'default', + }; + continue; + } + if (!current) continue; + const trimmed = line.trim(); + const idx = trimmed.indexOf(':'); + if (idx === -1) continue; + const key = trimmed.slice(0, idx).trim(); + const value = trimmed.slice(idx + 1).trim(); + switch (key) { + case 'Identity': + current.identity = value; + break; + case 'Workspace': + current.workspace = value; + break; + case 'Agent dir': + current.agentDir = value; + break; + case 'Model': + current.model = value; + break; + case 'Routing rules': { + const n = Number(value); + current.routingRules = Number.isFinite(n) ? n : undefined; + break; + } + case 'Routing': + current.routing = value; + break; + default: + break; + } + } + push(); + return out; +} diff --git a/plugin/index.ts b/plugin/index.ts index 62c69d4..24f0f1d 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -14,6 +14,7 @@ import { hostname, freemem, totalmem, uptime, loadavg, platform } from 'os'; import { getPluginConfig } from './core/config'; import { MonitorBridgeClient, type OpenClawMeta } from './core/monitor-bridge'; +import { listOpenClawAgents } from './core/openclaw-agents'; import { registerGatewayStartHook } from './hooks/gateway-start'; import { registerGatewayStopHook } from './hooks/gateway-stop'; import { @@ -120,7 +121,7 @@ export default { const meta: OpenClawMeta = { version: api.version || 'unknown', plugin_version: '0.3.1', - agents: [], // TODO: populate from api agent list when available + agents: await listOpenClawAgents(logger), }; const ok = await bridgeClient.pushOpenClawMeta(meta); From f3a38d64556d914fccd7759ab92de1c999bfd2df Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 17:30:56 +0000 Subject: [PATCH 13/15] fix: verify plugin config during install --- scripts/install.mjs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/scripts/install.mjs b/scripts/install.mjs index f7dea8b..54c66e7 100644 --- a/scripts/install.mjs +++ b/scripts/install.mjs @@ -98,6 +98,14 @@ function setOpenclawConfig(key, value) { exec(`openclaw config set ${key} '${JSON.stringify(value)}' --json`, { silent: true }); } +function assertConfigValue(key, predicate, description) { + const value = getOpenclawConfig(key, undefined); + if (!predicate(value)) { + throw new Error(`Config verification failed for ${key}: ${description}`); + } + return value; +} + function unsetOpenclawConfig(key) { try { exec(`openclaw config unset ${key}`, { silent: true }); } catch {} } @@ -359,6 +367,7 @@ async function configure() { paths.push(destDir); setOpenclawConfig('plugins.load.paths', paths); } + assertConfigValue('plugins.load.paths', (value) => Array.isArray(value) && value.includes(destDir), `missing ${destDir}`); logOk(`plugins.load.paths includes ${destDir}`); const allow = getOpenclawConfig('plugins.allow', []); @@ -366,6 +375,7 @@ async function configure() { allow.push(PLUGIN_NAME); setOpenclawConfig('plugins.allow', allow); } + assertConfigValue('plugins.allow', (value) => Array.isArray(value) && value.includes(PLUGIN_NAME), `missing ${PLUGIN_NAME}`); logOk(`plugins.allow includes ${PLUGIN_NAME}`); const enabledKey = `plugins.entries.${PLUGIN_NAME}.enabled`; @@ -376,6 +386,8 @@ async function configure() { if (getOpenclawConfig(configEnabledKey, undefined) === undefined) { setOpenclawConfig(configEnabledKey, true); } + assertConfigValue(enabledKey, (value) => value === true, 'expected true'); + assertConfigValue(configEnabledKey, (value) => value === true, 'expected true'); if (options.installMonitor === 'yes') { const binaryPath = join(openclawPath, 'plugins', PLUGIN_NAME, 'bin', 'HarborForge.Monitor'); @@ -383,13 +395,15 @@ async function configure() { if (getOpenclawConfig(managedMonitorKey, undefined) === undefined) { setOpenclawConfig(managedMonitorKey, binaryPath); } + assertConfigValue(managedMonitorKey, (value) => value === binaryPath, `expected ${binaryPath}`); logOk(`managedMonitor configured → ${binaryPath}`); } logOk('Plugin configured (remember to set apiKey in plugins.entries.harbor-forge.config)'); } catch (err) { - logWarn(`Config failed: ${err.message}`); + logErr(`Config failed: ${err.message}`); + throw err; } } From 7f86073fe7010b67d2caff9277fc591fd6b30654 Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 17:58:59 +0000 Subject: [PATCH 14/15] fix: send calendar heartbeat as post --- plugin/calendar/calendar-bridge.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/calendar/calendar-bridge.ts b/plugin/calendar/calendar-bridge.ts index 07644ce..213e897 100644 --- a/plugin/calendar/calendar-bridge.ts +++ b/plugin/calendar/calendar-bridge.ts @@ -14,7 +14,7 @@ * Default backendUrl: "https://monitor.hangman-lab.top" * * Endpoints used: - * GET /calendar/agent/heartbeat — fetch pending slots + * POST /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 * @@ -77,7 +77,7 @@ export class CalendarBridgeClient { try { const response = await this.fetchJson(url, { - method: 'GET', + method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Agent-ID': this.config.agentId, From b81125db0d5876cfda53cf25987f97d676bb710c Mon Sep 17 00:00:00 2001 From: orion Date: Sat, 4 Apr 2026 18:29:50 +0000 Subject: [PATCH 15/15] fix: use patch for calendar slot agent updates --- plugin/calendar/calendar-bridge.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin/calendar/calendar-bridge.ts b/plugin/calendar/calendar-bridge.ts index 213e897..dca5cf5 100644 --- a/plugin/calendar/calendar-bridge.ts +++ b/plugin/calendar/calendar-bridge.ts @@ -107,7 +107,7 @@ export class CalendarBridgeClient { */ async updateSlot(slotId: number, update: SlotAgentUpdate): Promise { const url = `${this.baseUrl}/calendar/slots/${slotId}/agent-update`; - return this.postBoolean(url, update); + return this.sendBoolean('PATCH', url, update); } /** @@ -166,7 +166,7 @@ export class CalendarBridgeClient { claw_identifier: this.config.clawIdentifier, ...params, }; - return this.postBoolean(url, body); + return this.sendBoolean('POST', url, body); } // ------------------------------------------------------------------------- @@ -195,13 +195,13 @@ export class CalendarBridgeClient { } } - private async postBoolean(url: string, body: unknown): Promise { + private async sendBoolean(method: 'POST' | 'PATCH', url: string, body: unknown): Promise { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), this.timeoutMs); try { const response = await fetch(url, { - method: 'POST', + method, headers: { 'Content-Type': 'application/json', 'X-Agent-ID': this.config.agentId,