Commit Graph

4 Commits

Author SHA1 Message Date
ethernet 5ed1c972d9 refactor(sidecars): one resolver for the Photon and WhatsApp children
Both sidecars answered the same question in their own way. Which
directory does this Node child run from, when some installs put the
source tree somewhere nothing can write?

gateway/sidecar_runtime.py answers it once. Four rungs:

1. An operator override.
2. A writable source.
3. A read-only source whose baked deps match the lockfile.
4. A read-only source that must move to $HERMES_HOME/sidecars/<name>
   before npm can run.

Node sets the shape of that last rung. Its ESM resolver reads
node_modules only from the directories above the importing file, and
NODE_PATH applies to CommonJS alone. Measured on Node 26: an ESM import
with NODE_PATH pointing at the packages fails, and the same import from
a directory beside them works. Both sidecars are "type": "module", so
the entry file and the packages must share a tree. A copy is the only
arrangement Node accepts.

_MIRROR_FILES is gone. It named the files to copy, so it had to name
every module the entry file imports. It was wrong twice. It listed the
deleted spectrum patch, and it omitted send-format.mjs and
stream-staleness.mjs. Both faults appear only on a read-only install.
The resolver copies the tree instead, without node_modules, and the test
compares the mirror against the source tree rather than against a second
list. A mutation that returns to a fixed list fails it.

The copy uses shutil.copy, not copy2. copy2 gives the mirror file the
mtime of the source, and a Nix store source has mtime = epoch. A
refreshed lockfile then always predates npm's install marker, and
deps_are_current() keeps stale node_modules through every upgrade,
which is the fault this resolver exists to fix. A plain copy stamps
the copy time, so a content change always postdates the previous
install. npm's hidden node_modules/.package-lock.json cannot replace
the content comparison: it is a different document, and npm matches it
semantically, not byte for byte.

WhatsApp gains what it never had: a staleness check and a refresh. Its
resolver returned any existing mirror without comparing it against the
lockfile, so an upgrade kept the old node_modules. This is a behaviour
change.

The mirrors move to $HERMES_HOME/sidecars/. The Baileys credentials live
in $HERMES_HOME/whatsapp/session, so a paired account is not affected.
`hermes doctor` reports the mirrors the old resolvers left at
$HERMES_HOME/photon/sidecar and $HERMES_HOME/scripts/whatsapp-bridge.
Nothing reads them now, and each one can hold a node_modules of some
hundred MB. --fix removes them.

_sidecar_deps_stale and deps_are_current read the same two files with
opposite missing-file answers, on purpose. Each one points at the other
and says why.

The container bakes both sidecars now. It baked Photon and left WhatsApp
to install at run time.
2026-08-09 17:33:14 -04:00
Teknium 6b81590c55
test: prune low-value tests suite-wide (wave 1) — 46,820 → 28,106 test functions
Systematic prune per AGENTS.md test policy, one pass over every major
test tree (gateway, hermes_cli, tools, agent, run_agent, plugins, cli,
cron, tui_gateway, honcho/openviking, root-level):

- DELETE: source-reading tests (read_text/getsource on prod files),
  change-detector tests (exact catalog counts, model-name snapshots,
  config version literals), mock-echo tests (assert a mock returns what
  it was told), assertion-free/trivial tests, near-duplicate
  parametrizations (boundaries + one representative kept), async/sync
  twin duplicates, cosmetic within-file variations.
- KEEP (mandatory): security/redaction/approval guards, message-role
  alternation invariants, prompt-caching/deterministic-call-id
  invariants, issue-number regression tests (deduped), E2E tests.
- 6 test files deleted outright (script-style/no-assert or fully
  redundant); conftest.py, fakes/, fixtures/ untouched.
- tests/acp/conftest.py added: autouse fixture stubs the live
  models.dev/GitHub/Copilot/Anthropic inventory fetches that ACP server
  tests performed on every session create — test_server.py 147s → 3.4s,
  and the tests are now genuinely hermetic.
- Sleep-based slowness shrunk where safe (codex_ttfb_watchdog,
  compression_concurrent_fork, etc.); no wall-clock assertion tightened.

Verification: full hermetic suite via scripts/run_tests.sh —
2439 files, 31,130 tests passed, 0 failed, 0 flaky retries, 315s wall
(baseline: 583s wall, 13,564s subprocess CPU).
2026-07-29 13:10:23 -07:00
Teknium b62fc24dfa refactor(photon): resolve sidecar dir lazily, not at import time
resolve_sidecar_dir() probes the filesystem (touch/unlink) and can mirror
sidecar files to HERMES_HOME. Doing that as a module-import side effect
meant plugin discovery, `hermes --help`, and test collection all paid a
filesystem probe (and possibly a mirror copy) just for importing the
photon adapter or CLI.

Convert _SIDECAR_DIR/_NPM_ERROR_LOG in adapter.py and cli.py to lazy
cached accessors (_sidecar_dir()/_npm_error_log()); resolution now
happens on first actual use. Existing tests that monkeypatch the
_SIDECAR_DIR module global keep working — the accessors honor a
non-None value. Adds a regression test proving import performs no
resolution.
2026-07-28 22:41:32 -07:00
Shannon Sands 0dfd5546fc fix(photon): support immutable install trees for the sidecar (NS-606)
The Photon iMessage sidecar needs node_modules under
plugins/platforms/photon/sidecar/, but hosted/managed images keep the
whole install tree under an immutable /opt/hermes — every install and
self-heal path (setup CLI, stale-deps reinstall, cold install) died on
EROFS, and hosted users have no shell to work around it.

Three-layer fix, mirroring the WhatsApp bridge resolver pattern:

1. Bake the deps into the image. The Dockerfile now runs npm ci for the
   sidecar in the layer-cached dependency stage (deterministic installs
   from the committed lockfile; the postinstall spectrum-ts patch runs
   at build time). Hosted happy path needs no runtime install at all.

2. New sidecar_paths.resolve_sidecar_dir() decides where the sidecar
   runs from: PHOTON_SIDECAR_DIR override > writable source dir (dev
   installs, unchanged) > read-only dir with baked fresh deps (managed
   image) > mirror to $HERMES_HOME/photon/sidecar (writable data
   volume) when deps are missing or stale in a read-only tree. The
   mirror refreshes changed source files on image updates while
   keeping node_modules, so the existing lockfile-staleness self-heal
   works there.

3. connect() can now cold-install: _start_sidecar() runs the bounded
   npm ci bootstrap when node_modules is missing instead of raising
   immediately, and check_requirements() reports available when a
   self-install is possible (npm present + writable resolved dir) so
   the gateway actually creates the adapter on hosted instances. A
   failed bootstrap still raises the actionable error, which connect()
   surfaces as the retryable SIDECAR_FAILED fatal state on the
   dashboard.

Tests: resolver decision table (env override, in-place, mirror,
refresh, fail-open), cold-install lifecycle paths, and a Dockerfile
contract test guarding the baked-deps + no-chown invariants.

Fixes NS-606.
2026-07-28 22:41:32 -07:00