fix: inject pending assistant reply into inbound turns and fill ReplyToMessageID

Gap 1: regular inbound turns now see the previous assistant reply in LLM context
even when OnDelivered hasn't fired yet (fast follow-up scenario). Achieved by
loading from pendingDeliveries sync.Map after captureRestorePoint and appending
to history before BuildMessages.

Gap 2: HandleMessage in BaseChannel now fills ReplyToMessageID from metadata so
the first-class struct field is populated for all channels that set the key in
metadata (e.g. Telegram).
This commit is contained in:
Dmitrii Balabanov 2026-03-31 17:26:04 +03:00
parent 9880cb343b
commit 942c3d3f6e
2 changed files with 43 additions and 22 deletions

View file

@ -58,6 +58,7 @@ type AgentLoop struct {
hookRuntime hookRuntime hookRuntime hookRuntime
steering *steeringQueue steering *steeringQueue
pendingSkills sync.Map pendingSkills sync.Map
pendingDeliveries sync.Map // sessionKey -> string: last undelivered assistant content
mu sync.RWMutex mu sync.RWMutex
// Concurrent turn management (from HEAD) // Concurrent turn management (from HEAD)
@ -1697,9 +1698,14 @@ func (al *AgentLoop) runAgentLoop(
} }
if !opts.NoHistory && result.finalContent != "" { if !opts.NoHistory && result.finalContent != "" {
// Register the pending content so that fast follow-up inbound turns can
// inject it into their LLM context before OnDelivered persists it.
al.pendingDeliveries.Store(opts.SessionKey, result.finalContent)
var once sync.Once var once sync.Once
response.OnDelivered = func(msgIDs []string) { response.OnDelivered = func(msgIDs []string) {
once.Do(func() { once.Do(func() {
al.pendingDeliveries.Delete(opts.SessionKey)
assistantMsg := providers.Message{ assistantMsg := providers.Message{
Role: "assistant", Role: "assistant",
Content: result.finalContent, Content: result.finalContent,
@ -1843,6 +1849,20 @@ func (al *AgentLoop) runTurn(ctx context.Context, ts *turnState) (turnResult, er
} }
ts.captureRestorePoint(history, summary) ts.captureRestorePoint(history, summary)
// Inject pending undelivered assistant reply into history so regular inbound turns
// see it in LLM context even before OnDelivered persists it to session storage.
// Only inject if history doesn't already end with an assistant message (avoids duplication
// when OnDelivered fires before the next turn starts).
if !ts.opts.NoHistory {
if pendingRaw, ok := al.pendingDeliveries.Load(ts.sessionKey); ok {
if pendingContent, _ := pendingRaw.(string); pendingContent != "" {
if len(history) == 0 || history[len(history)-1].Role != "assistant" {
history = append(history, providers.Message{Role: "assistant", Content: pendingContent})
}
}
}
}
messages := ts.agent.ContextBuilder.BuildMessages( messages := ts.agent.ContextBuilder.BuildMessages(
history, history,
summary, summary,

View file

@ -286,6 +286,7 @@ func (c *BaseChannel) HandleMessage(
MessageID: messageID, MessageID: messageID,
MediaScope: scope, MediaScope: scope,
Metadata: metadata, Metadata: metadata,
ReplyToMessageID: metadata["reply_to_message_id"],
} }
// Auto-trigger typing indicator, message reaction, and placeholder before publishing. // Auto-trigger typing indicator, message reaction, and placeholder before publishing.