Stage 404: PR #2716 — Performance optimizations by @dobby-d-elf (nesquena APPROVED)

This commit is contained in:
hermes-agent
2026-05-24 18:08:41 +00:00
13 changed files with 217 additions and 104 deletions
+19 -50
View File
@@ -2153,9 +2153,27 @@ def all_sessions(diag=None):
_diag_stage(diag, "all_sessions.prune_index")
with LOCK:
in_memory_ids = set(SESSIONS.keys())
try:
persisted_ids = {
p.stem
for p in SESSION_DIR.glob('*.json')
if not p.name.startswith('_')
}
except Exception:
persisted_ids = None
index = [
s for s in index
if _index_entry_exists(s.get('session_id'), in_memory_ids=in_memory_ids)
if (
str(s.get('session_id') or '') in in_memory_ids
or (
persisted_ids is not None
and str(s.get('session_id') or '') in persisted_ids
)
or (
persisted_ids is None
and _index_entry_exists(s.get('session_id'), in_memory_ids=in_memory_ids)
)
)
]
backfilled = []
for i, s in enumerate(index):
@@ -3032,55 +3050,6 @@ def get_state_db_session_messages(sid, *, stitch_continuations: bool = False, pr
return msgs
def get_state_db_session_summary(sid) -> dict:
"""Return cheap message count/max timestamp for one state.db session.
This is intentionally narrower than ``get_state_db_session_messages`` for
metadata-only WebUI polling: callers only need a staleness signal, not a
fully materialized transcript with tool/reasoning metadata.
"""
import os
try:
import sqlite3
except ImportError:
return {}
db_path = _active_state_db_path()
if not sid or not db_path.exists():
return {}
try:
with closing(sqlite3.connect(str(db_path))) as conn:
conn.row_factory = sqlite3.Row
cur = conn.cursor()
cur.execute("PRAGMA table_info(messages)")
available = {str(row['name']) for row in cur.fetchall()}
if not {'session_id', 'timestamp'}.issubset(available):
return {}
cur.execute(
"""
SELECT COUNT(*) AS message_count, MAX(timestamp) AS last_message_at
FROM messages
WHERE session_id = ?
""",
(str(sid),),
)
row = cur.fetchone()
if not row:
return {}
count = int(row['message_count'] or 0)
last_message_at = row['last_message_at']
result = {'message_count': count}
if last_message_at not in (None, ''):
try:
result['last_message_at'] = float(last_message_at)
except (TypeError, ValueError):
pass
return result
except Exception:
return {}
def _normalized_message_timestamp_for_key(value):
if value is None or value == "":
return ""
+46 -25
View File
@@ -2036,6 +2036,37 @@ def _merged_session_messages_for_display(session, cli_messages=None) -> list:
return sidecar_messages
def _message_summary(messages) -> dict:
messages = list(messages or [])
last_message_at = 0.0
for msg in messages:
if not isinstance(msg, dict):
continue
try:
last_message_at = max(last_message_at, float(msg.get("timestamp") or 0))
except (TypeError, ValueError):
pass
return {"message_count": len(messages), "last_message_at": last_message_at}
def _metadata_only_message_summary(sid: str, profile: str | None = None) -> dict:
"""Return the reconciled message summary used by metadata-only session loads.
Threads ``profile=`` through to ``get_state_db_session_messages`` so
background-thread reads land on the correct profile's state.db (per the
cookie-bound profile selector fixes the same TLS-vs-thread race the
#2762 fix addressed for write paths).
"""
sidecar_session = Session.load(sid)
sidecar_messages = []
if sidecar_session:
sidecar_messages = getattr(sidecar_session, "messages", []) or []
state_db_messages = get_state_db_session_messages(sid, profile=profile)
return _message_summary(
merge_session_messages_append_only(sidecar_messages, state_db_messages)
)
def _session_requires_cli_metadata_lookup(session) -> bool:
"""Return True when a sidecar/session row still needs CLI metadata.
@@ -3792,7 +3823,7 @@ def handle_get(handler, parsed) -> bool:
is_messaging_session = _is_messaging_session_record(s) or _is_messaging_session_record(cli_meta)
cli_messages = []
state_db_messages = []
sidecar_metadata_messages = None
metadata_summary = None
_session_profile = getattr(s, 'profile', None) or None
if is_messaging_session:
cli_messages = get_cli_session_messages(sid)
@@ -3800,17 +3831,11 @@ def handle_get(handler, parsed) -> bool:
state_db_messages = get_state_db_session_messages(sid, profile=_session_profile)
elif not is_messaging_session:
# Metadata-only callers still need the same append-only
# reconciliation contract as full loads. A raw state.db summary
# can count stale rows that the merge intentionally filters out,
# which makes sidebar polling think the transcript is always
# newer than the loaded conversation.
state_db_messages = get_state_db_session_messages(sid, profile=_session_profile)
sidecar_metadata_session = Session.load(sid)
sidecar_metadata_messages = (
getattr(sidecar_metadata_session, "messages", []) or []
if sidecar_metadata_session
else []
)
# reconciliation contract as full loads so stale/replayed
# state.db rows do not make sidebar polling think the
# transcript is always newer. Helper threads profile= to
# honor #2827's TLS-vs-thread fix.
metadata_summary = _metadata_only_message_summary(sid, profile=_session_profile)
_t2 = _time.monotonic()
effective_model = (
_resolve_effective_session_model_for_display(s)
@@ -3840,12 +3865,16 @@ def handle_get(handler, parsed) -> bool:
sidecar_messages = getattr(s, "messages", []) or []
_all_msgs = merge_session_messages_append_only(cli_messages, sidecar_messages)
else:
_metadata_sidecar = sidecar_metadata_messages
if _metadata_sidecar is None:
_metadata_sidecar = getattr(s, "messages", []) or []
_all_msgs = merge_session_messages_append_only(_metadata_sidecar, state_db_messages)
if metadata_summary is None:
metadata_summary = _message_summary(getattr(s, "messages", []) or [])
_summary_message_count = metadata_summary["message_count"]
_summary_last_message_at = metadata_summary["last_message_at"]
_all_msgs = []
if not load_messages:
_summary_message_count = len(_all_msgs)
if metadata_summary is None:
metadata_summary = _message_summary(_all_msgs)
_summary_message_count = metadata_summary["message_count"]
_summary_last_message_at = metadata_summary["last_message_at"]
if _summary_message_count == 0:
# Legacy session with no loaded sidecar and no state.db summary —
# fall back to the persisted metadata count from session JSON.
@@ -3858,14 +3887,6 @@ def handle_get(handler, parsed) -> bool:
_summary_message_count = max(0, int(metadata_count))
except (TypeError, ValueError):
pass
try:
_summary_last_message_at = max(
float((m or {}).get("timestamp") or 0)
for m in _all_msgs
if isinstance(m, dict)
) if _all_msgs else 0
except (TypeError, ValueError):
_summary_last_message_at = 0
else:
_summary_message_count = None
_summary_last_message_at = None
+7 -1
View File
@@ -1624,7 +1624,10 @@ function applyBotName(){
else if(typeof syncModelChip==='function') syncModelChip();
}
if(S.session) syncTopbar();
}).catch(()=>{});
}).catch(e=>{
window._modelDropdownReady=null;
throw e;
});
const _startBootModelDropdown=()=>{
const ready=window._modelDropdownReady;
if(ready&&typeof ready.then==='function') return ready;
@@ -1634,6 +1637,9 @@ function applyBotName(){
};
window._modelDropdownReady=null;
window._ensureModelDropdownReady=_startBootModelDropdown;
setTimeout(()=>{
try{Promise.resolve(_startBootModelDropdown()).catch(()=>{});}catch(_){}
},0);
// Start independent boot fetches without holding the conversation list behind
// them. The sidebar can render from /api/sessions while workspace/onboarding
// metadata settles in parallel.
+7 -4
View File
@@ -6736,10 +6736,13 @@ function _refreshModelDropdownsAfterProviderChange(){
if(typeof window._invalidateSlashModelCache==='function'){
window._invalidateSlashModelCache();
}
if(typeof populateModelDropdown==='function'){
// Fire-and-forget: don't block the providers panel refresh on a
// dropdown rebuild. The composer/Settings dropdowns will catch up
// on the very next paint frame.
// Fire-and-forget: don't block the providers panel refresh on a
// dropdown rebuild. The composer/Settings dropdowns will catch up
// on the very next paint frame.
if(typeof window._ensureModelDropdownReady==='function'){
window._modelDropdownReady=null;
Promise.resolve(window._ensureModelDropdownReady()).catch(()=>{});
}else if(typeof populateModelDropdown==='function'){
Promise.resolve(populateModelDropdown()).catch(()=>{});
}
}catch(_e){
+4 -1
View File
@@ -789,7 +789,10 @@ async function loadSession(sid){
syncTopbar();renderMessages();
if(typeof resumeManualCompressionForSession==='function') resumeManualCompressionForSession(sid);
const _dirP=loadDir('.');
await _dirP;
// Workspace refresh is guarded by session id inside loadDir(); do not
// block session-load completion, draft restore, or model resolution on
// file-tree IO for users focused on the chat.
if(_dirP&&typeof _dirP.catch==='function') _dirP.catch(()=>{});
}
}
+22 -7
View File
@@ -5992,7 +5992,7 @@ function renderMessages(options){
const msgCount=S.messages.length;
if(sid!==_messageRenderWindowSid) _resetMessageRenderWindow(sid);
const renderWindowSize=_currentMessageRenderWindowSize();
const renderSignature=_messageRenderCacheSignature();
let cachedRenderSignature=null;
const hasTransientTranscriptUi=!!(
(window._compressionUi&&(!window._compressionUi.sessionId||window._compressionUi.sessionId===sid)) ||
(window._handoffUi&&(!window._handoffUi.sessionId||window._handoffUi.sessionId===sid))
@@ -6007,6 +6007,8 @@ function renderMessages(options){
// cross-channel handoff summaries; otherwise the cached transcript returns
// before those cards can be inserted.
if(sid&&sid!==_sessionHtmlCacheSid&&!INFLIGHT[sid]&&!hasTransientTranscriptUi){
const renderSignature=_messageRenderCacheSignature();
cachedRenderSignature=renderSignature;
const cached=_sessionHtmlCache.get(sid);
if(cached&&cached.msgCount===msgCount&&cached.renderWindowSize===renderWindowSize&&cached.signature===renderSignature){
inner.innerHTML=cached.html;
@@ -6128,6 +6130,13 @@ function renderMessages(options){
const assistantSegments=new Map();
const assistantThinking=new Map();
const userRows=new Map();
const toolCallAssistantIdxs=new Set();
if(Array.isArray(S.toolCalls)){
for(const tc of S.toolCalls){
if(!tc) continue;
toolCallAssistantIdxs.add(tc.assistant_msg_idx!==undefined?tc.assistant_msg_idx:-1);
}
}
// Windowed render loop replaces the legacy full loop:
// for(let vi=0;vi<visWithIdx.length;vi++)
for(let vi=0;vi<renderVisWithIdx.length;vi++){
@@ -6374,11 +6383,12 @@ function renderMessages(options){
// a display list from per-message tool_calls (OpenAI format) stored in each
// assistant message. This covers the reload case described in issue #140.
if(!S.busy && (!S.toolCalls||!S.toolCalls.length)){
// Pass 1: index tool outputs by tool_call_id / tool_use_id so the
// Index tool outputs by tool_call_id / tool_use_id so the
// fallback-built cards carry their result snippet (not just the command).
// Without this step CLI-origin sessions reload with empty tool cards.
const resultsByTid={};
S.messages.forEach(m=>{
const fallbackToolSources=[];
S.messages.forEach((m,rawIdx)=>{
if(!m) return;
// OpenAI / Hermes CLI format: role=tool with tool_call_id
if(m.role==='tool'){
@@ -6398,10 +6408,14 @@ function renderMessages(options){
resultsByTid[tid]=_cliToolResultSnippet(raw);
});
}
if(m.role==='assistant'){
const hasTopLevelToolCalls=Array.isArray(m.tool_calls)&&m.tool_calls.length>0;
const hasContentToolUse=Array.isArray(m.content)&&m.content.some(p=>p&&typeof p==='object'&&p.type==='tool_use');
if(hasTopLevelToolCalls||hasContentToolUse) fallbackToolSources.push({m,rawIdx});
}
});
const derived=[];
S.messages.forEach((m,rawIdx)=>{
if(m.role!=='assistant') return;
fallbackToolSources.forEach(({m,rawIdx})=>{
// OpenAI format: top-level tool_calls field on the assistant message
(m.tool_calls||[]).forEach(tc=>{
if(!tc||typeof tc!=='object') return;
@@ -6548,7 +6562,7 @@ function renderMessages(options){
const hasTurnUsage=!!msg._turnUsage;
const compactActivityForMessage=isSimplifiedToolCalling()&&(
assistantThinking.has(mi)||
(S.toolCalls||[]).some(tc=>tc&&(tc.assistant_msg_idx!==undefined?tc.assistant_msg_idx:-1)===mi)
toolCallAssistantIdxs.has(mi)
);
const durationText=compactActivityForMessage?'':_formatTurnDuration(msg._turnDuration);
if(!hasTurnUsage&&!durationText&&!gatewayText&&!failoverText&&!modelWarningText) continue;
@@ -6615,10 +6629,11 @@ function renderMessages(options){
if(typeof _applyMediaPlaybackPreferences==='function') _applyMediaPlaybackPreferences(inner);
// Populate session cache so switching back here skips a full rebuild.
_sessionHtmlCacheSid=sid;
if(sid&&!hasTransientTranscriptUi){
if(sid&&!INFLIGHT[sid]&&!hasTransientTranscriptUi){
const _html=inner.innerHTML;
// Only cache sessions with <300KB rendered HTML; evict oldest beyond 8 sessions.
if(_html.length<300_000){
const renderSignature=cachedRenderSignature===null?_messageRenderCacheSignature():cachedRenderSignature;
_sessionHtmlCache.set(sid,{html:_html,msgCount,renderWindowSize,signature:renderSignature});
if(_sessionHtmlCache.size>8){_sessionHtmlCache.delete(_sessionHtmlCache.keys().next().value);}
}
+12 -4
View File
@@ -105,13 +105,15 @@ function _restoreExpandedDirs(){
async function loadDir(path){
if(!S.session)return;
const sessionId=S.session.session_id;
try{
if(!path||path==='.'){
S._dirCache={};
_restoreExpandedDirs(); // restore per-workspace expanded state on root load
}
S.currentDir=path||'.';
const data=await api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(path)}`);
const data=await api(`/api/list?session_id=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(path)}`);
if(!S.session||S.session.session_id!==sessionId)return;
S.entries=data.entries||[];renderBreadcrumb();renderFileTree();
// Pre-fetch contents of restored expanded dirs so they render without a second click
// (parallelized — avoids serial waterfall when multiple dirs are expanded)
@@ -120,10 +122,11 @@ async function loadDir(path){
const pending=[...expanded].filter(dirPath=>!S._dirCache[dirPath]);
if(pending.length){
const results=await Promise.all(pending.map(dirPath=>
api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=${encodeURIComponent(dirPath)}`)
api(`/api/list?session_id=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(dirPath)}`)
.then(dc=>({dirPath,entries:dc.entries||[]}))
.catch(()=>({dirPath,entries:[]}))
));
if(!S.session||S.session.session_id!==sessionId)return;
for(const {dirPath,entries} of results) S._dirCache[dirPath]=entries;
}
if(expanded.size>0)renderFileTree();
@@ -143,8 +146,10 @@ async function loadDir(path){
async function _refreshGitBadge(){
const badge=$('gitBadge');
if(!badge||!S.session)return;
const sessionId=S.session.session_id;
try{
const data=await api(`/api/git-info?session_id=${encodeURIComponent(S.session.session_id)}`);
const data=await api(`/api/git-info?session_id=${encodeURIComponent(sessionId)}`);
if(!S.session||S.session.session_id!==sessionId)return;
if(data.git&&data.git.is_git){
const g=data.git;
let text=g.branch||'git';
@@ -158,7 +163,10 @@ async function _refreshGitBadge(){
badge.style.display='none';
badge.textContent='';
}
}catch(e){badge.style.display='none';}
}catch(e){
if(!S.session||S.session.session_id!==sessionId)return;
badge.style.display='none';
}
}
function navigateUp(){
@@ -164,6 +164,15 @@ class TestProviderRemoveInvalidatesDropdowns:
"response (covers the dropdown + badge surfaces from #1539)."
)
def test_dropdown_flush_reuses_shared_model_ready_promise(self):
src = _read_static("panels.js")
body = _extract_function_body(src, "function _refreshModelDropdownsAfterProviderChange(")
ensure_pos = body.index("typeof window._ensureModelDropdownReady")
reset_pos = body.index("window._modelDropdownReady=null", ensure_pos)
call_pos = body.index("window._ensureModelDropdownReady()", reset_pos)
assert ensure_pos < reset_pos < call_pos
def test_dropdown_flush_is_resilient_to_missing_modules(self):
"""If commands.js or ui.js failed to load, the providers panel must
still update the dropdown flush is best-effort (#1539)."""
@@ -50,6 +50,13 @@ def test_load_dir_keeps_workspace_panel_open_when_clearing_preview():
)
def test_load_dir_ignores_stale_session_results():
block = _function_block(WORKSPACE_JS, "loadDir")
assert "const sessionId=S.session.session_id" in block
assert "encodeURIComponent(sessionId)" in block
assert "if(!S.session||S.session.session_id!==sessionId)return;" in block
def test_file_preview_breadcrumb_uses_directory_navigation_for_root():
block = _function_block(WORKSPACE_JS, "renderFileBreadcrumb")
assert "loadDir('.')" in block, "The preview root breadcrumb should navigate to the workspace root."
+4 -2
View File
@@ -88,9 +88,11 @@ class TestLoadSessionIdleOverlap:
"The idle path should rely on renderMessages()'s consolidated "
"post-render pass instead of running a second highlight pass."
)
assert "await" in block and "_dirP" in block, (
"loadDir() result should still be stored and awaited."
assert "_dirP" in block and "await _dirP" not in block, (
"loadDir() should refresh the workspace without blocking "
"session-load completion."
)
assert "_dirP.catch" in block
break
assert found, (
+10 -10
View File
@@ -123,8 +123,8 @@ def test_all_sessions_backfills_last_message_at_for_legacy_index_rows():
assert persisted[0].get("last_message_at") == 100.0
def test_all_sessions_prune_reuses_in_memory_id_snapshot(monkeypatch):
"""Index pruning should not reacquire the session lock for every row."""
def test_all_sessions_prune_batches_persisted_id_snapshot(monkeypatch):
"""Index pruning should not probe each backing file through the helper."""
index_file = models.SESSION_INDEX_FILE
entries = [
{
@@ -152,22 +152,22 @@ def test_all_sessions_prune_reuses_in_memory_id_snapshot(monkeypatch):
"archived": False,
},
]
for entry in entries:
(models.SESSION_DIR / f"{entry['session_id']}.json").write_text(
"{}",
encoding="utf-8",
)
_write_index_file(index_file, entries)
seen = []
def _assert_not_called(session_id, in_memory_ids=None):
raise AssertionError("all_sessions should batch persisted ids before pruning")
def _assert_snapshot_used(session_id, in_memory_ids=None):
assert in_memory_ids is not None, "all_sessions should snapshot SESSIONS once before pruning"
seen.append(session_id)
return True
monkeypatch.setattr(models, "_index_entry_exists", _assert_snapshot_used)
monkeypatch.setattr(models, "_index_entry_exists", _assert_not_called)
monkeypatch.setattr(models, "_enrich_sidebar_lineage_metadata", lambda _sessions: None)
rows = models.all_sessions()
assert [row["session_id"] for row in rows] == ["sess_a", "sess_b"]
assert seen == ["sess_a", "sess_b"]
# ── 6. test_incremental_patch_correctness ─────────────────────────────────
+36
View File
@@ -68,6 +68,42 @@ def test_boot_does_not_block_session_restore_on_model_catalog():
assert "await populateModelDropdown()" not in src
def test_boot_primes_model_catalog_without_awaiting_it():
"""The boot-time prime must NOT await the model-catalog hydration before
rendering the session list. A later awaited hydration inside the saved-
session restore path at ``if(S.session) await _startBootModelDropdown();``
is intentional that one re-applies the saved session's model after the
live catalog hydrates so the chip never shows a stale static default
(see comment in static/boot.js next to the saved-session restore).
"""
src = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
ensure_pos = src.index("window._ensureModelDropdownReady=_startBootModelDropdown;")
prime_pos = src.index("Promise.resolve(_startBootModelDropdown()).catch(()=>{});", ensure_pos)
session_restore_pos = src.index("await renderSessionList();", prime_pos)
assert ensure_pos < prime_pos < session_restore_pos
# No await on the boot-prime path itself: between ensure_pos and the first
# session_restore await, the dropdown is fired-and-forgotten.
boot_prelude = src[ensure_pos:session_restore_pos]
assert "await _startBootModelDropdown()" not in boot_prelude, (
"Boot prelude must not await _startBootModelDropdown — the prime is "
"fire-and-forget so the sidebar can render before /api/models returns."
)
assert "await populateModelDropdown()" not in boot_prelude
def test_failed_boot_model_catalog_prime_is_retryable():
src = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
start = src.index("const _hydrateBootModelDropdown=()=>populateModelDropdown().then")
end = src.index("const _startBootModelDropdown=()=>", start)
block = src[start:end]
assert "window._modelDropdownReady=null;" in block
assert "throw e;" in block
def test_boot_primes_visible_default_model_without_catalog_fetch():
src = (ROOT / "static" / "boot.js").read_text(encoding="utf-8")
default_block_start = src.index("if(s.default_model){")
@@ -512,6 +512,40 @@ def test_api_session_reload_drops_stale_cached_user_tail_after_saved_assistant(m
assert handler.response_json["session"]["message_count"] == 2
def test_metadata_fast_path_matches_reconciliation_for_restamped_replays(monkeypatch, tmp_path):
"""#2716 invariant: metadata-only /api/session uses merge_session_messages_append_only
(not a raw state.db COUNT) so restamped replay rows don't make sidebar polling think
the transcript is always newer than the loaded conversation."""
import api.routes as routes
sid = "webui_reconcile_metadata_replay"
_install_test_session(
monkeypatch,
tmp_path,
sid,
[
{"role": "user", "content": "old user", "timestamp": 1000.0},
{"role": "assistant", "content": "old assistant", "timestamp": 1001.0},
],
)
_make_state_db(
tmp_path / "state.db",
sid,
[
{"role": "user", "content": "old user", "timestamp": 1002.0},
],
)
handler = _GetHandler(f"/api/session?session_id={sid}&messages=0&resolve_model=0")
routes.handle_get(handler, urlparse(handler.path))
assert handler.status == 200
session = handler.response_json["session"]
assert session["messages"] == []
assert session["message_count"] == 2
assert session["last_message_at"] == 1001.0
def test_state_db_reconciliation_preserves_tool_metadata(monkeypatch, tmp_path):
import api.routes as routes