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 ec09578de3
6 changed files with 442 additions and 69 deletions

118
REFACTOR_PLAN.md Normal file
View File

@@ -0,0 +1,118 @@
# CalendarScheduler Refactor Plan (v2)
> Updated 2026-04-19 based on architecture discussion with hang
## Current Issues
1. `process.env.AGENT_ID` doesn't exist in plugins subprocess — always 'unknown'
2. Heartbeat is per-agent but should be per-claw-instance (global)
3. Scheduler only handles one agent — should manage all agents on this instance
4. wakeAgent used api.spawn (non-existent) → now uses dispatchInboundMessage (verified)
## Target Design
### Plugin State
```typescript
// Local schedule cache: { agentId → [slots] }
const schedules: Map<string, CalendarSlotResponse[]> = new Map();
```
### Sync Flow (every 5 min)
```
1. GET /calendar/sync?claw_identifier=xxx
- First call: server returns full { agentId → [slots] }
- Subsequent: server returns diff since last sync
2. Update local schedules map
3. Scan schedules for due slots:
for each agentId in schedules:
if has slot where scheduled_at <= now && status == not_started:
getAgentStatus(agentId, clawIdentifier) → busy?
if not busy → wakeAgent(agentId)
```
### Heartbeat (every 60s)
Simplified to liveness ping only:
```
POST /monitor/server/heartbeat
claw_identifier: xxx
→ server returns empty/ack
```
No slot data in heartbeat response.
### Wake Flow
```
dispatchInboundMessage:
SessionKey: agent:{agentId}:hf-wakeup
Body: "You have due slots. Follow the hf-wakeup workflow of skill hf-hangman-lab to proceed. Only reply WAKEUP_OK in this session."
Agent reads workflow → calls hf tools → sets own status to busy
```
### Agent ID Resolution
- **Sync**: agentId comes from server response (dict keys)
- **Wake**: agentId from local schedules dict key
- **Tool calls by agent**: agentId from tool ctx (same as padded-cell)
## Backend API Changes Needed
### New: GET /calendar/sync
```
GET /calendar/sync?claw_identifier=xxx
Headers: X-Claw-Identifier
Response (first call):
{
"full": true,
"schedules": {
"developer": [slot1, slot2, ...],
"operator": [slot3, ...]
},
"syncToken": "abc123"
}
Response (subsequent, with ?since=abc123):
{
"full": false,
"diff": [
{ "op": "add", "agent": "developer", "slot": {...} },
{ "op": "update", "agent": "developer", "slotId": 5, "patch": {...} },
{ "op": "remove", "agent": "operator", "slotId": 3 }
],
"syncToken": "def456"
}
```
### Existing: POST /calendar/agent/status
Keep as-is but ensure it accepts agentId + clawIdentifier as params:
```
POST /calendar/agent/status
{ agent_id, claw_identifier, status }
```
## Implementation Order
1. Backend: Add /calendar/sync endpoint
2. Plugin: Replace CalendarBridgeClient single-agent design with multi-agent
3. Plugin: Replace CalendarScheduler with new sync+check loop
4. Plugin: wakeAgent uses dispatchInboundMessage (done)
5. Plugin: Tool handlers get agentId from ctx (like padded-cell)
## Files to Change
### Backend (HarborForge.Backend)
- New route: `/calendar/sync`
- New service: schedule diff tracking per claw_identifier
### Plugin
- `plugin/calendar/calendar-bridge.ts` — remove agentId binding, add sync()
- `plugin/calendar/scheduler.ts` — rewrite to multi-agent sync+check
- `plugin/calendar/schedule-cache.ts` — already exists, adapt to multi-agent
- `plugin/index.ts` — update wakeAgent, getAgentStatus to accept agentId