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:
parent
9880cb343b
commit
942c3d3f6e
2 changed files with 43 additions and 22 deletions
|
|
@ -47,18 +47,19 @@ type AgentLoop struct {
|
||||||
hooks *HookManager
|
hooks *HookManager
|
||||||
|
|
||||||
// Runtime state
|
// Runtime state
|
||||||
running atomic.Bool
|
running atomic.Bool
|
||||||
contextManager ContextManager
|
contextManager ContextManager
|
||||||
fallback *providers.FallbackChain
|
fallback *providers.FallbackChain
|
||||||
channelManager *channels.Manager
|
channelManager *channels.Manager
|
||||||
mediaStore media.MediaStore
|
mediaStore media.MediaStore
|
||||||
transcriber asr.Transcriber
|
transcriber asr.Transcriber
|
||||||
cmdRegistry *commands.Registry
|
cmdRegistry *commands.Registry
|
||||||
mcp mcpRuntime
|
mcp mcpRuntime
|
||||||
hookRuntime hookRuntime
|
hookRuntime hookRuntime
|
||||||
steering *steeringQueue
|
steering *steeringQueue
|
||||||
pendingSkills sync.Map
|
pendingSkills sync.Map
|
||||||
mu sync.RWMutex
|
pendingDeliveries sync.Map // sessionKey -> string: last undelivered assistant content
|
||||||
|
mu sync.RWMutex
|
||||||
|
|
||||||
// Concurrent turn management (from HEAD)
|
// Concurrent turn management (from HEAD)
|
||||||
activeTurnStates sync.Map // key: sessionKey (string), value: *turnState
|
activeTurnStates sync.Map // key: sessionKey (string), value: *turnState
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -276,16 +276,17 @@ func (c *BaseChannel) HandleMessage(
|
||||||
scope := BuildMediaScope(c.name, chatID, messageID)
|
scope := BuildMediaScope(c.name, chatID, messageID)
|
||||||
|
|
||||||
msg := bus.InboundMessage{
|
msg := bus.InboundMessage{
|
||||||
Channel: c.name,
|
Channel: c.name,
|
||||||
SenderID: resolvedSenderID,
|
SenderID: resolvedSenderID,
|
||||||
Sender: sender,
|
Sender: sender,
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
Content: content,
|
Content: content,
|
||||||
Media: media,
|
Media: media,
|
||||||
Peer: peer,
|
Peer: peer,
|
||||||
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.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue