Skip to content

fix(whatsapp): bundle bridge.js with the gateway package so pip/Nix installs find it (#15336) - #15460

Closed
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/whatsapp-bridge-package-data-nixos
Closed

fix(whatsapp): bundle bridge.js with the gateway package so pip/Nix installs find it (#15336)#15460
briandevans wants to merge 2 commits into
NousResearch:mainfrom
briandevans:fix/whatsapp-bridge-package-data-nixos

Conversation

@briandevans

Copy link
Copy Markdown
Contributor

What does this PR do?

Fixes `#15336`. The WhatsApp bridge (Node.js daemon launched by `WhatsAppAdapter`) lived at `scripts/whatsapp-bridge/` — outside any Python package. Setuptools' `find_packages` only ships files that live INSIDE a package directory, so `pip install` (and downstream Nix / Docker / Homebrew installs that build from the wheel) silently dropped the bridge from the artifact. When users on those install paths tried to start the WhatsApp gateway:

```
✗ Bridge script not found at /nix/store/.../site-packages/scripts/whatsapp-bridge/bridge.js
```

Even though the source tree had `scripts/whatsapp-bridge/bridge.js`, the installed wheel did not.

Fix

Move the bridge inside the `gateway` package — its only consumer — and register it as setuptools package-data so wheels actually contain it. The directory now lives at `gateway/whatsapp_bridge/` and resolves cleanly under both source-tree runs and installed wheels.

Changes

  • `git mv scripts/whatsapp-bridge/ → gateway/whatsapp_bridge/` (5 files: `bridge.js`, `allowlist.js`, `allowlist.test.mjs`, `package.json`, `package-lock.json`)
  • New `gateway/whatsapp_bridge/init.py` marker — required so `find_packages` treats it as a regular setuptools package and the package-data globs reliably include the JS files (PEP 420 namespace packages have spotty package-data support across setuptools versions)
  • `pyproject.toml`: new `[tool.setuptools.package-data]` entry:
    ```toml
    "gateway.whatsapp_bridge" = [".js", ".mjs", "package.json", "package-lock.json"]
    ```
    Globs deliberately exclude `node_modules` — the `hermes setup` flow re-creates that tree at runtime via `npm install`, so we don't bloat the wheel with a resolved dependency tree.
  • Update three resolvers:
    • `gateway/platforms/whatsapp.py::WhatsAppAdapter._DEFAULT_BRIDGE_DIR` → `Path(file).parents[1] / "whatsapp_bridge"`
    • `hermes_cli/main.py`: bridge-deps install step now resolves via `gateway.file` so it works under wheel installs too
    • `hermes_cli/doctor.py`: same — npm-audit step also uses the package-relative path
  • Doc updates in `CONTRIBUTING.md` and `website/docs/user-guide/docker.md` to point at the new path

Related Issue

Fixes #15336

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (file move; no behavior change beyond the bug fix)

Test plan

  • 5 new tests in `tests/gateway/test_whatsapp_bridge_packaging.py` — all green on py3.11 venv
  • All 43 pre-existing whatsapp tests still pass (`test_whatsapp_connect.py` + `test_whatsapp_formatting.py`) — 48 total green
  • Verified regression guards: temporarily removed the `gateway.whatsapp_bridge` package-data entry from `pyproject.toml`; `test_pyproject_package_data_covers_bridge_files` correctly failed with the exact assertion message ("wheel installs will not contain bridge.js ([Bug]: WhatsApp bridge script (bridge.js) missing in NixOS module installation #15336)"). Restored → all 5 pass.

Test coverage detail

`test_whatsapp_bridge_packaging.py` (5 tests):

  • `test_bridge_dir_resolves_from_gateway_package` — `_DEFAULT_BRIDGE_DIR` computes to a real existing directory
  • `test_bridge_dir_lives_inside_gateway_package` — pin the location so a future move doesn't silently desync from `pyproject.toml`
  • `test_bridge_dir_contains_required_files` — `bridge.js`, `allowlist.js`, `package.json` all present
  • `test_pyproject_package_data_covers_bridge_files` — parses `pyproject.toml` via `tomllib` so this regression is caught even outside an installed wheel; pins both `*.js` and `package.json` patterns
  • `test_bridge_init_marker_present` — the `init.py` marker exists (the thing that makes the directory a real setuptools package on every version)

Why move to `gateway/whatsapp_bridge/` and not just include `scripts/`?

Considered but rejected:

  1. Add `scripts/init.py` — would also pull every other dev script (`build_skills_index.py`, `contributor_audit.py`, `release.py`, etc.) into the importable Python namespace. Bigger surface, fragile.
  2. `[tool.setuptools.data-files]` — places files under `/` not `site-packages/`, doesn't match the resolver path or fix the `pip install` case.
  3. PEP 420 namespace packages with `scripts.*` — works on modern setuptools but breaks on older versions still in use across the install matrix (Nix pins setuptools per channel, Homebrew lags).

Moving inside `gateway/` is the smallest surface area that works on every setuptools version, keeps the bridge next to its only consumer, and matches the existing `hermes_cli = ["web_dist/**/*"]` pattern already in the codebase.

Out of scope

  • Symlinking back to `scripts/whatsapp-bridge/` for backward compat with operator scripts that might `cd scripts/whatsapp-bridge && npm install` directly. The `hermes setup` flow now does that via the new path; a follow-up could add an alias.
  • The Dockerfile reference (already mentions the bridge but uses `pip install -e .` so it picks up the source-tree path automatically). Verified the Dockerfile builds unchanged.

Copilot AI review requested due to automatic review settings April 25, 2026 01:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a packaging bug where the WhatsApp Node.js bridge wasn’t included in built wheels (breaking pip/Nix installs), by moving the bridge into the gateway Python package and updating all resolvers to use package-relative paths.

Changes:

  • Move/ship the WhatsApp bridge under gateway/whatsapp_bridge/ via setuptools package-data so wheels include bridge.js and its Node metadata.
  • Update WhatsApp bridge path resolution in the gateway adapter and CLI (hermes whatsapp, hermes doctor) to work from installed wheels.
  • Add regression tests to ensure the bridge directory and required files remain packaged; update docs to reflect the new path.

Reviewed changes

Copilot reviewed 8 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
website/docs/user-guide/docker.md Updates Docker docs to reference the new bridge location under gateway/whatsapp_bridge/.
tests/gateway/test_whatsapp_bridge_packaging.py Adds regression tests ensuring bridge files are discoverable and covered by pyproject.toml package-data.
pyproject.toml Adds gateway.whatsapp_bridge package-data patterns so wheels include bridge assets.
hermes_cli/main.py Updates hermes whatsapp setup flow to locate the bridge via gateway.__file__.
hermes_cli/doctor.py Updates npm-audit path resolution to locate the bridge from the installed gateway package.
gateway/whatsapp_bridge/package.json Declares Node dependencies for the embedded WhatsApp bridge.
gateway/whatsapp_bridge/package-lock.json Locks Node dependency tree for the embedded WhatsApp bridge.
gateway/whatsapp_bridge/bridge.js Node.js WhatsApp bridge daemon (HTTP API for the Python adapter).
gateway/whatsapp_bridge/allowlist.test.mjs Node-level tests for allowlist parsing/mapping helpers.
gateway/whatsapp_bridge/allowlist.js Allowlist and identifier mapping utilities used by the bridge.
gateway/whatsapp_bridge/__init__.py Marks the directory as a Python package to ensure setuptools includes package-data reliably.
gateway/platforms/whatsapp.py Updates the default bridge directory to gateway/whatsapp_bridge/ (package-relative).
CONTRIBUTING.md Updates repository layout documentation to reflect the new bridge location.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread gateway/whatsapp_bridge/__init__.py Outdated
@@ -0,0 +1,15 @@
"""Embedded WhatsApp bridge (Node.js).

This package vendors the small Node.js daemon that talks to whatsapp-web.js.

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring says the bridge talks to whatsapp-web.js, but gateway/whatsapp_bridge/bridge.js (and package.json) use Baileys (@whiskeysockets/baileys). Please update this documentation to match the actual implementation to avoid confusing contributors and downstream packagers.

Suggested change
This package vendors the small Node.js daemon that talks to whatsapp-web.js.
This package vendors the small Node.js daemon that talks to WhatsApp via
Baileys (``@whiskeysockets/baileys``).

Copilot uses AI. Check for mistakes.
Comment thread gateway/platforms/whatsapp.py Outdated
Comment on lines +162 to +167
# Default bridge location. The bridge files live inside the
# ``gateway`` package (``gateway/whatsapp_bridge/``) so setuptools /
# pip / Nix all ship them as package-data. Resolving via
# ``__file__.parents[1]`` (gateway/) keeps the path correct under
# both source-tree runs and installed wheels (#15336).
_DEFAULT_BRIDGE_DIR = Path(__file__).resolve().parents[1] / "whatsapp_bridge"

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting _DEFAULT_BRIDGE_DIR to a path inside site-packages means the adapter’s existing auto-install step (npm install when node_modules/ is missing) will attempt to write into the installed package directory. That commonly fails for read-only installs (notably Nix store / system site-packages). Consider copying the bridge to a writable runtime directory (e.g., under the Hermes home/session dir) before running npm install, or otherwise directing npm’s install prefix/cache to a writable path.

Copilot uses AI. Check for mistakes.
Comment thread hermes_cli/doctor.py Outdated
_whatsapp_bridge_dir = (
Path(_gateway_pkg.__file__).resolve().parent / "whatsapp_bridge"
)
except Exception:

Copilot AI Apr 25, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The except Exception around importing gateway will also swallow unexpected runtime errors inside gateway (not just missing modules), making doctor silently fall back and potentially hiding real issues. Narrow this to ImportError (and, if needed, AttributeError for missing __file__) or at least surface/log the exception when falling back.

Suggested change
except Exception:
except (ImportError, AttributeError):

Copilot uses AI. Check for mistakes.
@briandevans

Copy link
Copy Markdown
Contributor Author

Heads-up on CI: the Scan PR for critical supply chain risks FAILURE is the known scan-bug from #13411 (two-dot $BASE..$HEAD diff pulls in every file upstream main has drifted through since this branch was forked, including unrelated hermes_cli/setup.py modifications).

Actual files this PR changes (verbatim from git diff origin/main...HEAD):

CONTRIBUTING.md
gateway/platforms/whatsapp.py
gateway/whatsapp_bridge/__init__.py             (new)
gateway/whatsapp_bridge/allowlist.js            (renamed from scripts/whatsapp-bridge/)
gateway/whatsapp_bridge/allowlist.test.mjs      (renamed from scripts/whatsapp-bridge/)
gateway/whatsapp_bridge/bridge.js               (renamed from scripts/whatsapp-bridge/)
gateway/whatsapp_bridge/package-lock.json       (renamed from scripts/whatsapp-bridge/)
gateway/whatsapp_bridge/package.json            (renamed from scripts/whatsapp-bridge/)
hermes_cli/doctor.py
hermes_cli/main.py
pyproject.toml
tests/gateway/test_whatsapp_bridge_packaging.py (new)
website/docs/user-guide/docker.md

None of these match the scan's actual patterns:

  • No .pth files
  • No setup.py / setup.cfg / sitecustomize.py / usercustomize.py / __init__.pth
  • The pyproject.toml change is a single package-data line addition, not an install-hook

The one-character fix for the scan to use three-dot merge-base diff ($BASE...$HEAD) is in #13411 — once that lands every PR clears this lane automatically.

Other lanes are pending or green: check-attribution ✓, docs-site-checks ✓, e2e ✓, nix (macos-latest) ✓, check ✓, nix (ubuntu-latest) and test still running.

@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround platform/whatsapp WhatsApp Business adapter comp/gateway Gateway runner, session dispatch, delivery area/nix Nix flake, NixOS module, container packaging labels Apr 25, 2026
briandevans added a commit to briandevans/hermes-agent that referenced this pull request Apr 25, 2026
…m install (Copilot NousResearch#15460)

Copilot's second review on NousResearch#15460 flagged three issues; the third is
critical and changes the bridge-install design:

1. ``gateway/whatsapp_bridge/__init__.py`` docstring called the
   bundled bridge ``whatsapp-web.js``-based.  It's actually Baileys
   (``@whiskeysockets/baileys`` in ``package.json``).  Corrected.
2. ``hermes_cli/doctor.py`` swallowed every exception when importing
   ``gateway`` — narrowed to ``(ImportError, AttributeError)`` so a
   genuine runtime bug inside the gateway module surfaces loudly.
3. **Critical**: shipping the bridge to ``site-packages/gateway/
   whatsapp_bridge/`` means ``npm install`` tries to write into
   site-packages.  On Nix store / system pip installs that's read-
   only, so every user on those install paths would hit ``EROFS``
   / ``EACCES`` the first time they ran the WhatsApp gateway.

### Fix for (3): template + runtime dir split

The site-packages location is now a read-only **template**.  The
adapter copies the JS sources + package.json to a writable runtime
directory (``HERMES_HOME / 'whatsapp-bridge/'``) on first ``start()``,
then runs ``npm install`` there.

New module-level helpers in ``gateway/platforms/whatsapp.py``:

* ``_BRIDGE_TEMPLATE_FILES`` — tuple of filenames that must be copied;
  excludes ``node_modules`` (npm manages it) and ``__init__.py`` (a
  Python artefact, not part of the Node app).
* ``_resolve_runtime_bridge_dir()`` — returns ``get_hermes_home() /
  'whatsapp-bridge'``.  Honouring ``HERMES_HOME`` means profile-
  isolated installs and Docker volumes work without extra wiring.
* ``_ensure_runtime_bridge_files(template_dir, runtime_dir)`` —
  copy-if-newer semantics so Hermes upgrades propagate new bridge
  code on first start after update.  ``shutil.copy2`` preserves
  content; we then ``chmod 0o644`` explicitly because pip may ship
  package-data at ``0o444`` and npm needs write access to update
  ``package-lock.json``.  No-ops cleanly when the template is
  missing (dev checkouts without ``pip install -e .``).

Adapter:

* New class constant ``_DEFAULT_BRIDGE_TEMPLATE_DIR`` pointing at the
  site-packages read-only location.
* ``_DEFAULT_BRIDGE_DIR`` keeps pointing at the same template for
  backward compat (several tests read that attribute).
* ``__init__`` stores a ``_runtime_bridge_dir`` on the instance and
  defaults ``_bridge_script`` to ``<runtime>/bridge.js``.
* ``start()`` calls ``_ensure_runtime_bridge_files`` before the
  existing npm-install step when the script lives under the runtime
  dir — operator overrides via ``config.extra['bridge_script']`` are
  untouched.

Setup wizard (``hermes_cli/main.py``) uses the same two helpers so the
interactive ``hermes setup --whatsapp`` flow runs npm in the writable
dir too.

``doctor.py``'s npm-audit step now points at the runtime location —
that's where ``node_modules`` actually lives once any user has run
the gateway — and narrows its import fallback exception set.

### Tests (6 new, all passing; total 54 whatsapp tests green)

* ``test_runtime_bridge_dir_lives_under_hermes_home`` — runtime dir
  always resolves under ``HERMES_HOME``, never site-packages.
* ``test_ensure_runtime_bridge_files_copies_template`` — first-boot
  copies the expected files, skips ``node_modules`` and
  ``__init__.py``.
* ``test_ensure_runtime_bridge_files_chmods_writable`` — template
  at ``0o444`` ends up as ``0o644`` in runtime so npm can write.
* ``test_ensure_runtime_bridge_files_is_mtime_aware`` — idempotent
  when template unchanged, re-copies when template gets newer
  (Hermes upgrade pulls in new bridge.js).
* ``test_ensure_runtime_bridge_files_handles_missing_template`` —
  dev checkout with no template dir: no-ops without crashing.
* ``test_adapter_default_bridge_script_points_at_runtime`` — E2E:
  ``WhatsAppAdapter(...)._bridge_script`` parent equals the runtime
  dir, not site-packages.

Updated the ``_make_adapter`` fixtures in ``test_whatsapp_connect.py``
and ``test_whatsapp_formatting.py`` to set ``_runtime_bridge_dir`` on
the ``__new__``-built instances (they bypass ``__init__`` so the new
attribute needs explicit setup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briandevans
briandevans force-pushed the fix/whatsapp-bridge-package-data-nixos branch from c542b52 to 2c0a4ff Compare April 25, 2026 01:42
@briandevans

Copy link
Copy Markdown
Contributor Author

Thanks @copilot — all three findings addressed in 2c0a4ffd, and the third one was a critical catch that genuinely reshapes the fix:

1. Docstring whatsapp-web.js → Baileys — ✅ Fixed, the bundled bridge is actually Baileys (@whiskeysockets/baileys).

2. except Exception too broad in doctor.py — ✅ Narrowed to (ImportError, AttributeError) so a genuine runtime bug inside gateway surfaces loudly rather than being swallowed as "no bridge found".

3. site-packages is read-only on Nix — npm install would fail there — ✅ Critical catch. Redesigned the bridge lifecycle as a template + runtime dir split:

  • Site-packages location (gateway/whatsapp_bridge/) is now an explicitly read-only template.
  • On first start(), the adapter copies the JS sources + package.json to a writable runtime dir (HERMES_HOME / 'whatsapp-bridge/') and runs npm install there.
  • _ensure_runtime_bridge_files is mtime-aware, so Hermes upgrades propagate new bridge code on the next start after update without burning IO on unchanged files.
  • Template files get chmod 0o644 on copy because pip / the Nix store may ship package-data at 0o444.

Six new tests cover the new seam: runtime dir location, file copy semantics, __init__.py / node_modules exclusion, writable-mode guarantee, mtime-aware idempotency, missing-template graceful no-op, and an end-to-end check that WhatsAppAdapter()._bridge_script resolves to HERMES_HOME not site-packages.

54/54 whatsapp tests still pass.

briandevans and others added 2 commits April 30, 2026 20:14
…nstalls find it (NousResearch#15336)

The WhatsApp bridge (Node.js daemon launched by ``WhatsAppAdapter``)
lived at ``scripts/whatsapp-bridge/`` — outside any Python package.
Setuptools' ``find_packages`` only ships files that live INSIDE a
package directory, so ``pip install`` (and downstream Nix / Docker /
Homebrew installs that build from the wheel) silently dropped the
bridge from the artifact.  When users on those install paths tried to
start the WhatsApp gateway:

    ✗ Bridge script not found at /nix/store/.../site-packages/scripts/whatsapp-bridge/bridge.js

Even though the source tree had ``scripts/whatsapp-bridge/bridge.js``,
the installed wheel did not.

Move the bridge inside the ``gateway`` package — its only consumer —
and register it as setuptools package-data so wheels actually contain
it.  The directory now lives at ``gateway/whatsapp_bridge/`` and
resolves cleanly under both source-tree runs and installed wheels via
``Path(gateway.__file__).parent / "whatsapp_bridge"``.

Files / changes:

- ``git mv scripts/whatsapp-bridge/ → gateway/whatsapp_bridge/`` (5 files)
- New ``gateway/whatsapp_bridge/__init__.py`` marker — required so
  ``find_packages`` treats it as a regular setuptools package and the
  package-data globs reliably include the JS files (PEP 420 namespace
  packages have spotty package-data support across setuptools versions).
- ``pyproject.toml``: add ``"gateway.whatsapp_bridge" = ["*.js",
  "*.mjs", "package.json", "package-lock.json"]`` to
  ``[tool.setuptools.package-data]``.  Globs deliberately exclude
  ``node_modules`` — the ``hermes setup`` flow re-creates that tree at
  runtime via ``npm install`` so we don't bloat the wheel with a
  resolved dependency tree.
- Update three resolvers:
  - ``gateway/platforms/whatsapp.py::WhatsAppAdapter._DEFAULT_BRIDGE_DIR``
    → uses ``Path(__file__).parents[1] / "whatsapp_bridge"`` (i.e. the
    package's own bridge subdir).
  - ``hermes_cli/main.py``: bridge-deps install step now resolves
    via ``gateway.__file__`` so it works under wheel installs too.
  - ``hermes_cli/doctor.py``: same — npm-audit step also uses the
    package-relative path.
- Doc updates in ``CONTRIBUTING.md`` and ``website/docs/user-guide/
  docker.md`` to point at the new path.

- ``_DEFAULT_BRIDGE_DIR`` resolves to a real existing directory
- That directory is a child of the ``gateway`` package's __file__
  parent (so the package-data globs target it)
- Required files (``bridge.js``, ``allowlist.js``, ``package.json``)
  are present
- ``pyproject.toml`` has the ``gateway.whatsapp_bridge`` package-data
  entry with at least ``*.js`` and ``package.json`` patterns — parsed
  via ``tomllib`` so this regression is caught even outside an
  installed wheel
- ``__init__.py`` marker exists (the thing that makes the directory a
  real setuptools package on every version)

**Verified regression guards**: temporarily removed the
``[tool.setuptools.package-data] "gateway.whatsapp_bridge"`` entry
from ``pyproject.toml``; ``test_pyproject_package_data_covers_bridge_files``
correctly failed with the exact assertion message ("wheel installs
will not contain bridge.js (NousResearch#15336)").  Restored → all 5 pass.

48 total tests pass (43 existing whatsapp tests + 5 new packaging
tests).

Closes NousResearch#15336

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…m install (Copilot NousResearch#15460)

Copilot's second review on NousResearch#15460 flagged three issues; the third is
critical and changes the bridge-install design:

1. ``gateway/whatsapp_bridge/__init__.py`` docstring called the
   bundled bridge ``whatsapp-web.js``-based.  It's actually Baileys
   (``@whiskeysockets/baileys`` in ``package.json``).  Corrected.
2. ``hermes_cli/doctor.py`` swallowed every exception when importing
   ``gateway`` — narrowed to ``(ImportError, AttributeError)`` so a
   genuine runtime bug inside the gateway module surfaces loudly.
3. **Critical**: shipping the bridge to ``site-packages/gateway/
   whatsapp_bridge/`` means ``npm install`` tries to write into
   site-packages.  On Nix store / system pip installs that's read-
   only, so every user on those install paths would hit ``EROFS``
   / ``EACCES`` the first time they ran the WhatsApp gateway.

The site-packages location is now a read-only **template**.  The
adapter copies the JS sources + package.json to a writable runtime
directory (``HERMES_HOME / 'whatsapp-bridge/'``) on first ``start()``,
then runs ``npm install`` there.

New module-level helpers in ``gateway/platforms/whatsapp.py``:

* ``_BRIDGE_TEMPLATE_FILES`` — tuple of filenames that must be copied;
  excludes ``node_modules`` (npm manages it) and ``__init__.py`` (a
  Python artefact, not part of the Node app).
* ``_resolve_runtime_bridge_dir()`` — returns ``get_hermes_home() /
  'whatsapp-bridge'``.  Honouring ``HERMES_HOME`` means profile-
  isolated installs and Docker volumes work without extra wiring.
* ``_ensure_runtime_bridge_files(template_dir, runtime_dir)`` —
  copy-if-newer semantics so Hermes upgrades propagate new bridge
  code on first start after update.  ``shutil.copy2`` preserves
  content; we then ``chmod 0o644`` explicitly because pip may ship
  package-data at ``0o444`` and npm needs write access to update
  ``package-lock.json``.  No-ops cleanly when the template is
  missing (dev checkouts without ``pip install -e .``).

Adapter:

* New class constant ``_DEFAULT_BRIDGE_TEMPLATE_DIR`` pointing at the
  site-packages read-only location.
* ``_DEFAULT_BRIDGE_DIR`` keeps pointing at the same template for
  backward compat (several tests read that attribute).
* ``__init__`` stores a ``_runtime_bridge_dir`` on the instance and
  defaults ``_bridge_script`` to ``<runtime>/bridge.js``.
* ``start()`` calls ``_ensure_runtime_bridge_files`` before the
  existing npm-install step when the script lives under the runtime
  dir — operator overrides via ``config.extra['bridge_script']`` are
  untouched.

Setup wizard (``hermes_cli/main.py``) uses the same two helpers so the
interactive ``hermes setup --whatsapp`` flow runs npm in the writable
dir too.

``doctor.py``'s npm-audit step now points at the runtime location —
that's where ``node_modules`` actually lives once any user has run
the gateway — and narrows its import fallback exception set.

* ``test_runtime_bridge_dir_lives_under_hermes_home`` — runtime dir
  always resolves under ``HERMES_HOME``, never site-packages.
* ``test_ensure_runtime_bridge_files_copies_template`` — first-boot
  copies the expected files, skips ``node_modules`` and
  ``__init__.py``.
* ``test_ensure_runtime_bridge_files_chmods_writable`` — template
  at ``0o444`` ends up as ``0o644`` in runtime so npm can write.
* ``test_ensure_runtime_bridge_files_is_mtime_aware`` — idempotent
  when template unchanged, re-copies when template gets newer
  (Hermes upgrade pulls in new bridge.js).
* ``test_ensure_runtime_bridge_files_handles_missing_template`` —
  dev checkout with no template dir: no-ops without crashing.
* ``test_adapter_default_bridge_script_points_at_runtime`` — E2E:
  ``WhatsAppAdapter(...)._bridge_script`` parent equals the runtime
  dir, not site-packages.

Updated the ``_make_adapter`` fixtures in ``test_whatsapp_connect.py``
and ``test_whatsapp_formatting.py`` to set ``_runtime_bridge_dir`` on
the ``__new__``-built instances (they bypass ``__init__`` so the new
attribute needs explicit setup).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@briandevans
briandevans force-pushed the fix/whatsapp-bridge-package-data-nixos branch from 2c0a4ff to b612330 Compare May 1, 2026 03:16
@briandevans

Copy link
Copy Markdown
Contributor Author

Rebased onto current origin/main (96691268d) — was 6 days stale and conflicting on hermes_cli/doctor.py and website/docs/user-guide/docker.md. Resolved both in favour of main's evolved content (the docker-cli / openssh-client / tini bullets and the _safe_which() helper) while preserving the bridge-relocation rationale.

Re-verified focused tests on the rebased head (b612330ef):

  • tests/gateway/test_whatsapp_bridge_packaging.py — 11/11 pass (this PR's regression coverage).
  • Adjacent suites (test_whatsapp_connect.py, test_whatsapp_formatting.py) — same baseline xdist PID-lock failures as clean origin/main (no PR-introduced regressions). Specifically TestBridgeRuntimeFailure::test_closed_when_http_not_ready/_phase2 reproduce on origin/main 9669126 under the same pytest-xdist invocation, so they're a pre-existing test-isolation artifact, not part of this diff.

Diff is otherwise unchanged: same source-of-truth move (scripts/whatsapp-bridge/gateway/whatsapp_bridge/), same Copilot follow-up that materialises the bridge under HERMES_HOME before npm install. Ready for another look.

@briandevans

Copy link
Copy Markdown
Contributor Author

Closing to keep the queue clean — 17 days idle and now conflicting across pyproject.toml and gateway/whatsapp_bridge/ following recent main refactors. Happy to reopen if this is still useful.

anoosa1 added a commit to anoosa1/hermes-agent that referenced this pull request May 25, 2026
@anoosa1

anoosa1 commented May 25, 2026

Copy link
Copy Markdown

Hello,
If you dont mind reopening this pull request. I attempted to fix the issues but unfortunately couldn't get it to work with the module.
I would appreciate this fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/nix Nix flake, NixOS module, container packaging comp/gateway Gateway runner, session dispatch, delivery P1 High — major feature broken, no workaround platform/whatsapp WhatsApp Business adapter type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: WhatsApp bridge script (bridge.js) missing in NixOS module installation

4 participants