- 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
262 lines
7.1 KiB
Markdown
262 lines
7.1 KiB
Markdown
# 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 |
|