hermes: show activity during dashboard bootstrap

This commit is contained in:
jenkins 2026-08-16 03:33:41 -03:00
parent ece8b009f8
commit 7d284871e8
4 changed files with 51 additions and 1 deletions

View File

@ -837,6 +837,44 @@ function replaceOnce(source, before, after, label) {
return source.replace(before, after); 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,
' <body>\n <div id="root"></div>',
` <body>
<div
id="hermes-resume-bootstrap"
role="status"
aria-live="polite"
aria-busy="true"
hidden
style="position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;background:#000;color:#f4f0df;font:14px/1.5 ui-monospace,monospace;text-align:center"
>
<div>
<strong style="display:block;margin-bottom:8px">Opening Hermes worker activity…</strong>
<span>Connecting to the accessible, durable transcript.</span>
</div>
</div>
<script>
(() => {
const params = new URLSearchParams(window.location.search);
const loading = document.getElementById("hermes-resume-bootstrap");
if (loading && window.location.pathname === "/chat" && params.has("resume")) {
loading.hidden = false;
}
})();
</script>
<div id="root"></div>`,
"resumed activity bootstrap",
);
fs.writeFileSync(path, source);
}
// A WebGL context can be discarded while a browser tab sleeps or loses its // 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 // GPU process. xterm's canvas renderer is fast enough for this single PTY and
// can be repainted deterministically from the server-side replay buffer. // can be repainted deterministically from the server-side replay buffer.

View File

@ -158,6 +158,12 @@ export function SessionActivityPanel({
const scrollRef = useRef<HTMLDivElement | null>(null); const scrollRef = useRef<HTMLDivElement | null>(null);
const stickToBottom = useRef(true); 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 () => { const load = useCallback(async () => {
if (requestRunning.current) return; if (requestRunning.current) return;
requestRunning.current = true; requestRunning.current = true;

View File

@ -4,7 +4,7 @@ kind: Kustomization
namespace: hermes namespace: hermes
images: images:
- name: registry.bstein.dev/bstein/hermes-agent - name: registry.bstein.dev/bstein/hermes-agent
digest: sha256:de9463fab178916ff9c880af2f8dae7ffc77585e3f7b73e15f5369454ce0f3dd digest: sha256:8a4a33df755607f69bc8803181cc8d38edf65330343ec8bf94ac5e24ba8d5e58
resources: resources:
- namespace.yaml - namespace.yaml
- vault-serviceaccount.yaml - vault-serviceaccount.yaml

View File

@ -199,6 +199,12 @@ def test_gateway_image_honors_ui_model_and_caps_reasoning():
assert "visibleLimit)," in activity assert "visibleLimit)," in activity
assert "function displayText(value: unknown)" in activity assert "function displayText(value: unknown)" in activity
assert "open && (" 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 assert "Open Telegram setup" in dockerfile