fix(hermes): put stock TUI in foreground

This commit is contained in:
jenkins 2026-08-10 05:45:36 -03:00
parent cb02c5112e
commit 8ae7105ea0
3 changed files with 55 additions and 92 deletions

View File

@ -5,10 +5,10 @@ FROM nousresearch/hermes-agent@sha256:9c841866021c54c4596849f6135717e8a4d52ba510
USER root USER root
# Codex uses the distribution bubblewrap package when a bounded sandbox is # Codex uses the distribution bubblewrap package when a bounded sandbox is
# requested. Full-access workers rely on the pod as their isolation boundary, # requested. tmux keeps the stock Hermes TUI alive across browser reconnects;
# but keep bwrap installed for diagnostics and any future restricted profile. # Herdr remains a background worker transport instead of the visible shell.
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends bubblewrap \ && apt-get install -y --no-install-recommends bubblewrap tmux \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Keep a credential-free search provider available for private chat tenants. # Keep a credential-free search provider available for private chat tenants.

View File

@ -21,10 +21,10 @@ spec:
app: hermes-agent app: hermes-agent
annotations: annotations:
ai.bstein.dev/role: project-coordinator ai.bstein.dev/role: project-coordinator
ai.bstein.dev/execution: Hermes delegation with durable Codex and Claude Code CLI workers ai.bstein.dev/execution: Stock Hermes TUI with durable Codex and Claude Code CLI workers
ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback
ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available
ai.bstein.dev/config-rev: "20260810-per-subtask-routing" ai.bstein.dev/config-rev: "20260810-stock-tui-per-subtask-routing"
vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: hermes-agent vault.hashicorp.com/role: hermes-agent
vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens
@ -142,7 +142,7 @@ spec:
requests: {cpu: 25m, memory: 32Mi} requests: {cpu: 25m, memory: 32Mi}
limits: {cpu: 100m, memory: 64Mi} limits: {cpu: 100m, memory: 64Mi}
- name: install-agent-tools - name: install-agent-tools
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- sh - sh
@ -194,7 +194,7 @@ spec:
requests: {cpu: 100m, memory: 256Mi} requests: {cpu: 100m, memory: 256Mi}
limits: {cpu: "1", memory: 1Gi} limits: {cpu: "1", memory: 1Gi}
- name: patch-auth - name: patch-auth
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- /opt/hermes/.venv/bin/python - /opt/hermes/.venv/bin/python
@ -217,7 +217,7 @@ spec:
requests: {cpu: 25m, memory: 64Mi} requests: {cpu: 25m, memory: 64Mi}
limits: {cpu: 100m, memory: 128Mi} limits: {cpu: 100m, memory: 128Mi}
- name: bootstrap-coordinator - name: bootstrap-coordinator
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- /opt/hermes/.venv/bin/python - /opt/hermes/.venv/bin/python
@ -243,7 +243,7 @@ spec:
requests: {cpu: 50m, memory: 128Mi} requests: {cpu: 50m, memory: 128Mi}
limits: {cpu: 500m, memory: 512Mi} limits: {cpu: 500m, memory: 512Mi}
- name: install-herdr-integrations - name: install-herdr-integrations
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- sh - sh
@ -272,7 +272,7 @@ spec:
requests: {cpu: 25m, memory: 32Mi} requests: {cpu: 25m, memory: 32Mi}
limits: {cpu: 250m, memory: 128Mi} limits: {cpu: 250m, memory: 128Mi}
- name: prepare-ttyd-index - name: prepare-ttyd-index
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- /opt/hermes/.venv/bin/python - /opt/hermes/.venv/bin/python
@ -294,7 +294,7 @@ spec:
limits: {cpu: 250m, memory: 128Mi} limits: {cpu: 250m, memory: 128Mi}
containers: containers:
- name: hermes - name: hermes
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: [/opt/hermes/.venv/bin/hermes] command: [/opt/hermes/.venv/bin/hermes]
args: [gateway, run, --no-supervise] args: [gateway, run, --no-supervise]
@ -409,7 +409,7 @@ spec:
requests: {cpu: 50m, memory: 128Mi} requests: {cpu: 50m, memory: 128Mi}
limits: {cpu: 750m, memory: 1Gi} limits: {cpu: 750m, memory: 1Gi}
- name: herdr-tui - name: herdr-tui
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: [/bin/sh, -ec] command: [/bin/sh, -ec]
args: args:
@ -426,28 +426,48 @@ spec:
--terminal-type xterm-256color \ --terminal-type xterm-256color \
--index /ttyd-index/index.html \ --index /ttyd-index/index.html \
--client-option disableReconnect=true \ --client-option disableReconnect=true \
--client-option "titleFixed=Hermes Agent - HERDR" \ --client-option "titleFixed=Hermes Agent" \
--client-option fontSize=15 \ --client-option fontSize=15 \
/bin/sh -c ' /usr/bin/tmux new-session -A \
while true; do -s hermes-coordinator \
/opt/data/tools/bin/herdr -c /opt/data/workspace \
exit_code=$? /bin/sh -lc '
printf "\r\nHerdr disconnected (exit %s); reconnecting in 2 seconds...\r\n" "${exit_code}" while true; do
sleep 2 /opt/hermes/.venv/bin/hermes \
done --continue \
' --tui \
--yolo \
--accept-hooks \
--checkpoints
exit_code=$?
printf "\r\nHermes exited (exit %s); resuming in 2 seconds...\r\n" "${exit_code}"
sleep 2
done
'
ports: ports:
- {name: herdr-tui, containerPort: 7681, protocol: TCP} - {name: herdr-tui, containerPort: 7681, protocol: TCP}
env: env:
- {name: HOME, value: /opt/data/home} - {name: HOME, value: /opt/data/home}
- {name: HERMES_HOME, value: /opt/data}
- {name: HERMES_AUTH_FILE, value: /shared-auth/auth.json}
- {name: CODEX_HOME, value: /opt/data/home/.codex} - {name: CODEX_HOME, value: /opt/data/home/.codex}
- {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude} - {name: CLAUDE_CONFIG_DIR, value: /opt/data/home/.claude}
- {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml} - {name: HERDR_CONFIG_PATH, value: /opt/data/home/.config/herdr/config.toml}
- {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock} - {name: HERDR_SOCKET_PATH, value: /opt/data/herdr/herdr.sock}
- {name: PATH, value: /opt/coordinator:/opt/data/tools/bin:/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin} - {name: KUBECONFIG, value: /opt/data/home/.kube/config}
- {name: PYTHONPATH, value: /opt/hermes}
- {name: PATH, value: /opt/coordinator:/opt/data/tools/bin:/opt/data/home/.local/bin:/opt/hermes/.venv/bin:/usr/local/bin:/usr/bin:/bin}
- {name: PLAYWRIGHT_BROWSERS_PATH, value: /opt/hermes/.playwright}
- {name: AGENT_BROWSER_EXECUTABLE_PATH, value: /opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell}
- {name: AGENT_BROWSER_ARGS, value: "--no-sandbox,--disable-dev-shm-usage"}
volumeMounts: volumeMounts:
- {name: home, mountPath: /opt/data} - {name: home, mountPath: /opt/data}
- {name: provider-auth, mountPath: /shared-auth}
- {name: coordinator, mountPath: /opt/coordinator, readOnly: true} - {name: coordinator, mountPath: /opt/coordinator, readOnly: true}
- {name: auth-patch, mountPath: /opt/hermes/hermes_cli/auth.py, subPath: auth.py}
- {name: coordinator, mountPath: /opt/data/home/.local/bin/herdr-dispatch, subPath: herdr_dispatch.py, readOnly: true}
- {name: kubeconfig, mountPath: /opt/data/home/.kube/config, subPath: config, readOnly: true}
- {name: auto-router-plugin, mountPath: /opt/data/plugins/auto-router, readOnly: true}
- {name: tmp, mountPath: /tmp} - {name: tmp, mountPath: /tmp}
- {name: ttyd-index, mountPath: /ttyd-index, readOnly: true} - {name: ttyd-index, mountPath: /ttyd-index, readOnly: true}
startupProbe: startupProbe:
@ -478,7 +498,7 @@ spec:
requests: {cpu: 25m, memory: 64Mi} requests: {cpu: 25m, memory: 64Mi}
limits: {cpu: 500m, memory: 512Mi} limits: {cpu: 500m, memory: 512Mi}
- name: herdr-server - name: herdr-server
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- sh - sh
@ -495,60 +515,6 @@ spec:
if herdr status server >/dev/null 2>&1; then break; fi if herdr status server >/dev/null 2>&1; then break; fi
sleep 1 sleep 1
done done
pane_file=/opt/data/herdr/coordinator-pane-id
pane="$(cat "${pane_file}" 2>/dev/null || true)"
pane_live=no
if [ -n "${pane}" ]; then
for attempt in $(seq 1 20); do
if herdr pane process-info --pane "${pane}" >/dev/null 2>&1; then
pane_live=yes
break
fi
sleep 1
done
fi
if [ "${pane_live}" != "yes" ]; then
if [ -n "${pane}" ]; then
workspace="${pane%%:*}"
herdr workspace close "${workspace}" >/dev/null 2>&1 || true
fi
created="$(herdr workspace create \
--cwd /opt/data/workspace \
--label coordinator \
--env HERMES_HOME=/opt/data \
--env HERMES_AUTH_FILE=/shared-auth/auth.json \
--env HOME=/opt/data/home \
--env KUBECONFIG=/opt/data/home/.kube/config \
--env PYTHONPATH=/opt/hermes \
--env PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright \
--env AGENT_BROWSER_EXECUTABLE_PATH=/opt/hermes/.playwright/chromium_headless_shell-1228/chrome-linux/headless_shell \
--env AGENT_BROWSER_ARGS=--no-sandbox,--disable-dev-shm-usage \
--focus)"
pane="$(printf '%s' "${created}" | /opt/hermes/.venv/bin/python -c \
'import json,sys; print(json.load(sys.stdin)["result"]["root_pane"]["pane_id"])')"
printf '%s\n' "${pane}" > "${pane_file}"
fi
active_workspace="${pane%%:*}"
coordinator_workspaces="$(herdr workspace list | /opt/hermes/.venv/bin/python -c \
'import json,sys; active=sys.argv[1]; payload=json.load(sys.stdin); print(" ".join(item["workspace_id"] for item in payload["result"]["workspaces"] if item.get("label") == "coordinator" and item["workspace_id"] != active))' \
"${active_workspace}")"
for stale_workspace in ${coordinator_workspaces}; do
herdr workspace close "${stale_workspace}" >/dev/null 2>&1 || true
done
process_info="$(herdr pane process-info --pane "${pane}" 2>/dev/null || printf '%s\n' '{"result":{"process_info":{"foreground_processes":[]}}}')"
coordinator_running="$(printf '%s' "${process_info}" | /opt/hermes/.venv/bin/python -c \
'import json,sys; processes=json.load(sys.stdin)["result"]["process_info"].get("foreground_processes", []); print("yes" if any("hermes" in " ".join(str(p.get(k) or "") for k in ("name", "argv0", "cmdline")) for p in processes) else "no")')"
if [ "${coordinator_running}" != "yes" ]; then
herdr agent start coordinator \
--kind hermes \
--pane "${pane}" \
--timeout 60000
fi
/opt/hermes/.venv/bin/python /opt/coordinator/herdr_tab_router.py \
--workspace-label coordinator \
--interval 1 &
router_pid=$!
trap 'kill "${router_pid}" "${server_pid}" 2>/dev/null || true' TERM INT
wait "${server_pid}" wait "${server_pid}"
env: env:
- {name: HERMES_HOME, value: /opt/data} - {name: HERMES_HOME, value: /opt/data}
@ -582,7 +548,7 @@ spec:
requests: {cpu: 50m, memory: 128Mi} requests: {cpu: 50m, memory: 128Mi}
limits: {cpu: "1", memory: 2Gi} limits: {cpu: "1", memory: 2Gi}
- name: model-steward - name: model-steward
image: registry.bstein.dev/bstein/hermes-agent@sha256:3f454c13b6050e590598a7e067ef6f2c9b0e7f395219b1d294e59c0020d7b50c image: registry.bstein.dev/bstein/hermes-agent@sha256:46de11122226a68adad6d902e81ef9b4b53deb0433dcc61f57fa43fae2e3a3a5
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: [/opt/hermes/.venv/bin/python, /opt/coordinator/hermes_coordinator.py, --loop, --interval, "3600"] command: [/opt/hermes/.venv/bin/python, /opt/coordinator/hermes_coordinator.py, --loop, --interval, "3600"]
env: env:

