fix(telegram): clean up subagent tool feedback
This commit is contained in:
parent
5292ddf325
commit
fdbca6d56d
6 changed files with 131 additions and 0 deletions
|
|
@ -49,3 +49,12 @@ func (a *channelManagerAdapter) DismissToolFeedback(
|
|||
) {
|
||||
a.inner.DismissToolFeedback(ctx, channel, chatID, outboundCtx)
|
||||
}
|
||||
|
||||
func (a *channelManagerAdapter) DismissToolFeedbackForSession(
|
||||
ctx context.Context,
|
||||
channel, chatID string,
|
||||
outboundCtx *bus.InboundContext,
|
||||
sessionKey string,
|
||||
) {
|
||||
a.inner.DismissToolFeedbackForSession(ctx, channel, chatID, outboundCtx, sessionKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,4 +51,16 @@ type ChannelManager interface {
|
|||
// outboundCtx carries topic/thread info needed for channels that use
|
||||
// scoped tracker keys (e.g., Telegram forum topics); may be nil.
|
||||
DismissToolFeedback(ctx context.Context, channel, chatID string, outboundCtx *bus.InboundContext)
|
||||
|
||||
// DismissToolFeedbackForSession clears a session-scoped tool feedback
|
||||
// message. This is used for background sub-turns whose progress messages
|
||||
// are visible in the originating chat, but whose final result is delivered
|
||||
// asynchronously through the parent turn instead of as a direct channel
|
||||
// response.
|
||||
DismissToolFeedbackForSession(
|
||||
ctx context.Context,
|
||||
channel, chatID string,
|
||||
outboundCtx *bus.InboundContext,
|
||||
sessionKey string,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -447,6 +447,17 @@ func spawnSubTurn(
|
|||
|
||||
// Result Delivery Strategy (Async vs Sync)
|
||||
if cfg.Async {
|
||||
if al != nil && al.channelManager != nil && childTS.channel != "" {
|
||||
dismissCtx, dismissCancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
al.channelManager.DismissToolFeedbackForSession(
|
||||
dismissCtx,
|
||||
childTS.channel,
|
||||
childTS.chatID,
|
||||
childTS.opts.InboundContext,
|
||||
childID,
|
||||
)
|
||||
dismissCancel()
|
||||
}
|
||||
deliverSubTurnResult(al, parentTS, childID, result)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -206,6 +206,32 @@ func dismissTrackedToolFeedbackMessage(
|
|||
}
|
||||
}
|
||||
|
||||
func dismissTrackedToolFeedbackMessageForSession(
|
||||
ctx context.Context,
|
||||
ch Channel,
|
||||
chatID string,
|
||||
outboundCtx *bus.InboundContext,
|
||||
sessionKey string,
|
||||
) {
|
||||
sessionKey = strings.TrimSpace(sessionKey)
|
||||
if sessionKey == "" {
|
||||
dismissTrackedToolFeedbackMessage(ctx, ch, chatID, outboundCtx)
|
||||
return
|
||||
}
|
||||
trackedChatID := resolveOutboundChatID(ch, chatID, outboundCtx)
|
||||
if trackedChatID == "" {
|
||||
return
|
||||
}
|
||||
trackedChatID += "#session:" + sessionKey
|
||||
if cleaner, ok := ch.(toolFeedbackMessageCleaner); ok {
|
||||
cleaner.DismissToolFeedbackMessage(ctx, trackedChatID)
|
||||
return
|
||||
}
|
||||
if tracker, ok := ch.(toolFeedbackMessageTracker); ok {
|
||||
tracker.ClearToolFeedbackMessage(trackedChatID)
|
||||
}
|
||||
}
|
||||
|
||||
func clearTrackedToolFeedbackMessage(
|
||||
ch Channel,
|
||||
chatID string,
|
||||
|
|
@ -235,6 +261,16 @@ func (m *Manager) DismissToolFeedback(
|
|||
dismissTrackedToolFeedbackMessage(ctx, ch, chatID, outboundCtx)
|
||||
}
|
||||
|
||||
func (m *Manager) DismissToolFeedbackForSession(
|
||||
ctx context.Context, channelName, chatID string, outboundCtx *bus.InboundContext, sessionKey string,
|
||||
) {
|
||||
ch, ok := m.GetChannel(channelName)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
dismissTrackedToolFeedbackMessageForSession(ctx, ch, chatID, outboundCtx, sessionKey)
|
||||
}
|
||||
|
||||
func prepareToolFeedbackMessageContent(ch Channel, content string) string {
|
||||
prepared := strings.TrimSpace(content)
|
||||
if prepared == "" {
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@ func (c *TelegramChannel) EditMessage(ctx context.Context, chatID string, messag
|
|||
|
||||
// DeleteMessage implements channels.MessageDeleter.
|
||||
func (c *TelegramChannel) DeleteMessage(ctx context.Context, chatID string, messageID string) error {
|
||||
chatID = telegramToolFeedbackDeliveryChatID(chatID)
|
||||
cid, _, err := parseTelegramChatID(chatID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -383,6 +383,7 @@ func TestSend_FinalReplyDoesNotFinalizeDifferentSessionToolFeedback(t *testing.T
|
|||
},
|
||||
}
|
||||
ch := newTestChannel(t, caller)
|
||||
ch.progress = channels.NewToolFeedbackAnimator(ch.EditMessage)
|
||||
|
||||
baseCtx := bus.InboundContext{
|
||||
Channel: "telegram",
|
||||
|
|
@ -421,6 +422,67 @@ func TestSend_FinalReplyDoesNotFinalizeDifferentSessionToolFeedback(t *testing.T
|
|||
assert.Equal(t, "1", msgID)
|
||||
}
|
||||
|
||||
func TestSend_SessionScopedToolFeedbackUpdatesExistingTelegramMessage(t *testing.T) {
|
||||
nextMessageID := 0
|
||||
caller := &stubCaller{
|
||||
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
||||
nextMessageID++
|
||||
return successResponseWithMessageID(t, nextMessageID), nil
|
||||
},
|
||||
}
|
||||
ch := newTestChannel(t, caller)
|
||||
ch.progress = channels.NewToolFeedbackAnimator(ch.EditMessage)
|
||||
|
||||
baseCtx := bus.InboundContext{
|
||||
Channel: "telegram",
|
||||
ChatID: "-1001234567890",
|
||||
TopicID: "42",
|
||||
Raw: map[string]string{
|
||||
"message_kind": "tool_feedback",
|
||||
},
|
||||
}
|
||||
|
||||
_, err := ch.Send(context.Background(), bus.OutboundMessage{
|
||||
ChatID: "-1001234567890",
|
||||
SessionKey: "subturn-1",
|
||||
Content: "Working...\n• tool: `read_file`",
|
||||
Context: baseCtx,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ids, err := ch.Send(context.Background(), bus.OutboundMessage{
|
||||
ChatID: "-1001234567890",
|
||||
SessionKey: "subturn-1",
|
||||
Content: "Working...\n• tool: `read_file`\n• tool: `mcp_gpt_researcher_deep_research`",
|
||||
Context: baseCtx,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"1"}, ids)
|
||||
require.Len(t, caller.calls, 2)
|
||||
assert.Contains(t, caller.calls[1].URL, "editMessageText")
|
||||
assert.NotContains(t, caller.calls[1].URL, "%23session")
|
||||
assert.NotContains(t, caller.calls[1].URL, "#session")
|
||||
}
|
||||
|
||||
func TestDismissToolFeedbackMessage_SessionScopedKeyDeletesTelegramMessage(t *testing.T) {
|
||||
caller := &stubCaller{
|
||||
callFn: func(ctx context.Context, url string, data *ta.RequestData) (*ta.Response, error) {
|
||||
return successResponse(t), nil
|
||||
},
|
||||
}
|
||||
ch := newTestChannel(t, caller)
|
||||
ch.RecordToolFeedbackMessage("-1001234567890/42#session:subturn-1", "7", "Working...\n• tool: `read_file`")
|
||||
|
||||
ch.DismissToolFeedbackMessage(context.Background(), "-1001234567890/42#session:subturn-1")
|
||||
|
||||
require.Len(t, caller.calls, 1)
|
||||
assert.Contains(t, caller.calls[0].URL, "deleteMessage")
|
||||
assert.NotContains(t, caller.calls[0].URL, "%23session")
|
||||
assert.NotContains(t, caller.calls[0].URL, "#session")
|
||||
_, ok := ch.currentToolFeedbackMessage("-1001234567890/42#session:subturn-1")
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
func TestFinalizeTrackedToolFeedbackMessage_StopsTrackingBeforeEdit(t *testing.T) {
|
||||
ch := newTestChannel(t, &stubCaller{
|
||||
callFn: func(context.Context, string, *ta.RequestData) (*ta.Response, error) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue