fix: background task notifications not reaching Telegram on startup

The isBackgroundTask check was using constants.IsInternalChannel(opts.Channel),
but heartbeat passes the actual user channel (e.g. "telegram") directly, not an
internal channel. This made isBackgroundTask always false for heartbeat tasks.

Now uses an explicit Background flag on processOptions, set by ProcessHeartbeat
and ProcessDirectWithChannel (via metadata). Also handles the case where the
channel is already a real channel (no need to resolve from state).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-22 02:57:13 +09:00
parent f59fb3f94d
commit 425360715a

View file

@ -95,6 +95,7 @@ type processOptions struct {
SendResponse bool // Whether to send response via bus
NoHistory bool // If true, don't load session history (for heartbeat)
TaskID string // Unique task ID for background task status tracking
Background bool // If true, this is a background task (cron/heartbeat) — enables live task notifications
}
func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers.LLMProvider, enableStats ...bool) *AgentLoop {
@ -413,6 +414,7 @@ func (al *AgentLoop) ProcessHeartbeat(ctx context.Context, content, channel, cha
EnableSummary: false,
SendResponse: false,
NoHistory: true, // Don't load session history for heartbeat
Background: true, // Enable live task notifications on Telegram
})
}
@ -530,6 +532,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
DefaultResponse: "I've completed processing but have no response to give.",
EnableSummary: true,
SendResponse: false,
Background: msg.Metadata["background"] == "true",
})
}
@ -630,18 +633,25 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
interrupt: make(chan string, 1),
}
// For background tasks (cron/cli), generate a TaskID and resolve notification channel
isBackgroundTask := constants.IsInternalChannel(opts.Channel) && al.state != nil
// For background tasks (cron/heartbeat), generate a TaskID and send notification
isBackgroundTask := opts.Background && al.state != nil
if isBackgroundTask && opts.TaskID == "" {
opts.TaskID = fmt.Sprintf("task-%s-%d", opts.SessionKey, time.Now().UnixMilli())
// Resolve user's last active channel for notifications
// Determine notification channel: use opts.Channel if already a real channel,
// otherwise resolve from last active channel
notifyChannel := opts.Channel
notifyChatID := opts.ChatID
if constants.IsInternalChannel(notifyChannel) || notifyChannel == "" {
if lastChannel := al.state.GetLastChannel(); lastChannel != "" {
// lastChannel format: "channel:chatID"
if idx := strings.Index(lastChannel, ":"); idx > 0 {
notifyChannel := lastChannel[:idx]
notifyChatID := lastChannel[idx+1:]
notifyChannel = lastChannel[:idx]
notifyChatID = lastChannel[idx+1:]
}
}
}
if notifyChannel != "" && notifyChatID != "" && !constants.IsInternalChannel(notifyChannel) {
// Override opts channel/chatID for status updates
opts.Channel = notifyChannel
opts.ChatID = notifyChatID
@ -656,7 +666,6 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
})
}
}
}
// Use TaskID as key if available (for background tasks), else sessionKey
taskKey := opts.SessionKey
@ -668,7 +677,7 @@ func (al *AgentLoop) runAgentLoop(ctx context.Context, agent *AgentInstance, opt
al.activeTasks.Delete(taskKey)
// Publish final task status on completion for background tasks
if opts.TaskID != "" && !constants.IsInternalChannel(opts.Channel) {
if opts.TaskID != "" {
elapsed := time.Since(task.StartedAt)
al.bus.PublishOutbound(bus.OutboundMessage{
Channel: opts.Channel,