feat: add local schedule cache + periodic sync to CalendarScheduler
- New ScheduleCache class: maintains today's full schedule locally - CalendarBridgeClient.getDaySchedule(): fetch all slots for a date - Scheduler now runs two intervals: - Heartbeat (60s): existing slot execution flow (unchanged) - Sync (5min): pulls full day schedule into local cache - Exposes getScheduleCache() for tools and status reporting This enables the plugin to detect slots assigned by other agents between heartbeats and provides a complete local view of the schedule. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
109
plugin/calendar/schedule-cache.ts
Normal file
109
plugin/calendar/schedule-cache.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Local cache of today's calendar schedule.
|
||||
* Synced periodically from HF backend, checked locally for due slots.
|
||||
*/
|
||||
|
||||
import type { CalendarSlotResponse } from "./types.js";
|
||||
|
||||
export class ScheduleCache {
|
||||
private slots: Map<string, CalendarSlotResponse> = new Map();
|
||||
private lastSyncAt: Date | null = null;
|
||||
private cachedDate: string | null = null; // YYYY-MM-DD
|
||||
|
||||
/**
|
||||
* Replace the cache with a fresh schedule from backend.
|
||||
*/
|
||||
sync(date: string, slots: CalendarSlotResponse[]): void {
|
||||
// If date changed, clear old data
|
||||
if (this.cachedDate !== date) {
|
||||
this.slots.clear();
|
||||
}
|
||||
this.cachedDate = date;
|
||||
|
||||
// Merge: update existing slots, add new ones
|
||||
const incomingIds = new Set<string>();
|
||||
for (const slot of slots) {
|
||||
const id = this.getSlotId(slot);
|
||||
incomingIds.add(id);
|
||||
this.slots.set(id, slot);
|
||||
}
|
||||
|
||||
// Remove slots that no longer exist on backend (cancelled etc.)
|
||||
for (const id of this.slots.keys()) {
|
||||
if (!incomingIds.has(id)) {
|
||||
this.slots.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastSyncAt = new Date();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get slots that are due (scheduled_at <= now) and still pending.
|
||||
*/
|
||||
getDueSlots(now: Date): CalendarSlotResponse[] {
|
||||
const results: CalendarSlotResponse[] = [];
|
||||
for (const slot of this.slots.values()) {
|
||||
if (slot.status !== "not_started" && slot.status !== "deferred") continue;
|
||||
if (!slot.scheduled_at) continue;
|
||||
|
||||
const scheduledAt = this.parseScheduledTime(slot.scheduled_at);
|
||||
if (scheduledAt && scheduledAt <= now) {
|
||||
results.push(slot);
|
||||
}
|
||||
}
|
||||
// Sort by priority descending
|
||||
results.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a slot locally (e.g., after status change).
|
||||
*/
|
||||
updateSlot(slotId: string, update: Partial<CalendarSlotResponse>): void {
|
||||
const existing = this.slots.get(slotId);
|
||||
if (existing) {
|
||||
this.slots.set(slotId, { ...existing, ...update });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a slot from cache.
|
||||
*/
|
||||
removeSlot(slotId: string): void {
|
||||
this.slots.delete(slotId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all cached slots.
|
||||
*/
|
||||
getAll(): CalendarSlotResponse[] {
|
||||
return Array.from(this.slots.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cache metadata.
|
||||
*/
|
||||
getStatus(): { slotCount: number; lastSyncAt: string | null; cachedDate: string | null } {
|
||||
return {
|
||||
slotCount: this.slots.size,
|
||||
lastSyncAt: this.lastSyncAt?.toISOString() ?? null,
|
||||
cachedDate: this.cachedDate,
|
||||
};
|
||||
}
|
||||
|
||||
private getSlotId(slot: CalendarSlotResponse): string {
|
||||
return slot.virtual_id ?? String(slot.id);
|
||||
}
|
||||
|
||||
private parseScheduledTime(scheduledAt: string): Date | null {
|
||||
// scheduled_at can be "HH:MM:SS" (time only) or full ISO
|
||||
if (/^\d{2}:\d{2}(:\d{2})?$/.test(scheduledAt)) {
|
||||
// Time-only: combine with cached date
|
||||
if (!this.cachedDate) return null;
|
||||
return new Date(`${this.cachedDate}T${scheduledAt}Z`);
|
||||
}
|
||||
const d = new Date(scheduledAt);
|
||||
return isNaN(d.getTime()) ? null : d;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user