Skip to content

Fix inbound file handling and add cross-container file exchange#28

Open
lundog wants to merge 2 commits into
dangoldbj:mainfrom
lundog:file-exchange
Open

Fix inbound file handling and add cross-container file exchange#28
lundog wants to merge 2 commits into
dangoldbj:mainfrom
lundog:file-exchange

Conversation

@lundog

@lundog lundog commented Jul 2, 2026

Copy link
Copy Markdown

Why

Makes SimpleX file transfer reliable in external mode, including the common case where the simplex-chat runtime runs in a container (a filesystem OpenClaw doesn't share) while OpenClaw runs on the host or in its own container. It's transport-agnostic — no native-mode code — and everything is additive and backward compatible (unset config = today's behavior).

This is the file-exchange half of the earlier native-mode PR, split out per that PR's discussion so it can be reviewed and land on its own. A native-transport PR follows, stacked on top of this branch.

What

Inbound

  • Fix a file-receive race: queue the pending file and retry the accept on rcvFileDescrReady, cancelling on timeout — a not-yet-ready XFTP descriptor no longer drops the file.
  • Stage received files into OpenClaw's media/inbound store so media tools and sandboxed workspaces can read them.
  • Resolve relative inbound paths against a new connection.filesFolder (default ~/.simplex/files, matching the bundled runtime service). When the runtime runs with --files-folder it reports a bare file name, which is joined to filesFolder; without it, the runtime reports absolute paths and they're read directly.
  • Log the reported inbound path (absolute vs. relative) for diagnosability.

Outbound

  • Add connection.outboundFolder. When set, outbound media is staged there before sending, and the staged path — valid on the runtime's side — is what travels over the WebSocket. Staged files are cleaned up after the send. Unset = the local path is passed as-is (single-filesystem default).
  • Add connection.outboundFolderOnClient for split-path deployments where the two sides mount the shared directory at different paths: the plugin stages into outboundFolder but rewrites the directory prefix to this before sending, so no verbatim/matching path is required. No effect without outboundFolder.
  • Resolve media://<subdir>/<id> store references to a physical path on send (e.g. re-sending a received file).

Housekeeping

  • Bump ws to ^8.21.0 (dependency-audit fix).

Config connection gains filesFolder and outboundFolder (types, Zod schema, regenerated manifest). Adds unit tests for inbound resolution, outbound staging/cleanup, the readable-path guard, and media reference handling; documents both settings in the config reference and the runtime-setup guide (symmetric inbound/outbound sections).

Compatibility

Additive and backward compatible: existing configs are unaffected, and with filesFolder/outboundFolder unset the plugin behaves exactly as before.

Testing

Validated end-to-end against a containerized simplex-chat runtime with OpenClaw on the host, sharing an inbound volume and a verbatim outbound mount:

  • Inbound: received files from a mobile client — reported name-only, resolved against filesFolder, staged into media/inbound.
  • Outbound: the bot sent files sourced three ways — a local path, a media:// store reference, and an http(s) URL — each staged into the shared outbound dir, delivered, and cleaned up.
  • tsc, biome, and vitest all green.

lundog added 2 commits July 2, 2026 14:54
Makes SimpleX file transfer work reliably in external mode, including when
the simplex-chat runtime runs in a separate container sharing a volume rather
than a filesystem. Transport-agnostic.

Inbound:
- Fix a file-receive race: queue the pending file and retry the accept on
  rcvFileDescrReady, cancelling on timeout, so a not-yet-ready XFTP descriptor
  no longer drops the file.
- Stage received files into OpenClaw's media/inbound store so media tools and
  sandboxed workspaces can read them.
- Resolve relative inbound paths against connection.filesFolder (default
  ~/.simplex/files, matching the runtime service) instead of a hardcoded /tmp.
  When the runtime runs with --files-folder it reports a bare file name, which
  is joined to filesFolder; absolute paths (no --files-folder) are read directly.
- Log the reported inbound path (absolute vs relative) for diagnosability.

Outbound:
- Add connection.outboundFolder. When set, outbound media is staged into that
  directory before sending, and the staged path — valid inside the runtime's
  container when both sides mount the dir verbatim — is what travels over the
  WebSocket. Staged files are cleaned up after the send. Unset = legacy
  single-filesystem behavior (the local path is passed as-is).
- Resolve media://<subdir>/<id> store references to a physical path on send
  (e.g. re-sending a received file).

Config connection gains filesFolder and outboundFolder (types, schema, manifest).
Adds unit tests for inbound resolution, outbound staging/cleanup, the
readable-path guard, and media reference handling; documents both settings.

Co-Authored-By: Claude Opus 4.8
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