From 19786166c114baad62e2e1bf604954c9096f9a24 Mon Sep 17 00:00:00 2001 From: Goon Date: Fri, 13 Mar 2026 23:01:59 +0700 Subject: [PATCH] fix(agent): enrich tags with persisted media IDs for Discord image attachments (#179) * fix(agent): enrich tags with persisted media IDs for Discord image attachments Discord image attachments were downloaded and persisted correctly, but tags in the message content remained bare (no ID attribute), unlike and tags which get enriched with media IDs. This made it harder for the LLM to confirm image receipt and reference specific images. Add enrichImageIDs() that embeds persisted media IDs into tags, matching the existing enrichAudioIDs()/enrichVideoIDs() pattern. Iterates refs in reverse order to correctly map multiple image refs to their positional tags when users attach several images at once. Closes #178 https://claude.ai/code/session_01KkE9UxcNB8eXpqRiJHRqeB * style(agent): add missing continue in enrichImageIDs for consistency with enrichVideoIDs https://claude.ai/code/session_01KkE9UxcNB8eXpqRiJHRqeB --------- Co-authored-by: Claude Co-authored-by: Viet Tran --- internal/agent/loop.go | 8 ++++++-- internal/agent/media.go | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/internal/agent/loop.go b/internal/agent/loop.go index 319e0077..75f645e5 100644 --- a/internal/agent/loop.go +++ b/internal/agent/loop.go @@ -297,8 +297,12 @@ func (l *Loop) runLoop(ctx context.Context, req RunRequest) (*RunResult, error) l.enrichVideoIDs(messages, mediaRefs) } - // 2e. Cross-session recovery: recover stale locked tasks and notify team leads - // about orphaned pending tasks and in-progress tasks being handled by delegates. + // 2e. Enrich tags with persisted media IDs so the LLM + // knows images were received and stored (consistent with audio/video enrichment). + l.enrichImageIDs(messages, mediaRefs) + + // 2f. Cross-session recovery: notify team leads about orphaned pending tasks + // and in-progress tasks being handled by delegates. // Safe because Bước 1 (early ClaimTask) ensures running tasks are in_progress, // so only truly un-spawned tasks remain pending. if l.teamStore != nil && l.agentUUID != uuid.Nil { diff --git a/internal/agent/media.go b/internal/agent/media.go index e2c280c3..a687c68b 100644 --- a/internal/agent/media.go +++ b/internal/agent/media.go @@ -244,6 +244,45 @@ func (l *Loop) enrichVideoIDs(messages []providers.Message, refs []providers.Med messages[lastIdx].Content = content } +// enrichImageIDs updates the last user message to embed persisted media IDs +// in tags so the LLM knows images were received and stored. +// Without this, the LLM sees plain and cannot confirm image availability. +// Iterates refs in reverse order so that when multiple images are present, +// each ref maps to the correct positional tag (last ref → last tag, etc.). +func (l *Loop) enrichImageIDs(messages []providers.Message, refs []providers.MediaRef) { + if len(messages) == 0 { + return + } + lastIdx := -1 + for i := len(messages) - 1; i >= 0; i-- { + if messages[i].Role == "user" { + lastIdx = i + break + } + } + if lastIdx < 0 { + return + } + + content := messages[lastIdx].Content + // Iterate in reverse so first ref matches first tag when using LastIndex replacements. + for i := len(refs) - 1; i >= 0; i-- { + ref := refs[i] + if ref.Kind != "image" { + continue + } + idAttr := fmt.Sprintf(" id=%q", ref.ID) + + // Replace the LAST bare with + bare := "" + if idx := strings.LastIndex(content, bare); idx >= 0 { + content = content[:idx] + "" + content[idx+len(bare):] + continue + } + } + messages[lastIdx].Content = content +} + // mediaKindFromMime returns the media kind ("image", "video", "audio", "document") // based on MIME type prefix. func mediaKindFromMime(mime string) string {