docs(changelog): stamp #6892 + add direct sync_session_title coverage tests
Codex round-1 SHOULD-FIX: the PR's only test change mocked the new write to a no-op. Add real-SessionDB tests proving generated-title persistence, manual-rename no-overwrite, and cli/gateway source preservation on ensure_session. Additive test-only; no behavior change to the contributor's fix. Co-authored-by: liuguangyong93 <liuguangyong93@users.noreply.github.com>
This commit is contained in:
1 parent
32b7f0a5e0
commit
d73cea803e
2 files changed
+96
No files matched your search
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **`hermes sessions list` no longer shows blank titles for WebUI sessions.** Background title generation wrote the auto-title to the WebUI session sidecar but never to hermes-agent's `state.db`, so the CLI/TUI session list showed those WebUI sessions untitled. The title is now bridged into `state.db` after it's generated (and on adaptive refresh) via `set_auto_title_if_empty`, which respects title provenance — a name you set manually via CLI/Gateway/TUI (`user` source) is never overwritten by the auto-title, an already-auto-titled row stays put, and an existing session's `source` is preserved (the sync creates the row with INSERT-OR-IGNORE semantics). Thanks @liuguangyong93. (#6892)
|
||||||
- **No more phantom "Compressing context" barrier after switching sessions.** The compression UI state is per-session, but `loadSession()` did not clear it on a switch, so a compression card from a prior session could leak across the load and appear as a phantom "Compressing context" barrier on a fresh session that never triggered compression. Switching sessions now clears the compression UI (state, elapsed timer, composer session-lock, and barrier element) alongside the other per-session resets, before the transcript loads. A compression genuinely running on the session you switch *to* is unaffected — it re-surfaces from that session's own live event stream (and manual compression from its server job status). Thanks @happy5318. (#6938)
|
- **No more phantom "Compressing context" barrier after switching sessions.** The compression UI state is per-session, but `loadSession()` did not clear it on a switch, so a compression card from a prior session could leak across the load and appear as a phantom "Compressing context" barrier on a fresh session that never triggered compression. Switching sessions now clears the compression UI (state, elapsed timer, composer session-lock, and barrier element) alongside the other per-session resets, before the transcript loads. A compression genuinely running on the session you switch *to* is unaffected — it re-surfaces from that session's own live event stream (and manual compression from its server job status). Thanks @happy5318. (#6938)
|
||||||
|
|
||||||
- **Layout test helper no longer flags scroll-reachable fields as off-viewport.** The shared `assert_layout_sane` degenerate check reported a false "interactive element is off-viewport" for an input that sits below the fold at rest but is reachable by scrolling an `overflow-y:auto` ancestor (e.g. a tall dialog on a short landscape viewport). It now exempts elements whose off-viewport edge is absorbed by a scrollable ancestor, while still flagging genuinely-unreachable elements (no scroll escape). The Kanban board settings modal layout test also drops its synthetic 480×320 short-landscape case (a viewport no real device uses, whose sub-fold geometry was environment-marginal and CI-flaky); real desktop/tablet/narrow viewports still assert the layout. (internal test infra)
|
- **Layout test helper no longer flags scroll-reachable fields as off-viewport.** The shared `assert_layout_sane` degenerate check reported a false "interactive element is off-viewport" for an input that sits below the fold at rest but is reachable by scrolling an `overflow-y:auto` ancestor (e.g. a tall dialog on a short landscape viewport). It now exempts elements whose off-viewport edge is absorbed by a scrollable ancestor, while still flagging genuinely-unreachable elements (no scroll escape). The Kanban board settings modal layout test also drops its synthetic 480×320 short-landscape case (a viewport no real device uses, whose sub-fold geometry was environment-marginal and CI-flaky); real desktop/tablet/narrow viewports still assert the layout. (internal test infra)
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
"""Direct coverage for api.state_sync.sync_session_title (#6892).
|
||||||
|
|
||||||
|
Codex round-1 SHOULD-FIX: the PR's only test change mocks sync_session_title to a
|
||||||
|
no-op, so the new state.db-writing path had no direct test proving it actually
|
||||||
|
persists a generated title AND preserves a manual rename. These tests exercise
|
||||||
|
the real function against a temporary hermes-agent SessionDB.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def _make_db(tmp_path):
|
||||||
|
hermes_state = pytest.importorskip("hermes_state")
|
||||||
|
SessionDB = hermes_state.SessionDB
|
||||||
|
return SessionDB(db_path=tmp_path / "state.db")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_agent_modules
|
||||||
|
def test_sync_session_title_persists_generated_title(tmp_path, monkeypatch):
|
||||||
|
"""A generated title is written to state.db so `hermes sessions list` isn't blank."""
|
||||||
|
from api import state_sync
|
||||||
|
|
||||||
|
db = _make_db(tmp_path)
|
||||||
|
try:
|
||||||
|
# Route sync_session_title at the real temp DB (a fresh handle each call,
|
||||||
|
# mirroring production, but pointed at our tmp state.db).
|
||||||
|
monkeypatch.setattr(
|
||||||
|
state_sync,
|
||||||
|
"_get_state_db",
|
||||||
|
lambda profile=None: _make_db(tmp_path),
|
||||||
|
)
|
||||||
|
|
||||||
|
state_sync.sync_session_title("sess-gen", "Generated Title", profile="default")
|
||||||
|
|
||||||
|
assert db.get_session_title("sess-gen") == "Generated Title"
|
||||||
|
# Provenance is the automatic LLM source, not a user rename.
|
||||||
|
assert db.get_session_title_source("sess-gen") == db.TITLE_SOURCE_LLM
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_agent_modules
|
||||||
|
def test_sync_session_title_does_not_overwrite_manual_rename(tmp_path, monkeypatch):
|
||||||
|
"""A user rename (source=user) is never clobbered by a later title sync."""
|
||||||
|
from api import state_sync
|
||||||
|
|
||||||
|
db = _make_db(tmp_path)
|
||||||
|
try:
|
||||||
|
# Seed a manual rename first — this records `user` provenance.
|
||||||
|
db.ensure_session(session_id="sess-manual", source="cli")
|
||||||
|
db.set_session_title("sess-manual", "My Manual Name")
|
||||||
|
assert db.get_session_title_source("sess-manual") == db.TITLE_SOURCE_USER
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
state_sync,
|
||||||
|
"_get_state_db",
|
||||||
|
lambda profile=None: _make_db(tmp_path),
|
||||||
|
)
|
||||||
|
|
||||||
|
# A background generation now tries to sync an auto title — must be a no-op.
|
||||||
|
state_sync.sync_session_title("sess-manual", "Auto Generated", profile="default")
|
||||||
|
|
||||||
|
assert db.get_session_title("sess-manual") == "My Manual Name"
|
||||||
|
assert db.get_session_title_source("sess-manual") == db.TITLE_SOURCE_USER
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.requires_agent_modules
|
||||||
|
def test_sync_session_title_preserves_existing_source_on_ensure(tmp_path, monkeypatch):
|
||||||
|
"""ensure_session(source='webui') must not clobber an existing cli/gateway row's source."""
|
||||||
|
from api import state_sync
|
||||||
|
|
||||||
|
db = _make_db(tmp_path)
|
||||||
|
try:
|
||||||
|
db.ensure_session(session_id="sess-cli", source="cli")
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
state_sync,
|
||||||
|
"_get_state_db",
|
||||||
|
lambda profile=None: _make_db(tmp_path),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Sync (which calls ensure_session(source='webui')) on a pre-existing cli row.
|
||||||
|
state_sync.sync_session_title("sess-cli", "Some Title", profile="default")
|
||||||
|
|
||||||
|
# INSERT OR IGNORE semantics: the original source is preserved, the title lands.
|
||||||
|
assert db.get_session_title("sess-cli") == "Some Title"
|
||||||
|
with db._lock:
|
||||||
|
row = db._conn.execute(
|
||||||
|
"SELECT source FROM sessions WHERE id = ?", ("sess-cli",)
|
||||||
|
).fetchone()
|
||||||
|
assert row["source"] == "cli"
|
||||||
|
finally:
|
||||||
|
db.close()
|
||||||
Reference in new issue
Block a user