Skip to content

fix: admit Discord attachments from the Hermes profile cache - #306

Merged
dnth merged 2 commits into
mainfrom
fm/fm-ext-image-roots
Nov 13, 2026
Merged

dnth merged 2 commits into
mainfrom
fm/fm-ext-image-roots

Conversation

@dnth

@dnth dnth commented Nov 13, 2026 •

Copy link
Copy Markdown
Owner

Summary

Images the captain pastes in Discord reach Firstmate even when the gateway serves a profile, and a refused attachment is named in the drain instead of vanishing.

flowchart LR
  A[Discord image] --> B["profile cache/images"]
  C[plugin register] --> D["intake env: FM_EXT_HERMES_HOME"]
  B --> D
  D --> E{inside that home's caches?}
  E -->|yes| F[state/ext-attachments/slug]
  E -->|no| G["skipped_attachments: name, reason"]
  G --> H["drain: Rejected attachment: ..."]
Loading

The plugin records the Hermes home it was loaded for at registration and passes it to every Firstmate subprocess as FM_EXT_HERMES_HOME.
The cache-root allowlist prefers that home over the process HERMES_HOME and stays exactly as strict: only its cache/{images,audio,documents} and populated legacy *_cache directories, regular files only, no symlinks, no widening to all of ~/.hermes.

Evidence

  • Before: with HERMES_HOME at the base home and the image cached under <base>/profiles/fmcomms/cache/images, the new plugin case staged base.webp and recorded the captain's img_1700.webp as {"reason":"uncached"}; the drain printed only thread and history lines.
    After: the same fixture stages 0-img_1700.webp into state/ext-attachments/<slug>/, lists it under attachments, still refuses base.webp and a CDN URL as uncached, and the drain prints Rejected attachment: remote.png (reason: uncached).
  • env -u FM_TASK_ID bash bin/fm-test-run.sh over the 14 ext/plugin scripts (bridge, plugin-dest, media, ask, decision, watch, thread-context, ack-guard, inbox-remove, needs-you-buttons, bot-identity, topic-threads, waiting-pin, hermes-sandbox): 0 failed.
  • bash bin/fm-lint.sh: clean at the pinned ShellCheck 0.11.0.

Merge Danger

Door: two-way. Reverting restores the previous environment precedence and removes one printed drain line; no data, config, or state migration.

Blast Radius: narrow. Only gateway attachment staging and the drain's EXT REQUESTS output change; the allowlist's path, symlink, regular-file, and byte/count guards are untouched.

Pipeline

Updates from git push no-mistakes

✅ **Intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 4 of 4 scenarios driven live against the product
Scenario Result Live Evidence
Local Hermes SDK admits synthetic profile-cache attachments while process HERMES_HOME names the base home ✅ pass live live-plugin-transcript.log; profile-inbox.json
Local plugin intake rejects base-home caches, outside directories, file and parent symlinks, directories, and remote URLs ✅ pass live profile-inbox.json; live-plugin-transcript.log
Firstmate displays every rejected attachment’s name and reason, including requests with no accepted image ✅ pass live wake-drain.txt; rejected-only-inbox.json
Standalone intake retains HERMES_HOME fallback when no plugin profile override is supplied ✅ pass live standalone-inbox.json; live-plugin-transcript.log
  • TMPDIR="$PWD/.validation-ext/tmp" bash tests/fm-ext-bridge.test.sh
  • TMPDIR="$PWD/.validation-ext/tmp" bash tests/fm-ext-plugin-dest.test.sh
  • TMPDIR="$PWD/.validation-ext/tmp" bash tests/fm-ext-bot-identity.test.sh
  • TMPDIR="$PWD/.validation-ext/tmp" bash tests/fm-ext-media.test.sh
  • Executed evidence/live_driver.py through bin/fm-hermes-sandbox.sh using a disposable Hermes SDK environment, real plugin discovery, synthetic MessageEvents, intake, and wake drain.
  • Read the documentation changes and compared changed paths; no changes under .omp/extensions/.
  • Verified child completion, removed the disposable environment, and confirmed a clean working tree.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

dnth added 2 commits November 13, 2026 12:14
A dedicated gateway profile caches inbound Discord media under its own
home while the process HERMES_HOME still names the base Hermes home, so
intake refused every image the captain pasted and recorded it only in
skipped_attachments, which nothing showed.

The plugin now records the Hermes home it was loaded for at registration
and passes it to every Firstmate subprocess as FM_EXT_HERMES_HOME, which
the attachment cache-root allowlist prefers over the process HERMES_HOME.
The allowlist stays exactly as strict: only that home's
cache/{images,audio,documents} and populated legacy *_cache dirs, regular
files, no symlinks, no widening to all of ~/.hermes.

The wake drain's EXT REQUESTS section now prints each refused attachment
with its name and reason, so a lost image is visible before Firstmate
answers the request.
@dnth
dnth merged commit d2aa67f into main Nov 13, 2026
21 checks passed
@dnth
dnth deleted the fm/fm-ext-image-roots branch November 13, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant