Initial drop guessed the heartbeat shape; sim e2e against a running
harborforge-backend revealed the real contract is per-agent with
header auth, not server-wide with bearer:
POST /calendar/agent/heartbeat
headers: X-Agent-ID, X-Claw-Identifier
body: {claw_identifier, agent_id}
response: {slots: [Slot], agent_status, message?}
PATCH /calendar/slots/{id}/agent-update
PATCH /calendar/slots/virtual/{vid}/agent-update
body: {status, started_at?, actual_duration?}
POST /calendar/agent/status
body: {claw_identifier, agent_id, status}
Refactors:
- internal/calendar/types.go now mirrors OpenclawPlugin/calendar/
types.ts 1:1 (SlotStatus camelCase, real vs virtual slot id
discrimination, event_data shape)
- internal/calendar/bridge.go: header-based auth, per-agent method
signatures, separate UpdateRealSlot vs UpdateVirtualSlot
- internal/calendar/scheduler.go: per-agent heartbeat loop
(one HTTP call per agent per tick), highest-priority slot
selection, agent-update PATCH for terminal/non-terminal states
- SingleActiveAgentID helper for main.bestEffortAgentID
Also fix two bugs found in sim:
- bgCtx capture: AgentLister closures were capturing Init's ctx
which dies the moment MCP initialize returns; switched to
bgCtx (lifetime = plugin process)
- tools.toolRestartStatus referenced a non-existent
sch.RestartPending — HF backend has no restart endpoint per
/openapi.json, so the tool now reports last_heartbeats freshness
Scheduler logs each tick + each heartbeat outcome at info so
operators can see backend connectivity without enabling debug.
E2E against http://harborforge-backend:8000 in sim:
daemon → heartbeat → 404 "Agent not found"
(= correct endpoint, correct headers, correct body — agent just
isn't registered yet, which is expected for an untenanted
plugin)
4.1 KiB
4.1 KiB