feat: schedule cache, workflow-aligned prompts, dispatchInbound wakeup

1. ScheduleCache: local cache of today's schedule, synced every 5 min
   from HF backend via new getDaySchedule() API

2. Wakeup prompts updated to reference daily-routine skill workflows
   (task-handson, plan-schedule, slot-complete)

3. Agent wakeup via dispatchInboundMessageWithDispatcher (in-process)
   - Same mechanism as Discord plugin
   - Creates unique session per slot: agent:{agentId}:hf-calendar:slot-{slotId}
   - No WebSocket, CLI, or cron dependency
   - Verified working on test environment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
operator
2026-04-19 08:10:39 +00:00
parent 248adfaafd
commit 0dd70ec2da
6 changed files with 349 additions and 54 deletions

112
REFACTOR_PLAN.md Normal file
View File

@@ -0,0 +1,112 @@
# CalendarScheduler Refactor Plan
## Current Design
```
Every 60s:
heartbeat() → POST /calendar/agent/heartbeat → returns pending slots
if idle → select highest priority → executeSlot → wakeAgent(spawn)
if busy → defer all pending slots
```
**Problems:**
1. Every heartbeat queries backend for pending slots — no local awareness of full schedule
2. Cannot detect slots assigned by other agents between heartbeats
3. 60s interval is too frequent for sync but too infrequent for precise wakeup
4. Wakeup via `api.spawn()` creates a plain session, not a Discord private channel
## Target Design
```
Every 5-10 min (sync interval):
syncSchedule() → GET /calendar/day → update local today cache
Every 30s (check interval):
checkDueSlots() → scan local cache for due slots
if due slot found:
confirmAgentStatus() → GET /calendar/agent/status
if not busy → wakeAgent (via Dirigent moderator bot private channel)
```
## Changes Required
### 1. Add Local Schedule Cache
New class `ScheduleCache`:
```typescript
class ScheduleCache {
private slots: Map<string, CalendarSlotResponse>; // slotId → slot
private lastSyncAt: Date | null;
async sync(bridge: CalendarBridgeClient): Promise<void>; // fetch today's full schedule
getDueSlots(now: Date): CalendarSlotResponse[]; // scheduled_at <= now && NOT_STARTED/DEFERRED
updateSlot(id: string, update: Partial<CalendarSlotResponse>): void; // local update
getAll(): CalendarSlotResponse[];
}
```
### 2. Add CalendarBridgeClient.getDaySchedule()
New endpoint call:
```typescript
async getDaySchedule(date: string): Promise<CalendarSlotResponse[]>
// GET /calendar/day?date=YYYY-MM-DD
```
This fetches ALL slots for the day, not just pending ones. The existing `heartbeat()` only returns NOT_STARTED/DEFERRED.
### 3. Split Heartbeat into Sync + Check
**Replace** single `runHeartbeat()` with two intervals:
```typescript
// Sync: every 5 min — pull full schedule from backend
this.syncInterval = setInterval(() => this.runSync(), 300_000);
// Check: every 30s — scan local cache for due slots
this.checkInterval = setInterval(() => this.runCheck(), 30_000);
```
`runSync()`:
1. `bridge.getDaySchedule(today)` → update cache
2. Still send heartbeat to keep backend informed of agent liveness
`runCheck()`:
1. `cache.getDueSlots(now)` → find due slots
2. Filter out session-deferred slots
3. If agent idle → select highest priority → execute
### 4. Wakeup via Dirigent (future)
Change `wakeAgent()` to create a private Discord channel via Dirigent moderator bot instead of `api.spawn()`. This requires:
- Access to Dirigent's moderator bot token or cross-plugin API
- Creating a private channel with only the target agent
- Posting the wakeup prompt as a message
**For now:** Keep `api.spawn()` as the wakeup method. The Dirigent integration can be added later as it requires cross-plugin coordination.
## Implementation Order
1. Add `ScheduleCache` class (new file: `plugin/calendar/schedule-cache.ts`)
2. Add `getDaySchedule()` to `CalendarBridgeClient`
3. Refactor `CalendarScheduler`:
- Replace single interval with sync + check intervals
- Use cache instead of heartbeat for slot discovery
- Keep heartbeat for agent liveness reporting (reduced frequency)
4. Update state persistence for new structure
5. Keep existing wakeAgent/completion/abort/pause/resume tools unchanged
## Files to Modify
| File | Changes |
|------|---------|
| `plugin/calendar/schedule-cache.ts` | New file |
| `plugin/calendar/calendar-bridge.ts` | Add `getDaySchedule()` |
| `plugin/calendar/scheduler.ts` | Refactor heartbeat → sync + check |
| `plugin/calendar/index.ts` | Export new types |
## Risk Assessment
- **Low risk:** ScheduleCache is additive, doesn't break existing behavior
- **Medium risk:** Splitting heartbeat changes core scheduling logic
- **Mitigation:** Keep `heartbeat()` method intact, use it for liveness reporting alongside new sync