Improve local_server and terminal_session coverage

This commit is contained in:
GitHub Copilot
2026-01-22 14:09:34 +00:00
parent 0cfb3b0a2f
commit d03f32bf69
4 changed files with 373 additions and 19 deletions
+69 -18
View File
@@ -8,6 +8,7 @@ import hashlib
import io
import json
import logging
import re
import signal
from pathlib import Path
from typing import TYPE_CHECKING
@@ -37,6 +38,12 @@ SCREENSHOT_MAX_BYTES = 65536
SCREENSHOT_CACHE_SECONDS = 1.0
SCREENSHOT_MAX_CACHE_SECONDS = 60.0
SVG_MONO_FONT_STACK = (
'ui-monospace, "SFMono-Regular", "FiraCode Nerd Font", "FiraMono Nerd Font", '
'"Fira Code", "Roboto Mono", Menlo, Monaco, Consolas, "Liberation Mono", '
'"DejaVu Sans Mono", "Courier New", monospace'
)
WEBTERM_STATIC_PATH = Path(__file__).parent / "static"
@@ -84,6 +91,22 @@ class LocalClientConnector(SessionConnector):
await self.server.handle_session_close(self.session_id, self.route_key)
def _rewrite_svg_fonts(svg: str) -> str:
"""Make Rich SVG output self-contained and aligned with our monospace styling."""
# Rich export_svg embeds @font-face rules that reference external CDNs.
svg = re.sub(r"@font-face\s*\{.*?\}\s*", "", svg, flags=re.DOTALL)
# Force our local monospace stack even if Rich sets font-family to Fira Code.
override = f"\ntext {{ font-family: {SVG_MONO_FONT_STACK} !important; }}\n"
if "</style>" in svg:
svg = svg.replace("</style>", override + "</style>", 1)
else:
svg = svg.replace("<svg ", f"<svg><style>{override}</style> ", 1)
return svg
def _apply_carriage_returns(text: str) -> list[str]:
"""Interpret \r as 'return to start of line' (overwrite), not a newline.
@@ -110,6 +133,22 @@ class LocalServer:
def mark_route_activity(self, route_key: str) -> None:
self._route_last_activity[route_key] = asyncio.get_event_loop().time()
def _get_cached_screenshot_response(
self, request: web.Request, route_key: str
) -> web.Response | None:
cached = self._screenshot_cache.get(route_key)
if cached is None:
return None
etag = self._screenshot_cache_etag.get(route_key)
if etag and request.headers.get("If-None-Match") == etag:
raise web.HTTPNotModified(headers={"ETag": etag, "Cache-Control": "no-cache"})
headers = {"Cache-Control": "no-cache"}
if etag:
headers["ETag"] = etag
return web.Response(text=cached[1], content_type="image/svg+xml", headers=headers)
def _get_screenshot_cache_ttl(self, route_key: str, now: float) -> float:
last_activity = self._route_last_activity.get(route_key, 0.0)
idle_for = max(0.0, now - last_activity)
@@ -157,6 +196,7 @@ class LocalServer:
self._screenshot_cache_etag: dict[str, str] = {}
self._screenshot_locks: dict[str, asyncio.Lock] = {}
self._route_last_activity: dict[str, float] = {}
self._screenshot_last_rendered_activity: dict[str, float] = {}
@property
def app_count(self) -> int:
@@ -288,12 +328,14 @@ class LocalServer:
msg_type = envelope[0]
if msg_type == "stdin":
self.mark_route_activity(route_key)
data = envelope[1] if len(envelope) > 1 else ""
session_process = self.session_manager.get_session_by_route_key(RouteKey(route_key))
if session_process:
await session_process.send_bytes(data.encode("utf-8"))
elif msg_type == "resize":
self.mark_route_activity(route_key)
size_data = envelope[1] if len(envelope) > 1 else {}
width = max(1, min(500, int(size_data.get("width", 80))))
height = max(1, min(500, int(size_data.get("height", 24))))
@@ -422,6 +464,15 @@ class LocalServer:
if session_process is None or not hasattr(session_process, "get_replay_buffer"):
raise web.HTTPNotFound(text="Session not found")
# If nothing has changed since the last render, serve cached screenshot without
# touching the session replay buffer.
last_activity = self._route_last_activity.get(route_key, 0.0)
last_rendered_activity = self._screenshot_last_rendered_activity.get(route_key, -1.0)
if last_activity <= last_rendered_activity:
cached_response = self._get_cached_screenshot_response(request, route_key)
if cached_response is not None:
return cached_response
replay_data = await session_process.get_replay_buffer() # type: ignore[func-returns-value]
if len(replay_data) > SCREENSHOT_MAX_BYTES:
replay_data = replay_data[-SCREENSHOT_MAX_BYTES:]
@@ -447,16 +498,17 @@ class LocalServer:
ttl = self._get_screenshot_cache_ttl(route_key, now)
cached = self._screenshot_cache.get(route_key)
if cached is not None:
etag = self._screenshot_cache_etag.get(route_key)
if etag and request.headers.get("If-None-Match") == etag:
raise web.HTTPNotModified(headers={"ETag": etag, "Cache-Control": "no-cache"})
# If we have a cached screenshot and the session is idle, keep serving it until
# new activity occurs (no periodic re-render).
if cached is not None and self._route_last_activity.get(route_key, 0.0) == 0.0:
cached_response = self._get_cached_screenshot_response(request, route_key)
if cached_response is not None:
return cached_response
if (now - cached[0]) < ttl:
headers = {"Cache-Control": "no-cache"}
if etag:
headers["ETag"] = etag
return web.Response(text=cached[1], content_type="image/svg+xml", headers=headers)
if cached is not None and (now - cached[0]) < ttl:
cached_response = self._get_cached_screenshot_response(request, route_key)
if cached_response is not None:
return cached_response
lock = self._screenshot_locks.get(route_key)
if lock is None:
@@ -467,15 +519,10 @@ class LocalServer:
# Another request may have refreshed the cache while we waited.
ttl = self._get_screenshot_cache_ttl(route_key, now)
cached = self._screenshot_cache.get(route_key)
etag = self._screenshot_cache_etag.get(route_key)
if cached is not None:
if etag and request.headers.get("If-None-Match") == etag:
raise web.HTTPNotModified(headers={"ETag": etag, "Cache-Control": "no-cache"})
if (now - cached[0]) < ttl:
headers = {"Cache-Control": "no-cache"}
if etag:
headers["ETag"] = etag
return web.Response(text=cached[1], content_type="image/svg+xml", headers=headers)
if cached is not None and (now - cached[0]) < ttl:
cached_response = self._get_cached_screenshot_response(request, route_key)
if cached_response is not None:
return cached_response
def _render_svg() -> str:
console = Console(record=True, width=width, height=height, file=io.StringIO())
@@ -505,9 +552,13 @@ class LocalServer:
)
svg = await asyncio.to_thread(_render_svg)
svg = _rewrite_svg_fonts(svg)
etag = hashlib.sha1(svg.encode("utf-8"), usedforsecurity=False).hexdigest()
self._screenshot_cache[route_key] = (asyncio.get_event_loop().time(), svg)
self._screenshot_cache_etag[route_key] = etag
self._screenshot_last_rendered_activity[route_key] = self._route_last_activity.get(
route_key, 0.0
)
headers = {"Cache-Control": "no-cache", "ETag": etag}
return web.Response(text=svg, content_type="image/svg+xml", headers=headers)