- 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
7.1 KiB
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:
config.identifier— if set in the plugin config (harbor-forge.identifier)os.hostname()— auto-detected from the machine hostname (fallback)
// 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
// 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)
{
"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)
{
"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":
{
"type": "Task",
"code": "TASK-42",
"working_sessions": ["session-id-1"]
}
When event_type == "system_event":
{
"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
{
"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
nullfrom 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
Exhaustedand not retry until theRetry-Afterheader indicates.
8. TypeScript Reference
Full type definitions are in plugin/calendar/types.ts:
// 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 |