* fix(discord): preserve attachment source URL in media tags (#602)
- Add SourceURL field to MediaInfo struct
- Populate SourceURL from Discord attachment URL in resolveMedia()
- Emit <media:image url="..."> in BuildMediaTags() when SourceURL is set
- Refactor enrichImageIDs/enrichImagePaths with helper functions
- Add comprehensive unit tests for media URL handling
* fix: update system prompt for url attribute + add enrichment regression tests
- System prompt now documents the url attribute in <media:image> tags
- Add TestEnrichImageIDs_BareTag for non-Discord channel enrichment
- Add TestEnrichImageIDs_SkipsAlreadyEnriched for double-enrichment safety
- Add TestEnrichImagePaths_NoDoubleEnrich for historical message safety
- Add TestEnrichImagePaths_AttributeOrderIndependence for url-before-id tags
---------
Co-authored-by: viettranx <viettranx@gmail.com>
When a user replies to a message containing images/attachments and
mentions the bot, the agent now receives media from both the replied-to
message and the current message. Reply media is tagged with
"(from replied message)" so the LLM can distinguish origin.
Changes per channel:
- Telegram: always merge reply + current media (was fallback-only)
- Discord: extract ReferencedMessage content + attachments, implicit mention
- Feishu: extend fetchReplyContext to download parent media via API
- Slack: add fetchThreadParentContext for thread parent files
- Shared: FromReply field on MediaInfo, annotated BuildMediaTags
Extract shared media utilities (MediaInfo, BuildMediaTags, TranscribeAudio,
DetectMIMEType) into internal/channels/media/ and refactor Telegram to use
them. Add full inbound/outbound media support to Discord and Feishu channels
(STT transcription, document extraction, media tags, voice agent routing).
Add WebSocket media upload/serve endpoints and MIME-aware media tags in
chat.send. Split large channel files for maintainability.