Skip to content

Commit 24f6af2

Browse files
committed
Fix tool call stuck as running when moved out of active reasoning block
AddOrUpdateToolCall only checked the active (last) reasoning block for existing tool calls. If a tool call was added to a reasoning block that later became inactive (due to new messages being appended), the lookup missed it and created a duplicate entry. The duplicate stayed stuck at ToolStatusRunning indefinitely while AddToolResult updated the original in the old reasoning block. Scan all reasoning blocks instead of only the active one, matching the approach already used by AddToolResult. Fixes #2333 Assisted-By: docker-agent
1 parent 5b0355b commit 24f6af2

2 files changed

Lines changed: 54 additions & 6 deletions

File tree

pkg/tui/components/messages/messages.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,12 +1339,16 @@ func (m *model) LoadFromSession(sess *session.Session) tea.Cmd {
13391339
}
13401340

13411341
func (m *model) AddOrUpdateToolCall(agentName string, toolCall tools.ToolCall, toolDef tools.Tool, status types.ToolStatus) tea.Cmd {
1342-
// First check if this tool call exists in an active reasoning block
1343-
if block, blockIdx := m.getActiveReasoningBlock(agentName); block != nil {
1344-
if block.HasToolCall(toolCall.ID) {
1345-
block.UpdateToolCall(toolCall.ID, status, toolCall.Function.Arguments)
1346-
m.invalidateItem(blockIdx)
1347-
return nil
1342+
// First check if this tool call exists in any reasoning block
1343+
for i := len(m.messages) - 1; i >= 0; i-- {
1344+
if m.messages[i].Type == types.MessageTypeAssistantReasoningBlock {
1345+
if block, ok := m.views[i].(*reasoningblock.Model); ok {
1346+
if block.HasToolCall(toolCall.ID) {
1347+
block.UpdateToolCall(toolCall.ID, status, toolCall.Function.Arguments)
1348+
m.invalidateItem(i)
1349+
return nil
1350+
}
1351+
}
13481352
}
13491353
}
13501354

pkg/tui/components/messages/messages_test.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,50 @@ func TestBindingsIncludesEditKeyWhenUserMessageSelected(t *testing.T) {
11141114
assert.True(t, foundE, "Bindings should include 'e' key when user message is selected")
11151115
}
11161116

1117+
func TestAddOrUpdateToolCallFindsToolInNonActiveReasoningBlock(t *testing.T) {
1118+
t.Parallel()
1119+
1120+
sessionState := &service.SessionState{}
1121+
m := NewScrollableView(80, 24, sessionState).(*model)
1122+
m.SetSize(80, 24)
1123+
1124+
agentName := "root"
1125+
toolCall := tools.ToolCall{
1126+
ID: "call_1",
1127+
Function: tools.FunctionCall{Name: "go_workspace", Arguments: `{}`},
1128+
}
1129+
toolDef := tools.Tool{Name: "go_workspace"}
1130+
1131+
// Step 1: Add a reasoning block and a tool call inside it (simulates PartialToolCallEvent)
1132+
m.AppendReasoning(agentName, "Thinking...")
1133+
require.Len(t, m.messages, 1)
1134+
assert.Equal(t, types.MessageTypeAssistantReasoningBlock, m.messages[0].Type)
1135+
1136+
m.AddOrUpdateToolCall(agentName, toolCall, toolDef, types.ToolStatusPending)
1137+
block, ok := m.views[0].(*reasoningblock.Model)
1138+
require.True(t, ok)
1139+
require.True(t, block.HasToolCall("call_1"))
1140+
1141+
// Step 2: Append an assistant message so the reasoning block is no longer the last message
1142+
m.AppendToLastMessage(agentName, "Here is the answer.")
1143+
require.Len(t, m.messages, 2)
1144+
assert.Equal(t, types.MessageTypeAssistant, m.messages[1].Type)
1145+
1146+
// Step 3: Update the tool call to Running (simulates ToolCallEvent)
1147+
// Before the fix, this would not find the tool in the old reasoning block
1148+
// and would create a duplicate standalone entry.
1149+
m.AddOrUpdateToolCall(agentName, toolCall, toolDef, types.ToolStatusRunning)
1150+
1151+
// Verify: still only 2 messages (no duplicate tool call created)
1152+
assert.Len(t, m.messages, 2, "should not create a duplicate tool call message")
1153+
1154+
// Verify the tool call in the reasoning block was updated (not duplicated)
1155+
block, ok = m.views[0].(*reasoningblock.Model)
1156+
require.True(t, ok)
1157+
assert.True(t, block.HasToolCall("call_1"))
1158+
assert.Equal(t, 1, block.ToolCount(), "reasoning block should still have exactly one tool call")
1159+
}
1160+
11171161
func TestBindingsExcludesEditKeyWhenAssistantMessageSelected(t *testing.T) {
11181162
t.Parallel()
11191163

0 commit comments

Comments
 (0)