Merge remote-tracking branch 'origin/master' into fix/5542-debrittle-rfc-anchors

This commit is contained in:
nesquena-hermes committed 2026-07-04 19:44:59 +00:00
commit 4c1cdcba3e
6 files changed
+73 -7

No files matched your search

+1
View File
@@ -14,6 +14,7 @@
### Fixed
- **Transparent Stream now shows a running tool as running, not already-done.** The live anchor-scene renderer hardcoded the tool-status as "settled" even while the tool was still executing, so a tool call flashed straight to its finished state in Transparent Stream instead of showing its in-progress status. It now uses the row's actual settled state, so a running tool reads as running until it genuinely completes. Thanks @rodboev. (#5547, #5523)
- **The composer no longer repopulates the tail of a message you already sent.** On mobile/slow connections, a just-sent message could momentarily reappear in the composer because a stale server-side `composer_draft` restored before the empty-draft write caught up. A short per-session suppression window now blocks that stale restore right after send — but scoped to the specific sent payload, so a genuinely new draft you (or another tab) start for the same conversation still restores immediately instead of being swallowed for the window. Drafts are also cleared on the busy send paths (queue / interrupt / steer). Thanks @ruizanthony. (#5471)
- **Windows: after an upgrade, browsers no longer keep serving stale JS/CSS when git isn't on the server's PATH.** When the WebUI server was launched on Windows from an environment without git on `PATH` (a venv, a service wrapper, a non-interactive launcher), version detection silently degraded to `unknown`, which froze the `?v=<stamp>` static-asset cache key — so browsers kept serving cached `ui.js` / `messages.js` / CSS even after the server restarted with fixed code, and the only workaround was a manual hard-refresh on every device. Version detection now resolves `git.exe` from the Git-for-Windows registry key (and common install paths) when it isn't on `PATH`, so the cache stamp updates and clients pick up frontend fixes automatically. Non-Windows behavior is unchanged. Thanks @allenliang2022. (#5522)
- **Transparent Stream is smoother during streaming — less row churn and no thinking-block scrollbar flicker.** Building on the identity-preserving live-row reconcile, the renderer now skips redundant work while a response streams: a preserved row's markup isn't rewritten when the incoming HTML is unchanged, a row isn't reinserted when it's already in the correct position, and long thinking blocks no longer flicker an internal scrollbar as their scroll container appears. Purely a reduction of no-op DOM work — what renders is unchanged, and matching rows still preserve their DOM node, copy button, tool-call data, and expanded/collapsed state. Thanks @Stacey2911. (#5456, #5367)
+1 -1
View File
@@ -10721,7 +10721,7 @@ function _anchorSceneTransparentNodeForRow(row, opts){
node=_decorateTransparentEventRow(buildToolCard(toolCall),{
type:'tool',
name:toolCall&&toolCall.name,
status:_transparentToolStatus(toolCall,true),
status:_transparentToolStatus(toolCall,settled),
toolCall,
...meta,
});
@@ -471,17 +471,27 @@ class TestNousLiveFetchEmpty:
"the curated static fallback so the picker isn't empty in "
"test envs that lack the agent package."
)
# The original curated 4 entries must all be present.
nous_ids = {m["id"] for m in nous_groups[0]["models"]}
# Every curated @nous: entry must survive the static fallback — but
# when the curated list exceeds _NOUS_FEATURED_THRESHOLD (>25) the
# picker dropdown intentionally shows only the featured subset and
# stashes the rest under "extra_models" (for /model autocomplete).
# The invariant is "no curated entry is DROPPED", so check the full
# returned set (visible models + extra_models), not just the visible
# dropdown slice (which drifts to failure as the curated list grows
# past the threshold — the root cause of the pre-fix flake).
_nous_group = nous_groups[0]
nous_ids = {m["id"] for m in _nous_group.get("models", [])}
nous_ids |= {m["id"] for m in _nous_group.get("extra_models", [])}
_curated_nous_ids = {
m["id"] for m in config._PROVIDER_MODELS.get("nous", [])
if m["id"].startswith("@nous:")
}
assert _curated_nous_ids.issubset(nous_ids), (
f"Static fallback is missing curated @nous: entries. "
f"Expected subset {_curated_nous_ids}, got {nous_ids}"
f"Static fallback dropped curated @nous: entries. "
f"Missing {_curated_nous_ids - nous_ids} "
f"(visible+extra_models total {len(nous_ids)})"
)
assert len(nous_groups[0]["models"]) >= 4, (
assert len(nous_ids) >= 4, (
"Static fallback should expose at least the curated 4-entry "
"list from _PROVIDER_MODELS['nous']."
)
@@ -631,6 +631,23 @@ def test_transparent_interrupted_status_on_settled_tools():
permanent Running shimmer). (Trifecta O-Edge.)"""
assert "function _transparentToolStatus(tc, settled)" in UI_JS
assert "settled?'Interrupted':'Running'" in UI_JS
start = UI_JS.index("function _anchorSceneTransparentNodeForRow(row, opts){")
depth = 0
end = None
for idx in range(start, len(UI_JS)):
char = UI_JS[idx]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
end = idx + 1
break
assert end is not None, "_anchorSceneTransparentNodeForRow body did not close"
body = UI_JS[start:end]
assert "else if(row.role==='tool')" in body
assert "_transparentToolStatus(toolCall,settled)" in body
assert "_transparentToolStatus(toolCall,true)" not in body
assert "_transparentToolStatus(event.toolCall,true)" in UI_JS
+38
View File
@@ -1,7 +1,45 @@
"""Tests for issue #4536 — main-model service_tier persistence and guarded forwarding."""
import pytest
class TestIssue4536ServiceTier:
@pytest.fixture(autouse=True)
def _isolate_config_globals(self):
"""Make these tests hermetic against leaked module-global config caches.
Two shared caches in api.config can serve a PRIOR test's config into this
one, causing a full-suite-only flake (passes in isolation):
1. `_yaml_file_cache` — memoized YAML parse keyed on
(str(config_path), st_mtime_ns, st_size). pytest reuses tmp_path base
dirs across the session, so a prior test's config.yaml can share both
the path string AND the (mtime_ns, size) of this test's tiny file
(same coarse mtime tick) → set_hermes_default_model() reads the STALE
cached dict (e.g. an lmstudio config) and writes THAT back, so the
on-disk assertion sees the wrong provider. This is the actual root
cause of the #4536 service_tier flake.
2. `cfg` / `_cfg_fingerprint` — if a prior test rebound `config.cfg`,
get_auxiliary_models() reads the stale dict after reload_config().
Clear both before AND after each test so neither an inherited leak nor
our own run can poison a neighbor.
"""
from api import config
def _reset():
with config._cfg_lock:
config._cfg_cache.clear()
config.cfg = config._cfg_cache
config._cfg_fingerprint = None
config._cfg_mtime = 0.0
with config._yaml_file_cache_lock:
config._yaml_file_cache.clear()
_reset()
yield
_reset()
def test_main_service_tier_roundtrip_via_auxiliary_endpoint(self, monkeypatch, tmp_path):
"""service_tier set on main model should persist in config and return via /api/model/auxiliary payload."""
from api import config
@@ -799,7 +799,7 @@ def test_transparent_stream_renders_persisted_anchor_scene_after_reload():
# tool + thinking rows are rendered as transparent event rows
assert "_decorateTransparentEventRow(_thinkingActivityNode" in row
assert "_decorateTransparentEventRow(buildToolCard(toolCall)" in row
assert "_transparentToolStatus(toolCall,true)" in row
assert "_transparentToolStatus(toolCall,settled)" in row
assert 'data-anchor-settled-scene-row' in row
assert "if(anchorOwnedAssistantRawIdxs.has(aIdx)) continue;" in render