diff --git a/pkg/agent/loop_media.go b/pkg/agent/loop_media.go index 9ab243bcc..766dc5815 100644 --- a/pkg/agent/loop_media.go +++ b/pkg/agent/loop_media.go @@ -372,7 +372,7 @@ func (al *AgentLoop) processingIndicator(ctx context.Context, channel, chatID, l } go func() { - ticker := time.NewTicker(150 * time.Millisecond) + ticker := time.NewTicker(3 * time.Second) defer ticker.Stop() frame := 0 @@ -596,8 +596,10 @@ func (al *AgentLoop) ocrPDF( return fmt.Sprintf("[file:%s]", pdfPath) } - // Track progress via stderr + // Track progress via stderr, keep last lines for error diagnosis page := 0 + var stderrTail []string + const maxTailLines = 20 scanner := bufio.NewScanner(stderrPipe) for scanner.Scan() { line := scanner.Text() @@ -605,12 +607,17 @@ func (al *AgentLoop) ocrPDF( page++ indicator.UpdateLabel(fmt.Sprintf("%s (%d/%s)...", modeLabel, page, totalStr)) } + stderrTail = append(stderrTail, line) + if len(stderrTail) > maxTailLines { + stderrTail = stderrTail[1:] + } } if waitErr := cmd.Wait(); waitErr != nil { logger.WarnCF("agent", "OCR command failed", map[string]any{ - "path": pdfPath, - "error": waitErr.Error(), + "path": pdfPath, + "error": waitErr.Error(), + "stderr": strings.Join(stderrTail, "\n"), }) return fmt.Sprintf("[file:%s]", pdfPath) } diff --git a/pkg/channels/manager_ext.go b/pkg/channels/manager_ext.go index 0c84cd07f..0c8cb5d16 100644 --- a/pkg/channels/manager_ext.go +++ b/pkg/channels/manager_ext.go @@ -18,7 +18,8 @@ const ( // for the same status/task bubble. EditMessage APIs are more rate-sensitive // than SendMessageDraft, so we throttle edits to avoid "(edited)" flicker // and API rate limit errors. Draft-based channels bypass this throttle. - statusEditInterval = 500 * time.Millisecond + // Telegram enforces ~20 messages/min per group chat, so 3s is safe. + statusEditInterval = 3 * time.Second ) // statusMsgEntry tracks a status or task message ID for later editing. @@ -85,8 +86,9 @@ func (m *Manager) handleStatusSend(ctx context.Context, name string, w *channelW if v, loaded := m.placeholders.Load(key); loaded { if entry, ok := v.(placeholderEntry); ok && entry.id != "" { if editor, ok := w.ch.(MessageEditor); ok { + // Always record edit time to prevent retry storms on 429 errors. + m.statusEditTimes.Store(key, time.Now()) if err := editor.EditMessage(ctx, msg.ChatID, entry.id, msg.Content); err == nil { - m.statusEditTimes.Store(key, time.Now()) return } } @@ -97,8 +99,8 @@ func (m *Manager) handleStatusSend(ctx context.Context, name string, w *channelW if v, loaded := m.statusMsgIDs.Load(key); loaded { if entry, ok := v.(statusMsgEntry); ok && entry.messageID != "" { if editor, ok := w.ch.(MessageEditor); ok { + m.statusEditTimes.Store(key, time.Now()) if err := editor.EditMessage(ctx, msg.ChatID, entry.messageID, msg.Content); err == nil { - m.statusEditTimes.Store(key, time.Now()) return } } @@ -223,8 +225,8 @@ func (m *Manager) handleTaskStatusSend(ctx context.Context, name string, w *chan if v, loaded := m.taskMsgIDs.Load(taskKey); loaded { if entry, ok := v.(statusMsgEntry); ok && entry.messageID != "" { if editor, ok := w.ch.(MessageEditor); ok { + m.statusEditTimes.Store(taskKey, time.Now()) if err := editor.EditMessage(ctx, msg.ChatID, entry.messageID, msg.Content); err == nil { - m.statusEditTimes.Store(taskKey, time.Now()) return } } diff --git a/pkg/channels/telegram/telegram.go b/pkg/channels/telegram/telegram.go index d666b4b03..2e8fe2a75 100644 --- a/pkg/channels/telegram/telegram.go +++ b/pkg/channels/telegram/telegram.go @@ -305,6 +305,10 @@ func (c *TelegramChannel) sendChunk( } if _, err := c.bot.SendMessage(ctx, tgMsg); err != nil { + // Don't retry on rate limit errors — they aren't parse failures. + if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "Too Many Requests") { + return fmt.Errorf("telegram send: %w", channels.ErrTemporary) + } logParseFailed(err, params.useMarkdownV2) tgMsg.Text = params.mdFallback @@ -372,6 +376,10 @@ func (c *TelegramChannel) EditMessage(ctx context.Context, chatID string, messag } _, err = c.bot.EditMessageText(ctx, editMsg) if err != nil { + // Don't retry on rate limit errors — they aren't parse failures. + if strings.Contains(err.Error(), "429") || strings.Contains(err.Error(), "Too Many Requests") { + return err + } logParseFailed(err, useMarkdownV2) _, err = c.bot.EditMessageText(ctx, tu.EditMessageText(tu.ID(cid), mid, content)) }