/** * Multi-agent local schedule cache. * * Maintains today's schedule for all agents on this claw instance. * Synced periodically from HF backend via /calendar/sync endpoint. */ export interface CachedSlot { id: number | null; virtual_id: string | null; slot_type: string; estimated_duration: number; scheduled_at: string; status: string; priority: number; event_type: string | null; event_data: Record | null; [key: string]: unknown; } export class MultiAgentScheduleCache { /** { agentId → slots[] } */ private schedules: Map = new Map(); private lastSyncAt: Date | null = null; private cachedDate: string | null = null; /** * Replace cache with data from /calendar/sync response. */ sync(date: string, schedules: Record): void { if (this.cachedDate !== date) { this.schedules.clear(); } this.cachedDate = date; for (const [agentId, slots] of Object.entries(schedules)) { this.schedules.set(agentId, slots); } this.lastSyncAt = new Date(); } /** * Get agents that have due (overdue or current) slots. * Returns [agentId, dueSlots[]] pairs. */ getAgentsWithDueSlots(now: Date): Array<{ agentId: string; slots: CachedSlot[] }> { const results: Array<{ agentId: string; slots: CachedSlot[] }> = []; for (const [agentId, slots] of this.schedules) { const due = slots.filter((s) => { if (s.status !== 'not_started' && s.status !== 'deferred') return false; const scheduledAt = this.parseScheduledTime(s.scheduled_at); return scheduledAt !== null && scheduledAt <= now; }); if (due.length > 0) { // Sort by priority descending due.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0)); results.push({ agentId, slots: due }); } } return results; } /** * Get all agent IDs in the cache. */ getAgentIds(): string[] { return Array.from(this.schedules.keys()); } /** * Get slots for a specific agent. */ getAgentSlots(agentId: string): CachedSlot[] { return this.schedules.get(agentId) ?? []; } /** * Get cache status for debugging. */ getStatus(): { agentCount: number; totalSlots: number; lastSyncAt: string | null; cachedDate: string | null } { let totalSlots = 0; for (const slots of this.schedules.values()) totalSlots += slots.length; return { agentCount: this.schedules.size, totalSlots, lastSyncAt: this.lastSyncAt?.toISOString() ?? null, cachedDate: this.cachedDate, }; } private parseScheduledTime(scheduledAt: string): Date | null { if (/^\d{2}:\d{2}(:\d{2})?$/.test(scheduledAt)) { if (!this.cachedDate) return null; return new Date(`${this.cachedDate}T${scheduledAt}Z`); } const d = new Date(scheduledAt); return isNaN(d.getTime()) ? null : d; } }