From 93681f3cf259cba18bbe27f5ebd6584ba716fbfb Mon Sep 17 00:00:00 2001 From: dj-oyu <68707227+dj-oyu@users.noreply.github.com> Date: Sun, 22 Feb 2026 02:57:13 +0900 Subject: [PATCH] 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 --- pkg/agent/loop.go | 55 +++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index efaabf917..226d274b1 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -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 { @@ -412,7 +413,8 @@ func (al *AgentLoop) ProcessHeartbeat(ctx context.Context, content, channel, cha DefaultResponse: "I've completed processing but have no response to give.", EnableSummary: false, SendResponse: false, - NoHistory: true, // Don't load session history for heartbeat + 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,32 +633,38 @@ 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 - if lastChannel := al.state.GetLastChannel(); lastChannel != "" { - // lastChannel format: "channel:chatID" - if idx := strings.Index(lastChannel, ":"); idx > 0 { - notifyChannel := lastChannel[:idx] - notifyChatID := lastChannel[idx+1:] - - // Override opts channel/chatID for status updates - opts.Channel = notifyChannel - opts.ChatID = notifyChatID - - // Send initial task notification - al.bus.PublishOutbound(bus.OutboundMessage{ - Channel: notifyChannel, - ChatID: notifyChatID, - Content: fmt.Sprintf("\U0001F916 Background task started\n%s", task.Description), - IsTaskStatus: true, - TaskID: opts.TaskID, - }) + // 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 != "" { + if idx := strings.Index(lastChannel, ":"); idx > 0 { + 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 + + // Send initial task notification + al.bus.PublishOutbound(bus.OutboundMessage{ + Channel: notifyChannel, + ChatID: notifyChatID, + Content: fmt.Sprintf("\U0001F916 Background task started\n%s", task.Description), + IsTaskStatus: true, + TaskID: opts.TaskID, + }) + } } // Use TaskID as key if available (for background tasks), else 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,