feat: US-008 - Inject callback into async tools in AgentLoop

- Update ToolRegistry.ExecuteWithContext to accept asyncCallback parameter
- Check if tool implements AsyncTool and set callback if provided
- Define asyncCallback in AgentLoop.runLLMIteration
- Callback uses bus.PublishOutbound to send async results to user
- Update Execute method to pass nil for backward compatibility
- Add debug logging for async callback injection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yinwm
2026-02-12 19:42:24 +08:00
parent 7bcd8b284f
commit 4c4c10c915
4 changed files with 59 additions and 6 deletions

View File

@@ -413,7 +413,25 @@ func (al *AgentLoop) runLLMIteration(ctx context.Context, messages []providers.M
"iteration": iteration,
})
toolResult := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, opts.Channel, opts.ChatID)
// Create async callback for tools that implement AsyncTool
// This callback sends async completion results to the user
asyncCallback := func(callbackCtx context.Context, result *tools.ToolResult) {
// Send ForUser content to user if not silent
if !result.Silent && result.ForUser != "" {
al.bus.PublishOutbound(bus.OutboundMessage{
Channel: opts.Channel,
ChatID: opts.ChatID,
Content: result.ForUser,
})
logger.InfoCF("agent", "Async tool result sent to user",
map[string]interface{}{
"tool": tc.Name,
"content_len": len(result.ForUser),
})
}
}
toolResult := al.tools.ExecuteWithContext(ctx, tc.Name, tc.Arguments, opts.Channel, opts.ChatID, asyncCallback)
lastToolResult = toolResult
// Send ForUser content to user immediately if not Silent