Add native in-process SimpleX transport (mode: "native")#26
Conversation
SimpleX deprecated its WebRTC TypeScript client in favor of the native `simplex-chat` Node.js library, which embeds the SimpleX core in-process. This adds it as an opt-in transport alongside the existing external WebSocket-runtime mode, so a bot can run with no sidecar — no separate simplex-chat process, Docker/systemd, or wsUrl. Set mode: "native". External mode is unchanged: a SimplexTransport interface is extracted from the existing WS client, SimplexWsClient implements it, and SimplexClient selects the implementation by account mode. Transport & lifecycle: - SimplexCoreClient drives the embedded core (init -> ensure user -> startChat -> address); it speaks the same string-command / JSON-event protocol as the WS runtime, so everything above the transport is unchanged. - One embedded core per account: withSimplexClient refuses to open a second connection to the same database, and startup rejects two native accounts that resolve to the same db.filePrefix. - Diagnostics/security are mode-aware (no WS-endpoint checks in native). Config (additive, backward compatible): - connection.mode gains "native"; adds db (filePrefix/encryptionKey), profile (displayName/fullName/image/peerType), and addressSettings (autoAccept/welcomeMessage/businessAddress). Manifest regenerated. - db.filePrefix defaults to <OPENCLAW_STATE_DIR|~/.openclaw>/simplex/<accountId>, per account. Profile & address: - Account-level profile resolved and reconciled on connect; the avatar is downscaled to a small JPEG (using the image/jpg media type SimpleX renders). The address is auto-created with welcome message + auto-accept and reconciled only when settings change. CLI: - The openclaw simplex ... commands (invite, address, requests, groups, files, connect, runtime) dispatch to the running gateway in native mode so they reach the in-process core, with identical terminal output; external behavior is untouched. Licensing: - simplex-chat is AGPL-3.0 and is NOT a dependency of this plugin. Default and external-mode installs never fetch it. Native mode is an explicit operator opt-in (npm install simplex-chat in the plugin's project), loaded lazily via dynamic import. Adds unit tests (transport guard, core lifecycle, profile/address reconcile, account defaults and duplicate-prefix guard) and a native-setup guide covering config, profile, address settings, and a database durability/reset runbook.
|
Hi @dangoldbj, I bumped ws from 8.20.0 to 8.21.0 to fix the vulnerability scan. Thanks for taking a look at this! |
Lets a native-mode account route over specific SMP/XFTP servers instead of the built-in presets — the last open item called out in the PR description. Verified end-to-end against a self-hosted SMP server (inbound and outbound). Config (additive, backward compatible): - connection.servers gains smp[] and xftp[], each a full server URI (smp://<fingerprint>@host / xftp://<fingerprint>@host). When set, the list replaces the corresponding preset servers. Manifest regenerated. Behavior: - Applied after startChat via the core's /smp and /xftp commands (multiple servers space-separated). External mode is untouched. - Fail-fast: a rejected configuration throws and aborts startup rather than silently falling back to the defaults — selecting custom servers means opting out of the presets (self-hosting, privacy, compliance), so the error is surfaced immediately. The startup error names the offending list (SMP/XFTP) and includes the core's structured reason. Adds unit tests (commands sent space-joined after startChat, skipped when unset, startup fails on rejection) and a native-setup guide section covering config, the replace-presets/fail-fast semantics, and a reachability note (private-IP servers are only usable by LAN clients that configure them, since private message routing otherwise proxies through a public server that cannot reach a private host).
When the external runtime is started with --files-folder, it reports each received file by name only (not a full path), expecting the client to know its own files-folder. The plugin was joining that bare name to a hardcoded /tmp, so media staging failed with ENOENT whenever --files-folder was set, and the file never reached OpenClaw's media/inbound store. (Without --files-folder the runtime reports absolute paths — Downloads on desktop, /tmp in headless containers — which already worked and are unaffected.) Resolve relative inbound paths against a new connection.filesFolder instead, defaulting to ~/.simplex/files to match the default the bundled runtime service launches simplex-chat with. Absolute paths bypass resolution entirely, so native mode (whose embedded core always reports absolute paths) is unaffected. This also makes the split-container Docker topology work: the sidecar and OpenClaw mount one shared volume at different paths, and the runtime's name-only reporting lets connection.filesFolder map the sidecar's write-path to OpenClaw's read-path. An absolute path could not, since the sidecar's directory does not exist inside the OpenClaw container. - connection.filesFolder config (schema/types/manifest), with ~ expansion. - resolveSimplexInboundDir() extracted and unit-tested (default, custom folder, ~ expansion). - Diagnostic log of the path exactly as the runtime reported it (absolute vs. relative) for observability. - Docs: connection.filesFolder in config reference, and a "Locating received files" section in the runtime-setup guide covering the shared-volume case. Builds on the media/inbound staging added earlier in this branch; absolute-path deployments see no behavior change. Co-Authored-By: Claude Opus 4.8
|
Quick correction on my earlier framing: I'd implied Also, to keep this respectful of how you've scoped the project: the inbound-file fixes in this PR — the file-receive race fix, staging received media into |
|
Closing to split. |
Why
SimpleX now ships a native
simplex-chatNode.js library that embeds the SimpleX core in-process — an alternative to
running the CLI as a separate WebSocket service. This PR exposes that embedded
core as an opt-in transport (
mode: "native") for operators who'd rathernot run a sidecar, while leaving the existing external WebSocket-runtime mode
completely unchanged and fully supported.
The win for that use case: a bot can run with no sidecar — no
separately-managed
simplex-chatprocess, no Docker/systemd, nowsUrl. Setmode: "native"and the plugin runs the core itself. For anyone happy with theservice/sidecar setup, nothing changes — this only adds a second option behind a
flag.
What's included
mode: "native") — embeddedsimplex-chatcore; speaksthe same command/event protocol as the WS runtime, so everything above the
transport layer is unchanged.
SimplexTransportinterface;SimplexWsClient(external) and newSimplexCoreClient(native) bothimplement it;
SimplexClientselects bymode. External mode isbyte-for-byte unchanged.
simplex-chatis not a dependency of the plugin. Itis installed manually, and loaded lazily at runtime via dynamic import.
Default and external-mode installs stay pure-JS/MIT and never fetch it.
connection.modegains"native", plusdb(
filePrefix/encryptionKey),profile(
displayName/fullName/image/peerType),addressSettings(
autoAccept/welcomeMessage/businessAddress),servers(smp/xftp),and
filesFolder(external mode — see Inbound file fixes below). Backwardcompatible; manifest regenerated.
connection.servers.smp/.xftproutethe embedded core over specific servers instead of the presets (applied via
the core's
/smp//xftpcommands, space-separated). A rejected configurationfails startup rather than silently falling back to the defaults, since
selecting custom servers means opting out of the presets. Verified end-to-end
against a self-hosted SMP server.
<OPENCLAW_STATE_DIR|~/.openclaw>/simplex/<accountId>, per-account.reconciled on connect; avatar auto-downscaled to a small JPEG (with the
image/jpgmedia type SimpleX requires); address auto-created with welcomemessage + auto-accept, also reconciled on change.
second connection to the same SQLite, e.g. from a CLI/probe), and a startup
check rejecting two native accounts that share a
db.filePrefix.openclaw simplex …commands (invite, address,requests, groups, files, connect, runtime) transparently dispatch to the
running gateway in native mode (so they reach the in-process core) with the
same terminal QR/output; external behavior untouched.
connection link.
stages received media into OpenClaw's
media/inboundstore. Received files arelocated correctly across deployments: absolute paths (no
--files-folder;native core) are read directly, while a runtime started with
--files-folderreports names only and they are resolved against
connection.filesFolder(default
~/.simplex/files) — which also bridges a shared volume mounted atdifferent paths across separate sidecar/OpenClaw containers. A startup
diagnostic logs each reported path (absolute vs. relative).
lifecycle/profile/address reconcile, account defaults and duplicate-prefix
guard; a
native-setupguide covering config, profile, address settings, anda DB-durability/reset runbook.
Compatibility & licensing
mode: "external"configs are unaffected;the native fields are additive.
simplex-chatis AGPL-3.0 while this plugin is MIT. It isnot declared as a dependency, so default and external-mode installs never
fetch it; enabling native mode is an explicit operator opt-in
(
npm install simplex-chatin the plugin's project). Whether todocument/endorse that opt-in AGPL path is ultimately a maintainer decision.
Testing
Validated end-to-end on macOS and a StartOS (containerized) deployment:
manual contact-request approval, and business-address (group) connections.
self-hosted SMP server and verified inbound + outbound; confirmed a malformed
server URI fails startup (no silent fallback to presets).
--files-folder→ absolute path (Downloads onmacOS,
/tmpin containers), read directly;--files-folder→ name-only path resolved againstconnection.filesFolder, including a shared Docker volume mounted atdifferent paths in the sidecar and OpenClaw containers;
/tmppath, read directly.tsc,biome, andvitestall green.Co-Authored-By: Claude Opus 4.8