Skip to content

fix(signal): inline image and PDF attachments as base64#2529

Open
brentkearney wants to merge 1 commit into
nanocoai:channelsfrom
brentkearney:fix/signal-attachment-mount
Open

fix(signal): inline image and PDF attachments as base64#2529
brentkearney wants to merge 1 commit into
nanocoai:channelsfrom
brentkearney:fix/signal-attachment-mount

Conversation

@brentkearney
Copy link
Copy Markdown

Fixes #2528.

Type of Change

  • Fix — image attachments unreachable from the agent container
  • Feat — adds PDF attachment handling (previously dropped silently)

Problem

src/channels/signal.ts was emitting {path: <host-path>} for image attachments in the structured attachments array, plus a [Image: <host-path>] line in the text body. The host never mounts signal-cli's attachments directory (~/.local/share/signal-cli/attachments) into agent containers, so the agent received a path that didn't resolve inside the container. Vision-capable models couldn't see images; PDFs were dropped entirely (no handler for application/pdf).

Full root cause and reproduction in the linked issue.

Solution

The host already has a complete pipeline: session-manager.extractAttachmentFiles saves any attachments[].data base64 to <sessionDir>/inbox/<msgId>/<filename> (= /workspace/inbox/... in container) and rewrites the entry as localPath. The container formatter renders [image|document: <name> — saved to /workspace/inbox/.../...].

signal.ts just needs to read each attachment off disk and inline it as base64 in the attachments array. No host changes, no new mount, no new dependency.

Changes

  • src/channels/signal.ts: extract a shared inlineAttachment helper, call it once for image/* attachments and once for application/pdf attachments. Helper reads file, base64-encodes, pushes {data, name, type, contentType, size}. Preserves the original filename when signal-cli supplies one (typical for documents); synthesizes <id>.<ext> otherwise. Drop the now-redundant [Image: <host-path>] line — the formatter renders its own ref line from the structured attachment.
  • src/channels/signal.test.ts: existing image test updated to assert the new structured shape (data, name: '<id>.jpeg', type: 'image') and an empty text body. New PDF test covers filename preservation and type: 'document'. Both tests stage a real file in tmpdir and round-trip its base64 through the adapter. 38/38 tests pass.

Voice attachments

Unaffected. They're transcribed on the host (via WHISPER_BIN / OPENAI_API_KEY) before being embedded in the text content, so no file is passed to the container.

Verified

Running live on a downstream install since 2026-05-17. Confirmed end-to-end:

  • Send an image on Signal → agent describes it correctly.
  • Send a PDF on Signal → agent reads its contents.

🤖 Generated with Claude Code

Bug: signal.ts was emitting `{path: <host-path>}` for image attachments
in the structured `attachments` array, plus a `[Image: <host-path>]`
line in the text body. The host never mounts signal-cli's attachments
directory (`~/.local/share/signal-cli/attachments`) into agent
containers, so the agent received a path that didn't resolve from
inside the container. Vision-capable models couldn't see images at
all; PDFs were dropped entirely (no handler for `application/pdf`).

Fix: read each attachment file off disk and inline it as base64 in
the structured `attachments` array (`{data, name, type, contentType,
size}`). The host's existing
session-manager.extractAttachmentFiles saves base64 attachments to
`<sessionDir>/inbox/<msgId>/<filename>` (= `/workspace/inbox/...`
inside the container) and rewrites the entry as `localPath`. The
container formatter renders `[image|document: <name> — saved to
/workspace/inbox/.../...]`. The agent can then Read the file.

Coverage:
- Images (existing flow): now uses base64 + correct container path.
  Drops the now-redundant `[Image: <host-path>]` text line.
- PDFs (new): same handler via a shared `inlineAttachment` helper.
  Preserves the original filename when signal-cli supplies one
  (typical for documents) — falls back to `<id>.<ext>` otherwise.
- Voice attachments: unaffected. They're transcribed on the host
  before being embedded in the text content, no file is passed to
  the container.

Tests: existing image test updated to assert the new structured
attachment shape and an empty text body. New PDF test added covering
filename preservation and `type: 'document'`. Both pass.

Verified live on a downstream install: signal images and PDFs now
both render correctly to the agent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: Fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant