# 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 |