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. |
||
|---|---|---|
| .. | ||
| dashboard_auth | ||
| observability | ||
| proxy | ||
| subcommands | ||
| web_routers | ||
| __init__.py | ||
| _early_recovery.py | ||
| _parser.py | ||
| _scan_venv_blockers.py | ||
| _startup_fast.py | ||
| _subprocess_compat.py | ||
| active_sessions.py | ||
| agent_import.py | ||
| agent_plugins.py | ||
| approval_mode.py | ||
| approvals_suggest.py | ||
| approvals_test.py | ||
| auth.py | ||
| auth_commands.py | ||
| azure_detect.py | ||
| backup.py | ||
| bang_shell.py | ||
| banner.py | ||
| blueprint_cmd.py | ||
| browser_connect.py | ||
| build_info.py | ||
| bundles.py | ||
| callbacks.py | ||
| checkpoints.py | ||
| claw.py | ||
| cli_agent_setup_mixin.py | ||
| cli_billing_mixin.py | ||
| cli_commands_mixin.py | ||
| cli_output.py | ||
| clipboard.py | ||
| codex_models.py | ||
| codex_runtime_plugin_migration.py | ||
| codex_runtime_switch.py | ||
| colors.py | ||
| commands.py | ||
| completion.py | ||
| config.py | ||
| config_defaults.py | ||
| config_migrations.py | ||
| console_engine.py | ||
| container_boot.py | ||
| context_switch_guard.py | ||
| copilot_auth.py | ||
| credential_lifecycle.py | ||
| cron.py | ||
| curator.py | ||
| curses_ui.py | ||
| dashboard_procs.py | ||
| dashboard_register.py | ||
| debug.py | ||
| default_soul.py | ||
| dep_ensure.py | ||
| diagnostics_upload.py | ||
| dingtalk_auth.py | ||
| doctor.py | ||
| doctor_live.py | ||
| dump.py | ||
| env_loader.py | ||
| fallback_cmd.py | ||
| fallback_config.py | ||
| focus_view.py | ||
| gateway.py | ||
| gateway_enroll.py | ||
| gateway_windows.py | ||
| goals.py | ||
| gui_uninstall.py | ||
| heartbeat.py | ||
| hooks.py | ||
| init_command.py | ||
| input_sanitize.py | ||
| inventory.py | ||
| journey.py | ||
| kanban.py | ||
| kanban_db.py | ||
| kanban_decompose.py | ||
| kanban_diagnostics.py | ||
| kanban_specify.py | ||
| kanban_swarm.py | ||
| lifecycle.py | ||
| linux_desktop_entry.py | ||
| logs.py | ||
| main.py | ||
| managed_scope.py | ||
| managed_uv.py | ||
| mcp_catalog.py | ||
| mcp_config.py | ||
| mcp_picker.py | ||
| mcp_security.py | ||
| mcp_startup.py | ||
| mem_trim.py | ||
| memory_oauth.py | ||
| memory_setup.py | ||
| middleware.py | ||
| migrate.py | ||
| moa_cmd.py | ||
| moa_config.py | ||
| model_catalog.py | ||
| model_cost_guard.py | ||
| model_normalize.py | ||
| model_search.py | ||
| model_setup_flows.py | ||
| model_switch.py | ||
| models.py | ||
| nous_account.py | ||
| nous_auth_keepalive.py | ||
| nous_billing.py | ||
| nous_subscription.py | ||
| npm_engine.py | ||
| onepassword_secrets_cli.py | ||
| oneshot.py | ||
| pairing.py | ||
| partial_compress.py | ||
| personality.py | ||
| pets.py | ||
| platforms.py | ||
| plugins.py | ||
| plugins_cmd.py | ||
| portal_cli.py | ||
| profile_describer.py | ||
| profile_distribution.py | ||
| profiles.py | ||
| projects_cmd.py | ||
| projects_db.py | ||
| prompt_size.py | ||
| prompt_stash.py | ||
| provider_catalog.py | ||
| providers.py | ||
| proxy_cli.py | ||
| psutil_android.py | ||
| pt_input_extras.py | ||
| pty_bridge.py | ||
| pty_session.py | ||
| relaunch.py | ||
| route_identity.py | ||
| runtime_provider.py | ||
| secret_prompt.py | ||
| secrets_cli.py | ||
| security_advisories.py | ||
| security_audit.py | ||
| security_audit_startup.py | ||
| send_cmd.py | ||
| service_manager.py | ||
| session_export.py | ||
| session_export_html.py | ||
| session_export_md.py | ||
| session_filters.py | ||
| session_listing.py | ||
| session_recap.py | ||
| session_recovery.py | ||
| sessions_cmd.py | ||
| setup.py | ||
| setup_hidden_env.py | ||
| setup_whatsapp_cloud.py | ||
| sizefmt.py | ||
| skills_config.py | ||
| skills_hub.py | ||
| skin_cmd.py | ||
| skin_engine.py | ||
| slack_cli.py | ||
| slash_exec.py | ||
| sqlite_runtime.py | ||
| sqlite_safe_read.py | ||
| sqlite_util.py | ||
| status.py | ||
| stdio.py | ||
| suggestions_cmd.py | ||
| telegram_managed_bot.py | ||
| timefmt.py | ||
| timeouts.py | ||
| tips.py | ||
| tools_config.py | ||
| toolset_validation.py | ||
| uninstall.py | ||
| update_cmd.py | ||
| update_lock.py | ||
| urllib_security.py | ||
| vercel_auth.py | ||
| verify_cmd.py | ||
| voice.py | ||
| web_deps.py | ||
| web_git.py | ||
| web_models.py | ||
| web_server.py | ||
| webhook.py | ||
| win_pty_bridge.py | ||
| windows_ssh_runtime.py | ||
| write_approval_commands.py | ||
| xai_retirement.py | ||