diff --git a/dockerfiles/Dockerfile.hermes-agent b/dockerfiles/Dockerfile.hermes-agent
index 393b8851..98f93ee2 100644
--- a/dockerfiles/Dockerfile.hermes-agent
+++ b/dockerfiles/Dockerfile.hermes-agent
@@ -837,6 +837,44 @@ function replaceOnce(source, before, after, label) {
return source.replace(before, after);
}
+// The dashboard bundle takes a few seconds to hydrate on slower clients. A
+// resumed worker must announce that it is connecting instead of exposing an
+// unexplained black terminal canvas during that interval.
+{
+ const path = "/opt/hermes/web/index.html";
+ let source = fs.readFileSync(path, "utf8");
+ source = replaceOnce(
+ source,
+ '
\n ',
+ `
+
+
+ Opening Hermes worker activity…
+ Connecting to the accessible, durable transcript.
+
+
+
+ `,
+ "resumed activity bootstrap",
+ );
+ fs.writeFileSync(path, source);
+}
+
// A WebGL context can be discarded while a browser tab sleeps or loses its
// GPU process. xterm's canvas renderer is fast enough for this single PTY and
// can be repainted deterministically from the server-side replay buffer.
diff --git a/dockerfiles/hermes-session-activity-panel.tsx b/dockerfiles/hermes-session-activity-panel.tsx
index 63c857f2..13de53cc 100644
--- a/dockerfiles/hermes-session-activity-panel.tsx
+++ b/dockerfiles/hermes-session-activity-panel.tsx
@@ -158,6 +158,12 @@ export function SessionActivityPanel({
const scrollRef = useRef(null);
const stickToBottom = useRef(true);
+ useEffect(() => {
+ // index.html exposes an immediate, screen-reader-visible loading state for
+ // resumed links. Remove it only after this durable transcript has mounted.
+ document.getElementById("hermes-resume-bootstrap")?.remove();
+ }, []);
+
const load = useCallback(async () => {
if (requestRunning.current) return;
requestRunning.current = true;
diff --git a/services/hermes/kustomization.yaml b/services/hermes/kustomization.yaml
index b6669b6d..c2066ed1 100644
--- a/services/hermes/kustomization.yaml
+++ b/services/hermes/kustomization.yaml
@@ -4,7 +4,7 @@ kind: Kustomization
namespace: hermes
images:
- name: registry.bstein.dev/bstein/hermes-agent
- digest: sha256:de9463fab178916ff9c880af2f8dae7ffc77585e3f7b73e15f5369454ce0f3dd
+ digest: sha256:8a4a33df755607f69bc8803181cc8d38edf65330343ec8bf94ac5e24ba8d5e58
resources:
- namespace.yaml
- vault-serviceaccount.yaml
diff --git a/testing/tests/test_hermes_chat_quality.py b/testing/tests/test_hermes_chat_quality.py
index 7f4cf08b..2c62d639 100644
--- a/testing/tests/test_hermes_chat_quality.py
+++ b/testing/tests/test_hermes_chat_quality.py
@@ -199,6 +199,12 @@ def test_gateway_image_honors_ui_model_and_caps_reasoning():
assert "visibleLimit)," in activity
assert "function displayText(value: unknown)" in activity
assert "open && (" in activity
+ assert 'document.getElementById("hermes-resume-bootstrap")?.remove()' in activity
+ assert 'id="hermes-resume-bootstrap"' in dockerfile
+ assert 'role="status"' in dockerfile
+ assert 'aria-busy="true"' in dockerfile
+ assert "Opening Hermes worker activity…" in dockerfile
+ assert 'params.has("resume")' in dockerfile
assert "Open Telegram setup" in dockerfile