From 26067d2432d56cc5418b803a8b356b723855e44b Mon Sep 17 00:00:00 2001 From: viettranx Date: Sat, 28 Feb 2026 18:58:55 +0700 Subject: [PATCH] feat: enhance agent tool loop detection sensitivity, add Markdown to HTML conversion for Telegram media captions, and refine trace detail dialog UI styling. --- internal/agent/toolloop.go | 6 +++--- internal/agent/toolloop_test.go | 9 +++++---- internal/channels/telegram/send.go | 5 +++++ ui/web/src/pages/traces/trace-detail-dialog.tsx | 4 ++-- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/internal/agent/toolloop.go b/internal/agent/toolloop.go index d6c863de..76693ef4 100644 --- a/internal/agent/toolloop.go +++ b/internal/agent/toolloop.go @@ -10,9 +10,9 @@ import ( // Tool loop detection thresholds (per-run, not per-session). const ( - toolLoopHistorySize = 30 - toolLoopWarningThreshold = 5 // inject warning into conversation - toolLoopCriticalThreshold = 10 // force stop the iteration loop + toolLoopHistorySize = 30 + toolLoopWarningThreshold = 3 // inject warning into conversation + toolLoopCriticalThreshold = 5 // force stop the iteration loop ) // toolLoopState tracks recent tool calls within a single agent run diff --git a/internal/agent/toolloop_test.go b/internal/agent/toolloop_test.go index e96528c2..525f6269 100644 --- a/internal/agent/toolloop_test.go +++ b/internal/agent/toolloop_test.go @@ -5,8 +5,8 @@ import "testing" func TestToolLoopDetection_NoLoop(t *testing.T) { var s toolLoopState - // 4 identical calls with same result → below threshold, no detection - for i := 0; i < 4; i++ { + // 2 identical calls with same result → below threshold, no detection + for i := 0; i < 2; i++ { h := s.record("list_files", map[string]interface{}{"path": "."}) s.recordResult(h, "access denied") level, _ := s.detect("list_files", h) @@ -77,7 +77,8 @@ func TestToolLoopDetection_MixedTools(t *testing.T) { var s toolLoopState // Alternate between two tools with same result → each tool only hit ~half - for i := 0; i < 15; i++ { + // With 8 iterations, each tool is called 4 times → below critical (5) + for i := 0; i < 8; i++ { toolName := "list_files" if i%2 == 1 { toolName = "read_file" @@ -85,7 +86,7 @@ func TestToolLoopDetection_MixedTools(t *testing.T) { h := s.record(toolName, map[string]interface{}{"path": "."}) s.recordResult(h, "error") level, _ := s.detect(toolName, h) - // Each tool is only called ~7-8 times, should at most warn + // Each tool is only called 4 times, should at most warn if level == "critical" { t.Fatalf("iteration %d: unexpected critical for alternating tools", i) } diff --git a/internal/channels/telegram/send.go b/internal/channels/telegram/send.go index 05a22d36..18a14ec0 100644 --- a/internal/channels/telegram/send.go +++ b/internal/channels/telegram/send.go @@ -137,6 +137,11 @@ func (c *Channel) sendMediaMessage(ctx context.Context, chatID int64, msg bus.Ou msg.Content = "" // only use for first media } + // Convert caption from markdown to Telegram HTML (same as regular messages) + if caption != "" { + caption = markdownToTelegramHTML(caption) + } + // Split caption if too long (Telegram limit: 1024 chars) var followUpText string if len(caption) > telegramCaptionMaxLen { diff --git a/ui/web/src/pages/traces/trace-detail-dialog.tsx b/ui/web/src/pages/traces/trace-detail-dialog.tsx index 0768658b..b502532f 100644 --- a/ui/web/src/pages/traces/trace-detail-dialog.tsx +++ b/ui/web/src/pages/traces/trace-detail-dialog.tsx @@ -176,7 +176,7 @@ function SpanTreeNode({ node, depth }: { node: SpanNode; depth: number }) { return (
@@ -233,7 +233,7 @@ function SpanTreeNode({ node, depth }: { node: SpanNode; depth: number }) { {/* Expanded detail panel */} {detailOpen && ( -
+
{span.model && (
Model: {span.provider}/{span.model}