fix(state): skip out-of-window timestamps when computing last_active
This commit is contained in:
@@ -10,6 +10,7 @@ import sys
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from hermes_cli.timefmt import EPOCH_MAX, EPOCH_MIN
|
||||
from agent.skill_commands import SKILL_EXCERPT_JOINT, SKILL_SCAFFOLD_SQL_LIKE, describe_skill_invocation
|
||||
from agent.context_compressor import (LEGACY_SUMMARY_PREFIX, SUMMARY_PREFIX, _MERGED_PRIOR_CONTEXT_HEADER,
|
||||
_MERGED_SUMMARY_DELIMITER, _SUMMARY_END_MARKER)
|
||||
@@ -218,10 +219,14 @@ def _ephemeral_child_sql(alias: str = "s") -> str:
|
||||
|
||||
def _sql_freshest_of(activity: str, session_id_expr: str, started: str) -> str:
|
||||
"""Freshest of *activity* and the latest message timestamp for *session_id_expr*, else *started*.
|
||||
Heartbeats are rate-limited (~60s) so ``last_activity_at`` can lag a newer message; never use it alone."""
|
||||
msg_max = f"(SELECT MAX(_act_m.timestamp) FROM messages _act_m WHERE _act_m.session_id = {session_id_expr})"
|
||||
return (f"COALESCE((SELECT MAX(_act_v.v) FROM (SELECT {activity} AS v UNION ALL SELECT {msg_max}) _act_v), "
|
||||
f"{started})")
|
||||
Heartbeats are rate-limited (~60s) so ``last_activity_at`` can lag a newer message; never use it alone.
|
||||
Cells outside the ``coerce_epoch`` window (garbage doubles salvaged from a damaged page, TEXT) are
|
||||
skipped, or one bad message row pins the session's recency to ``5e+246`` (#91536)."""
|
||||
in_window = f"BETWEEN {EPOCH_MIN!r} AND {EPOCH_MAX!r}"
|
||||
msg_max = (f"(SELECT MAX(_act_m.timestamp) FROM messages _act_m WHERE _act_m.session_id = {session_id_expr}"
|
||||
f" AND _act_m.timestamp {in_window})")
|
||||
return (f"COALESCE((SELECT MAX(_act_v.v) FROM (SELECT {activity} AS v UNION ALL SELECT {msg_max}) _act_v"
|
||||
f" WHERE _act_v.v {in_window}), {started})")
|
||||
|
||||
|
||||
def _sql_session_last_active(alias: str = "s") -> str:
|
||||
|
||||
@@ -57,6 +57,29 @@ def test_list_export_and_insights_survive_corrupt_timestamp_rows(corrupt_db, cap
|
||||
assert any("bad-huge" in rec.getMessage() for rec in caplog.records)
|
||||
|
||||
|
||||
def test_last_active_skips_a_garbage_message_timestamp(tmp_path):
|
||||
"""The Desktop sessions pane reads ``last_active`` straight from ``list_sessions_rich`` and builds
|
||||
``new Date(last_active * 1000)``; one salvaged garbage double must not become the session's recency
|
||||
(#91536)."""
|
||||
db = SessionDB(db_path=tmp_path / "state.db")
|
||||
try:
|
||||
db.create_session("recovered", "cli")
|
||||
for text in ("first", "second", "third"):
|
||||
db.append_message("recovered", "user", text)
|
||||
good = max(row["timestamp"] for row in db.get_messages("recovered"))
|
||||
db._execute_write(lambda conn: conn.execute(
|
||||
"UPDATE messages SET timestamp = 5.4905047707024164e+246 WHERE session_id = 'recovered' "
|
||||
"AND id = (SELECT MIN(id) FROM messages WHERE session_id = 'recovered')"))
|
||||
db._execute_write(lambda conn: conn.execute(
|
||||
"UPDATE sessions SET last_activity_at = 'not-a-timestamp' WHERE id = 'recovered'"))
|
||||
rows = {r["id"]: r for r in db.list_sessions_rich(limit=10)}
|
||||
tip_rows = {r["id"]: r for r in db.list_sessions_rich(limit=10, order_by_last_active=True)}
|
||||
finally:
|
||||
db.close()
|
||||
assert rows["recovered"]["last_active"] == good
|
||||
assert tip_rows["recovered"]["last_active"] == good
|
||||
|
||||
|
||||
def test_writers_never_persist_an_out_of_window_timestamp(tmp_path):
|
||||
db = SessionDB(db_path=tmp_path / "state.db")
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user