@@ -1025,12 +1025,11 @@ func handleChatMessage(
10251025 }()
10261026
10271027 // ── Tool Tracing ───────────────────────────────────────────────
1028- // Single editable message showing live tool execution progress.
1029- // In verbose mode this shows raw tool names/results; in engaging
1030- // mode it's replaced by the narrator progress message above.
1031- var traceMsgID int
1032- var traceMu sync.Mutex
1033- traceLines := make ([]string , 0 , 8 )
1028+ // Individual messages per tool call so the user can follow the
1029+ // agent's journey step by step. Reasoning is shown before tool
1030+ // calls. All trace messages are deleted after the agent responds.
1031+ // toolMsgIDs tracks per-turn tool message IDs for editing on result.
1032+ var toolMsgIDs sync.Map // map[string]int
10341033
10351034 // truncate shortens a string for display, appending "…" if trimmed.
10361035 truncate := func (s string , max int ) string {
@@ -1040,6 +1039,15 @@ func handleChatMessage(
10401039 return s
10411040 }
10421041
1042+ // truncateWords limits text to maxWords, appending "…" if trimmed.
1043+ truncateWords := func (s string , maxWords int ) string {
1044+ words := strings .Fields (s )
1045+ if len (words ) <= maxWords {
1046+ return s
1047+ }
1048+ return strings .Join (words [:maxWords ], " " ) + "…"
1049+ }
1050+
10431051 // Collect agent run stats via the iteration callback.
10441052 var runInfo loop.IterationInfo
10451053 var allToolsMu sync.Mutex
@@ -1127,9 +1135,7 @@ func handleChatMessage(
11271135 Renderer : rend ,
11281136 ToolEventHandler : func (event string , name string , data string ) {
11291137 // Engaging mode: update the progress message with narrated tool
1130- // descriptions instead of raw traces. We skip tool_result events
1131- // here — the next tool_call will overwrite the message anyway,
1132- // keeping the chat scannable and avoiding flash edits.
1138+ // descriptions instead of raw traces.
11331139 if resolved .InteractionMode != "verbose" {
11341140 if progressMsgID != 0 && event == "tool_call" && narrator != nil {
11351141 if msg := narrator .ToolCallMessage (name , data ); msg != "" {
@@ -1138,41 +1144,43 @@ func handleChatMessage(
11381144 }
11391145 return
11401146 }
1141- traceMu .Lock ()
1142- defer traceMu .Unlock ()
1143-
1144- // Lazy-init: create the trace message on the first tool call.
1145- if traceMsgID == 0 && event == "tool_call" {
1146- if msg , err := bot .SendMessage (chatID , "🔧 …" , nil ); err == nil {
1147- traceMsgID = msg .ID
1148- } else {
1149- return
1150- }
1151- }
1152- if traceMsgID == 0 {
1153- return
1154- }
11551147
1148+ // Verbose mode: individual messages per tool call so the user
1149+ // can follow the agent's journey step by step.
11561150 switch event {
11571151 case "tool_call" :
11581152 args := truncate (data , 150 )
1159- line := fmt .Sprintf ("%s %s(%s) ⏳" , render .ToolEmoji (name ), name , args )
1160- traceLines = append (traceLines , line )
1161- bot .EditMessageText (chatID , traceMsgID , strings .Join (traceLines , "\n " ), nil )
1153+ line := fmt .Sprintf ("%s `%s` %s" , render .ToolEmoji (name ), name , args )
1154+ if msg , err := bot .SendMessage (chatID , line , nil ); err == nil {
1155+ toolMsgIDs .Store (name , msg .ID )
1156+ }
11621157
11631158 case "tool_result" :
1164- sizeLabel := fmt . Sprintf ( "%dB" , len ( data ))
1165- if len ( data ) > 1024 {
1166- sizeLabel = fmt .Sprintf ("%dKB " , len (data )/ 1024 )
1167- }
1168- if len (traceLines ) > 0 {
1169- last := traceLines [ len ( traceLines ) - 1 ]
1170- traceLines [ len ( traceLines ) - 1 ] = strings . Replace ( last , " ⏳" , " ✅ (" + sizeLabel + ")" , 1 )
1171- bot . EditMessageText ( chatID , traceMsgID , strings . Join ( traceLines , " \n " ), nil )
1159+ if msgIDVal , ok := toolMsgIDs . Load ( name ); ok {
1160+ msgID := msgIDVal .( int )
1161+ sizeLabel : = fmt .Sprintf ("%dB " , len (data ))
1162+ if len ( data ) > 1024 {
1163+ sizeLabel = fmt . Sprintf ( "%dKB" , len (data ) / 1024 )
1164+ }
1165+ bot . EditMessageText ( chatID , msgID ,
1166+ fmt . Sprintf ( "%s `%s` ✅ (%s)" , render . ToolEmoji ( name ), name , sizeLabel ), nil )
11721167 }
11731168 }
11741169 },
11751170 IterationCallback : func (info loop.IterationInfo ) {
1171+ // Pre-tool callback: show LLM reasoning before tools run.
1172+ if info .IsPreTool {
1173+ if info .ReasoningContent != "" {
1174+ reasoning := truncateWords (info .ReasoningContent , 50 )
1175+ if reasoning != "" {
1176+ bot .SendMessage (chatID , "💭 " + reasoning ,
1177+ & telegram.SendOpts {ReplyToMessageID : messageID })
1178+ }
1179+ }
1180+ return
1181+ }
1182+
1183+ // Post-tool / final-answer callback: collect tool stats.
11761184 allToolsMu .Lock ()
11771185 for _ , name := range info .ToolNames {
11781186 if _ , ok := allTools [name ]; ! ok {
@@ -1257,11 +1265,8 @@ func handleChatMessage(
12571265 // Run the agent with the full message history (multi-turn).
12581266 response , updatedMessages , err := agent .RunWithMessages (agentCtx , cs .Messages )
12591267 if err != nil {
1260- // Clean up trace message on error too.
1261- if traceMsgID != 0 {
1262- bot .DeleteMessage (chatID , traceMsgID )
1263- traceMsgID = 0
1264- }
1268+ // Clean up any tool trace messages on error.
1269+ deleteToolTraceMessages (bot , chatID , & toolMsgIDs )
12651270
12661271 // If the context was cancelled (by /stop or restart), save partial
12671272 // state and notify the user with a cancellation summary.
@@ -1314,13 +1319,8 @@ func handleChatMessage(
13141319 if response != "" {
13151320 handler .SendResponse (chatID , response , messageID )
13161321
1317- // Clean up the tool trace message — it's stale after the response.
1318- if traceMsgID != 0 {
1319- if err := bot .DeleteMessage (chatID , traceMsgID ); err != nil {
1320- log .Debug ("delete trace message failed" , "chat_id" , chatID , "msg_id" , traceMsgID , "error" , err )
1321- }
1322- traceMsgID = 0
1323- }
1322+ // Clean up all tool trace messages — they're stale after the response.
1323+ deleteToolTraceMessages (bot , chatID , & toolMsgIDs )
13241324
13251325 // Send run stats as a separate message directly via Bot.SendMessage
13261326 // (bypassing SendResponse/FormatResponse) so MarkdownV2 backtick code
@@ -1659,3 +1659,17 @@ func buttonsToMarkup(buttons [][]map[string]string) *telegram.InlineKeyboardMark
16591659 }
16601660 return markup
16611661}
1662+
1663+ // deleteToolTraceMessages deletes all individual tool trace messages for a chat.
1664+ // Used to clean up after the agent finishes (success or error).
1665+ func deleteToolTraceMessages (bot * telegram.Bot , chatID int64 , msgIDs * sync.Map ) {
1666+ msgIDs .Range (func (key , value any ) bool {
1667+ if msgID , ok := value .(int ); ok {
1668+ if err := bot .DeleteMessage (chatID , msgID ); err != nil {
1669+ fmt .Fprintf (os .Stderr , "odek telegram: delete tool message failed: %v\n " , err )
1670+ }
1671+ }
1672+ msgIDs .Delete (key )
1673+ return true
1674+ })
1675+ }
0 commit comments