程序员阿江(Relakkes) 207da0ad9b feat(feishu): integrate inbound/outbound attachments into StreamingCard architecture
Re-implements the inbound and outbound attachment paths on top of the
StreamingCard rewrite (ae586b0) instead of the legacy MessageBuffer
pipeline. Replaces the dangling extractText reference and ports the
behavior originally drafted on feat/im-attachments to the new structure:

Inbound (user → Claude):
- handleMessage now uses extractInboundPayload to detect text and any
  PendingDownloads (image/file/file_archive/post-embedded media)
- safeMessageId captured outside enqueue to keep TS narrowing happy
- All command branches gated on `!hasAttachments` so attachment-bearing
  messages bypass /new, /clear, /stop, /projects, etc.
- Per-message Promise.allSettled download via FeishuMediaService;
  per-attachment checkAttachmentLimit; partial-failure user hint
- AttachmentRef[] forwarded through bridge.sendUserMessage(...)
- effectiveText guard prevents empty content from being sent to Claude
  when all attachments are rejected and the user supplied no text

Outbound (Claude → user):
- content_delta handler feeds the streaming text into per-chat
  ImageBlockWatcher and dispatches each new PendingUpload via
  dispatchOutboundImage (fire-and-forget)
- dispatchOutboundImage handles all three source kinds (base64/path/url),
  applies the same checkAttachmentLimit, dedupes by fingerprint, and
  publishes the image as an independent im.message.create({msg_type:'image'})
  alongside the streaming card text
- Card-embedded `{tag:'img', img_key}` rendering is intentionally deferred
  to a follow-up PR — the MVP keeps streaming text and outbound images as
  separate messages

Lifecycle: clearTransientChatState and startNewSession now also delete
imageWatchers and uploadedImageKeys for the chat so a new session never
inherits stale fingerprints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:17:09 +08:00
..