fix: snake_case SlotStatus + scheduler debug logs
Two issues found while end-to-end testing against a running
harborforge-backend:
- SlotStatus enum values: backend stores snake_case
("not_started" / "ongoing" / …), not the camelCase the
OpenClaw plugin's TypeScript types.ts misled the initial
drop into using. Heartbeat responses came back with
Slot.Status="not_started" which the scheduler never matched
against SlotStatus("NotStarted"), so dispatchSlot never
fired. Aligned with backend's actual enum string values
(verified via heartbeat response shape).
- Added info-level logs at slot selection + dispatchSlot
entry + WakeAgent fire/result so operators can see the
plugin's decision chain in production without enabling
debug. Cheap (~one tick per agent per heartbeat interval).
E2E in sim: backend returns slots=1 → selection chosen=true →
dispatch enter → WakeAgent enqueued ok → backend slot ongoing
→ next heartbeat returns slots=0.
This commit is contained in:
@@ -158,6 +158,9 @@ func (s *Scheduler) tickForAgent(ctx context.Context, agent ReportableAgent, now
|
||||
chosen = slot
|
||||
}
|
||||
}
|
||||
s.host.Log("info", "calendar slot selection", map[string]any{
|
||||
"agent": agent.ID, "available": len(resp.Slots), "chosen": chosen != nil,
|
||||
})
|
||||
if chosen != nil {
|
||||
s.dispatchSlot(ctx, agent.ID, *chosen)
|
||||
}
|
||||
@@ -173,14 +176,19 @@ func (s *Scheduler) tickForAgent(ctx context.Context, agent ReportableAgent, now
|
||||
// transition immediately.
|
||||
func (s *Scheduler) dispatchSlot(ctx context.Context, agentID string, slot Slot) {
|
||||
ident := slot.SlotIdent()
|
||||
s.host.Log("info", "calendar dispatchSlot enter", map[string]any{
|
||||
"agent": agentID, "slot_ident": ident,
|
||||
})
|
||||
s.mu.Lock()
|
||||
if _, dup := s.activeBySlotIdent[ident]; dup {
|
||||
s.mu.Unlock()
|
||||
s.host.Log("info", "calendar dispatchSlot skipped (already active)", map[string]any{"slot": ident})
|
||||
return
|
||||
}
|
||||
if _, agentBusy := s.activeByAgentID[agentID]; agentBusy {
|
||||
// Don't pick up another slot until the current one resolves.
|
||||
s.mu.Unlock()
|
||||
s.host.Log("info", "calendar dispatchSlot skipped (agent has active slot)", map[string]any{"agent": agentID})
|
||||
return
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
@@ -191,12 +199,21 @@ func (s *Scheduler) dispatchSlot(ctx context.Context, agentID string, slot Slot)
|
||||
|
||||
message := buildWakeMessage(slot)
|
||||
source := "calendar:" + ident
|
||||
s.host.Log("info", "calendar firing WakeAgent", map[string]any{
|
||||
"agent": agentID, "slot": ident, "source": source, "msg_len": len(message),
|
||||
})
|
||||
if err := s.host.WakeAgent(ctx, sdkplugin.WakeAgentRequest{
|
||||
AgentID: agentID, Message: message, Source: source,
|
||||
}); err != nil {
|
||||
s.host.Log("warn", "calendar WakeAgent failed", map[string]any{
|
||||
"agent": agentID, "err": err.Error(),
|
||||
})
|
||||
s.resolveLocally(ident, agentID, SlotAborted, "", "wake failed: "+err.Error())
|
||||
return
|
||||
}
|
||||
s.host.Log("info", "calendar WakeAgent enqueued ok", map[string]any{
|
||||
"agent": agentID, "slot": ident,
|
||||
})
|
||||
// Mark Ongoing on the backend.
|
||||
update := SlotAgentUpdate{
|
||||
Status: SlotOngoing, StartedAt: now.Format("15:04:05"),
|
||||
|
||||
@@ -8,16 +8,18 @@ package calendar
|
||||
import "time"
|
||||
|
||||
// SlotStatus enumerates the lifecycle. String values match backend's
|
||||
// SlotStatus enum verbatim (camelCase as stored in DB).
|
||||
// SlotStatus enum verbatim (snake_case — verified via heartbeat
|
||||
// response shape against running harborforge-backend).
|
||||
type SlotStatus string
|
||||
|
||||
const (
|
||||
SlotNotStarted SlotStatus = "NotStarted"
|
||||
SlotOngoing SlotStatus = "Ongoing"
|
||||
SlotFinished SlotStatus = "Finished"
|
||||
SlotAborted SlotStatus = "Aborted"
|
||||
SlotDeferred SlotStatus = "Deferred"
|
||||
SlotPaused SlotStatus = "Paused"
|
||||
SlotNotStarted SlotStatus = "not_started"
|
||||
SlotOngoing SlotStatus = "ongoing"
|
||||
SlotFinished SlotStatus = "finished"
|
||||
SlotAborted SlotStatus = "aborted"
|
||||
SlotDeferred SlotStatus = "deferred"
|
||||
SlotPaused SlotStatus = "paused"
|
||||
SlotSkipped SlotStatus = "skipped"
|
||||
)
|
||||
|
||||
// SlotType: work vs on_call. Affects whether the agent flips to busy.
|
||||
|
||||
Reference in New Issue
Block a user