feat(slack): support reading image attachments (ARM-139) - #79
Conversation
Vera can now see screenshots and other image attachments shared in
Slack messages — DMs, mentions, thread follow-ups, and snapshot-based
session restarts.
What changed:
- New `SlackClient.downloadFile()` — fetches `url_private(_download)`
with the bot token (required for private Slack files).
- New `src/slack/files.ts` — `downloadSlackImages()` filters a Slack
file array to JPEG/PNG/GIF/WebP, downloads each in parallel, and
returns `ImageContent[]` ready for `session.prompt({ images })`.
Oversized files (>10 MB) and download failures are logged and skipped
rather than thrown — partial success is preferred over hard failure.
- `SlackEvent.files` extracted into a typed `SlackFile` interface
with `id`, `mimetype`, `size`, etc.
- `handler.ts` now downloads images for every prompt path
(`processEvent`, `continueSession`, `restartFromSnapshot`) and
forwards them via `session.prompt(text, { images })`.
- Empty-text guards relaxed across the webhook router and handler so
image-only messages flow through. A placeholder `(image attached)`
text is substituted when there's no body — the model needs an anchor.
- `recovery.resumeWithSnapshot()` accepts an optional `images` arg
so attachments survive the snapshot-restart handoff.
Tests: 12 new unit tests for `downloadSlackImages` covering filtering,
multi-file handling, mixed attachment types, download failures, size
caps, and unsupported mime types. All 469 existing tests still pass.
Out of scope: PDFs, videos, and other non-image attachments are
silently ignored — track separately if needed.
ARM-139 Support reading image attachments (screenshots) from Slack messages
ProblemVera currently cannot read image attachments (screenshots, photos, etc.) shared in Slack messages. When a user posts a screenshot — e.g. of an error, a UI bug, a dashboard, or a design — Vera has no way to see it and must ask the user to describe or paste the content as text. This is a major gap. Screenshots are one of the most common ways people share context in Slack, especially for bug reports and design feedback. Expected behaviorWhen a user sends Vera a Slack message containing one or more image attachments:
Should work for:
Out of scope (for now)
Notes
Reproduction
Research NotesGood news — most of the plumbing is already in place.
Entry Points
Definition of Done
Test Cases
Edge Cases & Gotchas
Implementation PromptAdd image-attachment support to the Slack handler in |
Closes ARM-139.
What
Vera can now see screenshots and other image attachments shared in Slack — DMs,
@-mentions, thread follow-ups, and snapshot-based session restarts. Previously she had no way to access them and had to ask the user to describe images in text.How
The pi SDK already supports vision input via
session.prompt(text, { images })and Vera already runs onclaude-opus-4-6(multimodal). The SlackSlackEvent.filesfield was already populated by the webhook router. The only thing missing was the consumer that downloaded the files and threaded them through. This PR adds it.Changes
SlackClient.downloadFile()(src/slack/client.ts) — fetchesurl_private_downloadwithAuthorization: Bearer <bot_token>, which is required for private Slack files (a plain fetch returns the HTML login page).src/slack/files.ts— newdownloadSlackImages()util:SlackEvent.filesextracted into a typedSlackFileinterface withid,mimetype,size, etc.handler.ts—processEvent,continueSession, andrestartFromSnapshotnow download images and pass them viasession.prompt(text, { images }). A smallbuildPromptInputs()helper centralizes the logic.(image attached)is substituted — the model needs some text anchor.recovery.resumeWithSnapshot()now accepts an optionalimagesarg so attachments survive the snapshot-restart handoff.Testing
src/slack/files.test.tscover:url_private_download→url_privateimage/svg+xml,image/heic)bun typecheck,biome check: clean.How to verify in Slack
@verain a channel with text + a screenshot — she should answer using both..pdfalongside an image — the PDF is silently skipped, the image is processed.Out of scope
PDFs, videos, and other non-image attachments are silently ignored — tracked separately if needed.
🤖 Generated with Vera