fix: suppress duplicate heartbeat notifications with 24h TTL
Prevent heartbeat from spamming the user with repeated interview/review messages. After sending a non-silent result, further notifications are suppressed for 24 hours. Suppression resets when a real user message arrives via OnUserMessage callback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bc00371b12
commit
615a1fabc5
3 changed files with 41 additions and 8 deletions
|
|
@ -123,6 +123,9 @@ func gatewayCmd() {
|
|||
return tools.SilentResult(response)
|
||||
})
|
||||
|
||||
// Reset heartbeat suppression when a real user message arrives
|
||||
agentLoop.OnUserMessage = heartbeatService.ResetSuppression
|
||||
|
||||
channelManager, err := channels.NewManager(cfg, msgBus)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating channel manager: %v\n", err)
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ type AgentLoop struct {
|
|||
activeTasks sync.Map // sessionKey → *activeTask
|
||||
sessions *SessionTracker
|
||||
OnStateChange func() // called on plan/session/skills mutations
|
||||
OnUserMessage func() // called when a real user message is processed
|
||||
}
|
||||
|
||||
// processOptions configures how a message is processed
|
||||
|
|
@ -533,6 +534,11 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
|||
return al.processSystemMessage(ctx, msg)
|
||||
}
|
||||
|
||||
// Notify listeners that a real user message arrived (e.g. reset heartbeat suppression)
|
||||
if al.OnUserMessage != nil {
|
||||
al.OnUserMessage()
|
||||
}
|
||||
|
||||
// Expand /skill command: inject SKILL.md content into message, then continue to LLM
|
||||
var expansionCompact string
|
||||
if expanded, compact, ok := al.expandSkillCommand(msg); ok {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
const (
|
||||
minIntervalMinutes = 5
|
||||
defaultIntervalMinutes = 30
|
||||
suppressionTTL = 24 * time.Hour
|
||||
)
|
||||
|
||||
// HeartbeatHandler is the function type for handling heartbeat.
|
||||
|
|
@ -33,14 +34,15 @@ type HeartbeatHandler func(prompt, channel, chatID string) *tools.ToolResult
|
|||
|
||||
// HeartbeatService manages periodic heartbeat checks
|
||||
type HeartbeatService struct {
|
||||
workspace string
|
||||
bus *bus.MessageBus
|
||||
state *state.Manager
|
||||
handler HeartbeatHandler
|
||||
interval time.Duration
|
||||
enabled bool
|
||||
mu sync.RWMutex
|
||||
stopChan chan struct{}
|
||||
workspace string
|
||||
bus *bus.MessageBus
|
||||
state *state.Manager
|
||||
handler HeartbeatHandler
|
||||
interval time.Duration
|
||||
enabled bool
|
||||
mu sync.RWMutex
|
||||
stopChan chan struct{}
|
||||
lastNotifiedAt time.Time // when a non-silent result was last sent to user
|
||||
}
|
||||
|
||||
// NewHeartbeatService creates a new heartbeat service
|
||||
|
|
@ -76,6 +78,15 @@ func (hs *HeartbeatService) SetHandler(handler HeartbeatHandler) {
|
|||
hs.handler = handler
|
||||
}
|
||||
|
||||
// ResetSuppression clears the notification suppression so the next
|
||||
// non-silent heartbeat result will be delivered to the user again.
|
||||
// Typically called when a user message arrives.
|
||||
func (hs *HeartbeatService) ResetSuppression() {
|
||||
hs.mu.Lock()
|
||||
defer hs.mu.Unlock()
|
||||
hs.lastNotifiedAt = time.Time{}
|
||||
}
|
||||
|
||||
// Start begins the heartbeat service
|
||||
func (hs *HeartbeatService) Start() error {
|
||||
hs.mu.Lock()
|
||||
|
|
@ -205,6 +216,15 @@ func (hs *HeartbeatService) executeHeartbeat() {
|
|||
return
|
||||
}
|
||||
|
||||
// Suppress duplicate notifications within the TTL window
|
||||
hs.mu.RLock()
|
||||
suppressed := !hs.lastNotifiedAt.IsZero() && time.Since(hs.lastNotifiedAt) < suppressionTTL
|
||||
hs.mu.RUnlock()
|
||||
if suppressed {
|
||||
hs.logInfo("Heartbeat suppressed (already notified user recently)")
|
||||
return
|
||||
}
|
||||
|
||||
// Send result to user
|
||||
if result.ForUser != "" {
|
||||
hs.sendResponse(result.ForUser)
|
||||
|
|
@ -212,6 +232,10 @@ func (hs *HeartbeatService) executeHeartbeat() {
|
|||
hs.sendResponse(result.ForLLM)
|
||||
}
|
||||
|
||||
hs.mu.Lock()
|
||||
hs.lastNotifiedAt = time.Now()
|
||||
hs.mu.Unlock()
|
||||
|
||||
hs.logInfo("Heartbeat completed: %s", result.ForLLM)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue