diff --git a/agents/hermes/Dockerfile b/agents/hermes/Dockerfile index 6b744950186..462aeae6a14 100644 --- a/agents/hermes/Dockerfile +++ b/agents/hermes/Dockerfile @@ -46,6 +46,7 @@ RUN _hermes_certifi=$(/opt/hermes/.venv/bin/python -c 'import certifi; print(cer # placeholder rewriting, hostname-based policy enforcement, and native # WebSocket credential rewrite at the egress boundary. ENV HERMES_TELEGRAM_DISABLE_FALLBACK_IPS=1 +ENV HERMES_TUI_DIR="/opt/hermes/ui-tui" # Copy NemoClaw plugin for Hermes (Python-based) COPY agents/hermes/plugin/ /opt/nemoclaw-hermes-plugin/ diff --git a/agents/hermes/Dockerfile.base b/agents/hermes/Dockerfile.base index cce6e7242b4..1e2a6cbf7df 100644 --- a/agents/hermes/Dockerfile.base +++ b/agents/hermes/Dockerfile.base @@ -173,6 +173,9 @@ RUN mkdir -p /opt/hermes \ && tar -xzf /tmp/hermes.tar.gz -C /opt/hermes --strip-components=1 \ && rm /tmp/hermes.tar.gz /tmp/hermes.tar.gz.sha256 WORKDIR /opt/hermes +# ui-tui and web are part of the checksum-pinned Hermes release tarball. +# npm ci uses the lockfiles from that pinned tree; re-review on every +# HERMES_VERSION/HERMES_TARBALL_SHA256 bump. # hadolint ignore=SC2086 RUN set -eu; \ set --; \ @@ -194,5 +197,6 @@ RUN set -eu; \ && ln -sf /opt/hermes/.venv/bin/hermes-agent /usr/local/bin/hermes-agent \ && ln -sf /opt/hermes/.venv/bin/hermes-acp /usr/local/bin/hermes-acp -ENV PATH="/usr/local/bin:/opt/hermes/.venv/bin:${PATH}" +ENV PATH="/usr/local/bin:/opt/hermes/.venv/bin:${PATH}" \ + HERMES_TUI_DIR="/opt/hermes/ui-tui" RUN /usr/local/bin/hermes --version diff --git a/agents/hermes/start.sh b/agents/hermes/start.sh index b0ecb9a652a..858351273b5 100755 --- a/agents/hermes/start.sh +++ b/agents/hermes/start.sh @@ -40,6 +40,15 @@ fi # SECURITY: Lock down PATH export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" +# Hermes' browser Chat tab shells out to the React/Ink TUI. Force it to the +# trusted prebuilt bundle baked into the image so `hermes dashboard --tui +# --skip-build` never honors a stale/user-controlled TUI path or tries to run +# npm under root-owned /opt/hermes at runtime. Remove this when upstream Hermes +# reliably discovers the prebaked ui-tui bundle without HERMES_TUI_DIR. +if [ -f /opt/hermes/ui-tui/dist/entry.js ]; then + export HERMES_TUI_DIR="/opt/hermes/ui-tui" +fi + # ── Early stderr/stdout capture ────────────────────────────────── # Capture all entrypoint output to /tmp/nemoclaw-start.log so startup # failures before /tmp/gateway.log exists are still diagnosable. @@ -646,6 +655,11 @@ export https_proxy="$_PROXY_URL" export no_proxy="$_NO_PROXY_VAL" export HERMES_HOME="${HERMES_DIR}" PROXYEOF + cat <<'TUIENVEOF' +if [ -f /opt/hermes/ui-tui/dist/entry.js ]; then + export HERMES_TUI_DIR="/opt/hermes/ui-tui" +fi +TUIENVEOF for _ca_env_name in SSL_CERT_FILE CURL_CA_BUNDLE REQUESTS_CA_BUNDLE GIT_SSL_CAINFO; do _ca_env_value="${!_ca_env_name:-}" if [ -n "$_ca_env_value" ]; then diff --git a/test/hermes-start.test.ts b/test/hermes-start.test.ts index 29eab486ccd..fc70775a3ff 100644 --- a/test/hermes-start.test.ts +++ b/test/hermes-start.test.ts @@ -526,6 +526,8 @@ describe("agents/hermes/start.sh runtime shell env", () => { expect(run.result.status).toBe(0); expect(run.envFileMode).toBe("444"); expect(run.envFileContent).toContain(`export HERMES_HOME="${run.hermesHome}"`); + expect(run.envFileContent).toContain('export HERMES_TUI_DIR="/opt/hermes/ui-tui"'); + expect(run.envFileContent).not.toContain('HERMES_TUI_DIR="${HERMES_TUI_DIR:-'); expect(run.envFileContent).toContain(`export SSL_CERT_FILE=${escapedCaFile}`); expect(run.envFileContent).toContain("# nemoclaw-configure-guard begin"); expect(run.envFileContent).toContain("hermes() {");