mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 18:18:37 +00:00
fix(ws-media): use ContentSuffix to inject images into session before run.completed
Previous approach failed because: 1. PublishOutbound with channel "ws" is silently dropped (no handler) 2. LLM strips image URLs despite instruction text 3. Post-run AddMessage races with frontend loadHistory() New approach: ContentSuffix field on RunRequest is appended to the assistant response inside the agent loop BEFORE saving to session and BEFORE emitting run.completed. This guarantees image markdown is in the session when frontend calls loadHistory(). Only affects WS channel — other channels still use ForwardMedia and their respective outbound handlers.
This commit is contained in:
1 parent
892ee8ea70
commit
9897dd77ed
3 files changed
+28
-38
No files matched your search
+20
-36
@@ -425,25 +425,21 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
outMeta := buildAnnounceOutMeta(origLocalKey)
|
||||
|
||||
// Build request before goroutine to capture msg fields.
|
||||
// WS: convert media to markdown URLs inline (no outbound channel handler).
|
||||
// Must happen BEFORE the run so the agent loop saves images in the session
|
||||
// before run.completed fires — avoids race with frontend loadHistory().
|
||||
// WS channel has no outbound handler — media converted to markdown URLs
|
||||
// and appended to the assistant response via ContentSuffix, which the
|
||||
// agent loop applies BEFORE saving to session and emitting run.completed.
|
||||
fwdMedia := msg.Media
|
||||
announceMessage := msg.Content
|
||||
wsMediaSuffix := ""
|
||||
contentSuffix := ""
|
||||
if origChannel == "ws" && len(msg.Media) > 0 {
|
||||
mdMedia := mediaToMarkdownFromPaths(msg.Media, cfg.WorkspacePath(), cfg)
|
||||
if mdMedia != "" {
|
||||
announceMessage += "\n\n[Image URLs for web display — include these EXACTLY as-is at the end of your response]" + mdMedia
|
||||
wsMediaSuffix = mdMedia
|
||||
}
|
||||
fwdMedia = nil // already embedded as markdown
|
||||
contentSuffix = mediaToMarkdownFromPaths(msg.Media, cfg.WorkspacePath(), cfg)
|
||||
fwdMedia = nil // WS: images delivered via ContentSuffix, not ForwardMedia
|
||||
}
|
||||
|
||||
announceReq := agent.RunRequest{
|
||||
SessionKey: sessionKey,
|
||||
Message: announceMessage,
|
||||
Message: msg.Content,
|
||||
ForwardMedia: fwdMedia,
|
||||
ContentSuffix: contentSuffix,
|
||||
Channel: origChannel,
|
||||
ChatID: msg.ChatID,
|
||||
PeerKind: origPeerKind,
|
||||
@@ -459,7 +455,7 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
// Handle announce asynchronously with per-session serialization.
|
||||
// The mutex ensures concurrent announces for the same session wait for
|
||||
// each other, so each reads up-to-date session history.
|
||||
go func(sessionKey, origCh, chatID, senderID, label string, meta map[string]string, req agent.RunRequest, mediaSuffix string) {
|
||||
go func(sessionKey, origCh, chatID, senderID, label string, meta map[string]string, req agent.RunRequest) {
|
||||
mu := getAnnounceMu(sessionKey)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
@@ -497,11 +493,6 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
announceContent = "" // suppress NO_REPLY text but still send media
|
||||
}
|
||||
|
||||
// Fallback: if LLM stripped the image markdown URLs, append them.
|
||||
if mediaSuffix != "" && !strings.Contains(announceContent, mediaSuffix) {
|
||||
announceContent += mediaSuffix
|
||||
}
|
||||
|
||||
outMsg := bus.OutboundMessage{
|
||||
Channel: origCh,
|
||||
ChatID: chatID,
|
||||
@@ -515,7 +506,7 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
})
|
||||
}
|
||||
msgBus.PublishOutbound(outMsg)
|
||||
}(sessionKey, origChannel, msg.ChatID, msg.SenderID, msg.Metadata["subagent_label"], outMeta, announceReq, wsMediaSuffix)
|
||||
}(sessionKey, origChannel, msg.ChatID, msg.SenderID, msg.Metadata["subagent_label"], outMeta, announceReq)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -569,23 +560,20 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
// Build outbound metadata for topic/thread routing.
|
||||
outMeta := buildAnnounceOutMeta(origLocalKey)
|
||||
|
||||
// WS: convert media to markdown URLs inline (same as subagent announce).
|
||||
// WS channel has no outbound handler — media injected into session after run.
|
||||
// WS channel has no outbound handler — media delivered via ContentSuffix.
|
||||
fwdMedia := msg.Media
|
||||
announceMessage := msg.Content
|
||||
wsMediaSuffix := ""
|
||||
contentSuffix := ""
|
||||
if origChannel == "ws" && len(msg.Media) > 0 {
|
||||
mdMedia := mediaToMarkdownFromPaths(msg.Media, cfg.WorkspacePath(), cfg)
|
||||
if mdMedia != "" {
|
||||
announceMessage += "\n\n[Image URLs for web display — include these EXACTLY as-is at the end of your response]" + mdMedia
|
||||
wsMediaSuffix = mdMedia
|
||||
}
|
||||
fwdMedia = nil
|
||||
contentSuffix = mediaToMarkdownFromPaths(msg.Media, cfg.WorkspacePath(), cfg)
|
||||
fwdMedia = nil // WS: images delivered via ContentSuffix, not ForwardMedia
|
||||
}
|
||||
|
||||
announceReq := agent.RunRequest{
|
||||
SessionKey: sessionKey,
|
||||
Message: announceMessage,
|
||||
Message: msg.Content,
|
||||
ForwardMedia: fwdMedia,
|
||||
ContentSuffix: contentSuffix,
|
||||
Channel: origChannel,
|
||||
ChatID: msg.ChatID,
|
||||
PeerKind: origPeerKind,
|
||||
@@ -600,7 +588,7 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
}
|
||||
|
||||
// Same per-session serialization as subagent announce above.
|
||||
go func(sessionKey, origCh, chatID, senderID string, meta map[string]string, req agent.RunRequest, mediaSuffix string) {
|
||||
go func(sessionKey, origCh, chatID, senderID string, meta map[string]string, req agent.RunRequest) {
|
||||
mu := getAnnounceMu(sessionKey)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
@@ -626,16 +614,12 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
slog.Info("delegate announce: suppressed silent/empty reply", "delegation", senderID)
|
||||
return
|
||||
}
|
||||
|
||||
announceContent := outcome.Result.Content
|
||||
if isSilent {
|
||||
announceContent = "" // suppress NO_REPLY text but still send media
|
||||
}
|
||||
|
||||
// Fallback: if LLM stripped the image markdown URLs, append them.
|
||||
if mediaSuffix != "" && !strings.Contains(announceContent, mediaSuffix) {
|
||||
announceContent += mediaSuffix
|
||||
}
|
||||
|
||||
outMsg := bus.OutboundMessage{
|
||||
Channel: origCh,
|
||||
ChatID: chatID,
|
||||
@@ -649,7 +633,7 @@ func consumeInboundMessages(ctx context.Context, msgBus *bus.MessageBus, agents
|
||||
})
|
||||
}
|
||||
msgBus.PublishOutbound(outMsg)
|
||||
}(sessionKey, origChannel, msg.ChatID, msg.SenderID, outMeta, announceReq, wsMediaSuffix)
|
||||
}(sessionKey, origChannel, msg.ChatID, msg.SenderID, outMeta, announceReq)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -877,6 +877,11 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Append content suffix (e.g. image markdown for WS) before saving to session.
|
||||
if req.ContentSuffix != "" && !strings.Contains(finalContent, req.ContentSuffix) {
|
||||
finalContent += req.ContentSuffix
|
||||
}
|
||||
|
||||
pendingMsgs = append(pendingMsgs, providers.Message{
|
||||
Role: "assistant",
|
||||
Content: finalContent,
|
||||
|
||||
@@ -276,8 +276,9 @@ type RunRequest struct {
|
||||
MaxIterations int // per-request override (0 = use agent default, must be lower)
|
||||
|
||||
// Run classification
|
||||
RunKind string // "delegation", "announce" — empty for user-initiated runs
|
||||
HideInput bool // don't persist input message in session history (announce runs)
|
||||
RunKind string // "delegation", "announce" — empty for user-initiated runs
|
||||
HideInput bool // don't persist input message in session history (announce runs)
|
||||
ContentSuffix string // appended to assistant response before saving (e.g. image markdown for WS)
|
||||
|
||||
// Delegation context (set when running as a delegate agent)
|
||||
DelegationID string // delegation ID for event correlation
|
||||
|
||||
Reference in new issue
Block a user