View File

@ -580,22 +580,19 @@ def test_agent_installs_hermes_integration_before_startup():
assert "--no-sandbox" in env["AGENT_BROWSER_ARGS"] assert "--no-sandbox" in env["AGENT_BROWSER_ARGS"]
server_command = containers["herdr-server"]["command"][-1] server_command = containers["herdr-server"]["command"][-1]
assert 'herdr pane process-info --pane "${pane}"' in server_command assert "herdr server" in server_command
assert 'herdr workspace close "${workspace}"' in server_command assert "herdr agent start coordinator" not in server_command
assert 'item.get("label") == "coordinator"' in server_command assert "/opt/coordinator/herdr_tab_router.py" not in server_command
assert 'item["workspace_id"] != active' in server_command
assert 'herdr workspace close "${stale_workspace}"' in server_command
assert "herdr agent start coordinator" in server_command
assert "/opt/coordinator/herdr_tab_router.py" in server_command
assert "--workspace-label coordinator" in server_command
assert "--kind hermes" in server_command
assert "--timeout 60000" in server_command
assert "--env AGENT_BROWSER_EXECUTABLE_PATH=" in server_command
tui_command = containers["herdr-tui"]["args"][0] tui_command = containers["herdr-tui"]["args"][0]
assert "while true" in tui_command assert "/usr/bin/tmux new-session -A" in tui_command
assert "Herdr disconnected" in tui_command assert "-s hermes-coordinator" in tui_command
assert "/opt/data/tools/bin/herdr" in tui_command assert "/opt/hermes/.venv/bin/hermes" in tui_command
assert "--continue" in tui_command
assert "--tui" in tui_command
assert "--yolo" in tui_command
assert "--accept-hooks" in tui_command
assert "--checkpoints" in tui_command
def test_agent_mounts_auto_router_into_both_hermes_runtimes(): def test_agent_mounts_auto_router_into_both_hermes_runtimes():