atlas-iac/services/hermes/router/session_continuity.go
2026-08-17 14:39:21 +00:00

98 lines
4.9 KiB
Go

package main
const sessionContinuityJS = `(() => {
const match = location.pathname.match(/^\/session\/([^/]+)\/?$/);
if (!match) return;
let sessionId;
try { sessionId = decodeURIComponent(match[1]); } catch (_) { sessionId = match[1]; }
const started = Date.now();
let timer = 0;
let request = null;
const card = document.createElement('aside');
card.id = 'hermes-session-continuity';
card.setAttribute('role', 'status');
card.setAttribute('aria-live', 'polite');
card.setAttribute('aria-atomic', 'true');
card.hidden = true;
card.style.cssText = 'position:fixed;right:16px;bottom:16px;z-index:1000;max-width:min(420px,calc(100vw - 32px));box-sizing:border-box;padding:10px 12px;border:1px solid #475569;border-radius:10px;background:#111827;color:#e5e7eb;font:13px/1.4 system-ui,sans-serif;box-shadow:0 8px 28px #0008';
const message = document.createElement('span');
const newChat = document.createElement('a');
newChat.href = '/';
newChat.textContent = ' Start a new chat.';
newChat.style.color = '#7dd3fc';
card.append(message, newChat);
document.body.appendChild(card);
const show = (text, busy, link) => {
message.textContent = text;
card.hidden = false;
card.setAttribute('aria-busy', busy ? 'true' : 'false');
newChat.hidden = !link;
};
const hide = () => { card.hidden = true; card.setAttribute('aria-busy', 'false'); };
const activityLabel = (messages) => {
const latest = messages[messages.length - 1] || {};
if (latest.tool_name) return 'tool ' + String(latest.tool_name).slice(0, 80);
if (Array.isArray(latest.tool_calls) && latest.tool_calls.length) {
const call = latest.tool_calls[latest.tool_calls.length - 1] || {};
return 'tool ' + String((call.function || {}).name || 'activity').slice(0, 80);
}
if (latest.activity_event) return String(latest.activity_event).replace(/[._-]+/g, ' ').slice(0, 80);
if (latest.observed && typeof latest.content === 'string') return latest.content.slice(0, 120);
return latest.role === 'assistant' ? 'assistant update' : latest.role === 'user' ? 'request stored' : 'working';
};
const authOrMissing = (response) => {
if (response.status === 401 || response.status === 403) {
const rd = location.pathname + location.search + location.hash;
location.assign('/oauth2/start?rd=' + encodeURIComponent(rd));
return 'auth';
}
if (response.status === 404) {
show('This session is unavailable to this account.', false, true);
schedule(10000);
return 'missing';
}
return '';
};
const schedule = (delay) => {
clearTimeout(timer);
timer = window.setTimeout(poll, delay);
};
async function poll() {
if (request) return;
request = new AbortController();
const timeout = window.setTimeout(() => request && request.abort(), 8000);
try {
const root = '/api/sessions/' + encodeURIComponent(sessionId);
const messageResponse = await fetch(root + '/messages?limit=24&hermes_fallback=1', {cache:'no-store', credentials:'same-origin', signal:request.signal});
if (authOrMissing(messageResponse)) return;
if (!messageResponse.ok) throw new Error('session messages poll failed');
const payload = await messageResponse.json();
const messages = Array.isArray(payload.messages) ? payload.messages : [];
const resolvedId = typeof payload.session_id === 'string' && payload.session_id ? payload.session_id : sessionId;
const detailResponse = await fetch('/api/sessions/' + encodeURIComponent(resolvedId), {cache:'no-store', credentials:'same-origin', signal:request.signal});
if (authOrMissing(detailResponse)) return;
if (!detailResponse.ok) throw new Error('session detail poll failed');
const session = await detailResponse.json();
const lastActive = Number(session.last_active || session.started_at || 0);
const durableWorker = session.source === 'api_server' && Boolean(session.parent_session_id);
const recentlyInteractive = Number.isFinite(lastActive) && Date.now() / 1000 - lastActive < 300;
const active = session.ended_at == null && messages.length > 0 && (durableWorker || recentlyInteractive);
if (active) show('Hermes is working. Latest stored activity: ' + activityLabel(messages) + '.', true, false);
else if (!messages.length && Date.now() - started >= 4000) show('This session has no renderable messages yet. It may be new or no longer available.', false, true);
else hide();
schedule(document.hidden ? 10000 : 2500);
} catch (_) {
show('Session updates disconnected. Retrying without changing this session…', true, false);
schedule(document.hidden ? 10000 : 3000);
} finally {
clearTimeout(timeout);
request = null;
}
}
addEventListener('online', () => schedule(0));
addEventListener('pageshow', () => schedule(0));
document.addEventListener('visibilitychange', () => schedule(0));
schedule(1200);
})();`