fix(agent): enrich <media:image> tags with persisted media IDs for Discord image attachments (#179)

* fix(agent): enrich <media:image> tags with persisted media IDs for Discord image attachments

Discord image attachments were downloaded and persisted correctly, but
<media:image> tags in the message content remained bare (no ID attribute),
unlike <media:audio> and <media:video> 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 <media:image>
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 <noreply@anthropic.com>
Co-authored-by: Viet Tran <viettranx@gmail.com>
This commit is contained in:
authored and GitHub committed 2026-03-13 23:01:59 +07:00
1 parent 1a42dc93a6
commit 19786166c1
2 files changed
+45 -2

No files matched your search

+6 -2
View File
@@ -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 <media:image> 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 {
+39
View File
@@ -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 <media:image> tags so the LLM knows images were received and stored.
// Without this, the LLM sees plain <media:image> 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 <media:image> with <media:image id="uuid">
bare := "<media:image>"
if idx := strings.LastIndex(content, bare); idx >= 0 {
content = content[:idx] + "<media:image" + idAttr + ">" + 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 {