Fold the Kanban attachment download onto the existing gateway file-save
resolver (lib/media.ts downloadGatewayMediaFile / captureGatewayFileDownload)
instead of the source PR's second resolver (api/file-download.ts): one auth
shape, one owner-scope contract, for every Desktop gateway-file save.
- lib/media.ts: downloadGatewayMediaFile now accepts an explicit owner scope
({ connectionId, profile }) so a capture snapshot (Kanban) and the ambient
$connection path (artifacts, chat previews) share one function. Adds
downloadGatewayFileWithFeedback (the toast wrapper) and
captureGatewayFileDownload (snapshots capabilityScoped() at read time).
- store/file-actions.ts: downloadRemoteFile is now a thin call into
downloadGatewayFileWithFeedback -- the same fileMenu.downloadSaved /
downloadFailed toasts the Files panel already used; cancel stays silent.
- plugins/kanban/drawer.tsx: rebased AttachmentDownload/AttachmentsSection
onto main's Dialog-based drawer (aside "Attachments" section), using the
app's boxless text-button treatment (size="inline" variant="text") to
match the drawer's other inline actions, with a Tip for a long filename.
- sdk/index.ts: keeps captureGatewayFileDownload as the plugin SDK export
(now sourced from lib/media, not a second module); the plugin docs keep
it as a void-returning, non-throwing capture (toasts already fire inside).
- types.ts: keeps KanbanAttachment.stored_path.
- Deletes api/file-download.ts and its resolver; drawer/file-actions tests
adapted for the new call shape plus a local-mode ownership case.
Root cause: the Kanban drawer only ever rendered the attachment filename
with no action, so on macOS (and everywhere else) there was no way to fetch
the stored file at all -- the backend already returns stored_path
(plugins/kanban/dashboard/plugin_api.py) but nothing in the renderer used it.
Tests: apps/desktop `npx vitest run --project ui src/plugins/kanban
src/lib/media src/api src/store/file-actions` (112 passed); the new drawer /
media-file-download / file-actions cases fail on main first (confirmed
against a scratch main worktree) and pass here. `npm run typecheck` clean.
Limits: attachments with no stored_path (older backend rows) keep the
control disabled rather than guessing a workspace path, matching the source
PR's compatibility stance.
Fixes: https://github.com/NousResearch/hermes-agent/issues/85672
Supersedes: https://github.com/NousResearch/hermes-agent/pull/107370
Supersedes: https://github.com/NousResearch/hermes-agent/pull/110161
Supersedes: https://github.com/NousResearch/hermes-agent/pull/87727
Co-authored-by: Johan Roest <229638764+jroest@users.noreply.github.com>