#!/usr/bin/env python3 """Apply Atlas baseline UI policy patches to the pinned Hermes WebUI.""" from pathlib import Path config = Path("/opt/hermes-webui/api/config.py") source = config.read_text(encoding="utf-8") before = ( 'VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh", "max")' ) after = 'VALID_REASONING_EFFORTS = ("minimal", "low", "medium", "high", "xhigh")' if source.count(before) != 1: raise SystemExit("Hermes WebUI reasoning-effort patch context changed") config.write_text(source.replace(before, after, 1), encoding="utf-8") index = Path("/opt/hermes-webui/static/index.html") source = index.read_text(encoding="utf-8") before = '
Max
\n' if source.count(before) != 1: raise SystemExit("Hermes WebUI xhigh UI patch context changed") index.write_text(source.replace(before, "", 1), encoding="utf-8") # Enable edge-to-edge safe-area insets on mobile / standalone PWAs. The theme # already pads the titlebar with env(safe-area-inset-top/left/right) and the # composer reserves env(safe-area-inset-bottom) (hermes-brand.css), but env() # resolves to 0 unless the viewport opts into viewport-fit=cover. Android # standalone PWAs render the webview edge-to-edge regardless, so without cover # there is no inset to pad with and the composer's control row hides behind the # gesture bar. Opt in so those insets carry real values. source = index.read_text(encoding="utf-8") before = ( '' ) after = ( '' ) if source.count(before) != 1: raise SystemExit("Hermes WebUI viewport-fit patch context changed") index.write_text(source.replace(before, after, 1), encoding="utf-8") # oauth2-proxy returns 401 for browser API and health probes when the secure # session expires. Re-enter OIDC with the complete return path. ui = Path("/opt/hermes-webui/static/ui.js") source = ui.read_text(encoding="utf-8") before = """ const res=await fetcher(_offlineHealthUrl(),opts); return !!(res&&res.ok); """ after = """ const res=await fetcher(_offlineHealthUrl(),opts); if(res&&(res.status===401||res.status===403)){ const rd=window.location.pathname+window.location.search+window.location.hash; window.location.assign('/oauth2/start?rd='+encodeURIComponent(rd)); return false; } return !!(res&&res.ok); """ if source.count(before) != 1: raise SystemExit("Hermes WebUI auth-recovery patch context changed") ui.write_text(source.replace(before, after, 1), encoding="utf-8") # Make delegated session hierarchy obvious and collapsible in the sidebar. sessions = Path("/opt/hermes-webui/static/sessions.js") source = sessions.read_text(encoding="utf-8") before = """ const childLabel=t('session_meta_children', childCount); childCountEl.textContent=childLabel; childCountEl.title=_sessionChildBadgeTooltip(childLabel); """ after = """ const childLabel=t('session_meta_children', childCount); const childrenExpanded=_expandedChildSessionKeys.has(lineageKey)||!!searchQueryRaw; childCountEl.textContent=(childrenExpanded?'▾ ':'▸ ')+childLabel; childCountEl.setAttribute('aria-expanded',childrenExpanded?'true':'false'); childCountEl.title=_sessionChildBadgeTooltip(childLabel); """ if source.count(before) != 1: raise SystemExit("Hermes WebUI child-session toggle patch context changed") sessions.write_text(source.replace(before, after, 1), encoding="utf-8") # A profile's model is only its default; label that scope in both render paths. panels = Path("/opt/hermes-webui/static/panels.js") source = panels.read_text(encoding="utf-8") before = " if (typeof p.model === 'string' && p.model) meta.push(p.model.split('/').pop());\n" after = """ if (typeof p.model === 'string' && p.model) { const routeLabels = { 'atlas/auto/fast': 'Automatic · Fast', 'atlas/auto/balanced': 'Automatic · Balanced', 'atlas/auto/deep': 'Automatic · Deep', 'atlas/auto/maximum': 'Automatic · Maximum', }; meta.push('profile default: ' + (routeLabels[p.model] || p.model.split('/').pop())); } """ if source.count(before) != 2: raise SystemExit("Hermes WebUI profile-model label patch context changed") panels.write_text(source.replace(before, after, 2), encoding="utf-8")