From 3aa6dd2d6ec5b7f24369f7ae5f7db916818eee1a Mon Sep 17 00:00:00 2001 From: zhi Date: Sun, 19 Apr 2026 09:30:57 +0000 Subject: [PATCH] feat: add /calendar/sync endpoint for multi-agent schedule sync Returns today's slots for all agents on a claw instance, keyed by agent_id. Used by HF Plugin to maintain a local schedule cache instead of per-agent heartbeat. Also records heartbeat for all agents on the instance. Co-Authored-By: Claude Opus 4.6 (1M context) --- app/api/routers/calendar.py | 56 +++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/api/routers/calendar.py b/app/api/routers/calendar.py index d3322b0..6a88ba7 100644 --- a/app/api/routers/calendar.py +++ b/app/api/routers/calendar.py @@ -361,6 +361,62 @@ def agent_heartbeat( ) +@router.get( + "/sync", + summary="Sync today's schedules for all agents on a claw instance", +) +def sync_schedules( + x_claw_identifier: str = Header(..., alias="X-Claw-Identifier"), + db: Session = Depends(get_db), +): + """Return today's slots for all agents belonging to the given claw instance. + + Used by the HF OpenClaw plugin to maintain a local schedule cache. + Returns a dict of { agent_id: [slots] } for all agents with matching + claw_identifier. + """ + today = date_type.today() + + # Find all agents on this claw instance + agents = ( + db.query(Agent) + .filter(Agent.claw_identifier == x_claw_identifier) + .all() + ) + + schedules: dict[str, list[dict]] = {} + for agent in agents: + # Get real slots for today + real_slots = ( + db.query(TimeSlot) + .filter( + TimeSlot.user_id == agent.user_id, + TimeSlot.date == today, + TimeSlot.status.notin_(list(_INACTIVE_STATUSES)), + ) + .all() + ) + items = [_real_slot_to_item(s).model_dump(mode="json") for s in real_slots] + + # Get virtual plan slots + virtual_slots = get_virtual_slots_for_date(db, agent.user_id, today) + for vs in virtual_slots: + items.append(_virtual_slot_to_item(vs).model_dump(mode="json")) + + schedules[agent.agent_id] = items + + # Record heartbeat for liveness + for agent in agents: + record_heartbeat(db, agent) + db.commit() + + return { + "schedules": schedules, + "date": today.isoformat(), + "agent_count": len(agents), + } + + @router.patch( "/slots/{slot_id}/agent-update", response_model=TimeSlotEditResponse,