Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
"""Regression tests: session continuity when the default/global state.db is unavailable.
|
||||
|
||||
Root cause (live incident, 2026-08-17): when the default/global state.db is
|
||||
corrupt or unreadable at gateway startup, ``SessionStore._db`` degrades to
|
||||
None (JSONL routing fallback) and ``GatewayRunner._session_db`` is None.
|
||||
Under multiplexed profile routes the AIAgent lazily opens the PROFILE's
|
||||
state.db (``AIAgent._get_session_db_for_recall``) and its
|
||||
``_ensure_db_session`` created the session row WITHOUT routing identity
|
||||
(session_key/chat_id/chat_type/thread_id/user_id/origin_json/display_name
|
||||
all NULL), so the row was unrecoverable by
|
||||
``find_latest_gateway_session_for_peer``. The gateway's own
|
||||
``record_gateway_session_peer`` self-heal never ran because ``_db`` was None.
|
||||
On the next message, ``SessionStore.load_transcript`` returned [] even though
|
||||
the transcript existed in the profile DB -> the agent lost prior context.
|
||||
|
||||
These tests pin fix 1 (routing identity on lazy row creation):
|
||||
``AIAgent._ensure_db_session`` persists the full routing identity the
|
||||
agent already carries, so the lazily-created row is recoverable. (The
|
||||
transcript-recovery half of the original report is covered separately by
|
||||
the scope-aware session DB resolution on main.)
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
SESSION_ID = "degraded-db-continuity-session"
|
||||
CHAT_ID = "-1001234567890"
|
||||
THREAD_ID = "42"
|
||||
SESSION_KEY = "agent:orion:telegram:group:-1001234567890:8148316720"
|
||||
|
||||
|
||||
def _make_gateway_agent(session_db):
|
||||
"""Build an AIAgent carrying gateway routing identity (multiplex profile route)."""
|
||||
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}):
|
||||
from run_agent import AIAgent
|
||||
|
||||
agent = AIAgent(
|
||||
api_key="test-key",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
model="test/model",
|
||||
quiet_mode=True,
|
||||
session_db=session_db,
|
||||
session_id=SESSION_ID,
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
platform="telegram",
|
||||
user_id="8148316720",
|
||||
user_name="testuser",
|
||||
chat_id=CHAT_ID,
|
||||
chat_name="Test Group",
|
||||
chat_type="group",
|
||||
thread_id=THREAD_ID,
|
||||
gateway_session_key=SESSION_KEY,
|
||||
)
|
||||
# The multiplexed profile scope would resolve the active profile name to
|
||||
# the route's profile (e.g. "orion"); pin it so the row + origin_json
|
||||
# carry the profile deterministically.
|
||||
with patch("hermes_cli.profiles.get_active_profile_name", return_value="orion"):
|
||||
agent._ensure_db_session()
|
||||
return agent
|
||||
|
||||
|
||||
class TestEnsureDbSessionPersistsRoutingIdentity:
|
||||
def test_lazy_creation_writes_session_key_and_chat_identity(self):
|
||||
"""First-created row must carry gateway routing metadata (not identity-less)."""
|
||||
from hermes_state import SessionDB
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db = SessionDB(db_path=Path(tmpdir) / "t.db")
|
||||
try:
|
||||
_make_gateway_agent(db)
|
||||
row = db.get_session(SESSION_ID)
|
||||
assert row is not None
|
||||
assert row["session_key"] == SESSION_KEY
|
||||
assert row["chat_id"] == CHAT_ID
|
||||
assert row["chat_type"] == "group"
|
||||
assert row["thread_id"] == THREAD_ID
|
||||
assert row["user_id"] == "8148316720"
|
||||
assert row["display_name"] == "Test Group"
|
||||
assert row["source"] == "telegram"
|
||||
assert row["profile_name"] == "orion"
|
||||
origin = json.loads(row["origin_json"])
|
||||
assert origin["platform"] == "telegram"
|
||||
assert origin["chat_id"] == CHAT_ID
|
||||
assert origin["chat_name"] == "Test Group"
|
||||
assert origin["chat_type"] == "group"
|
||||
assert origin["user_id"] == "8148316720"
|
||||
assert origin["user_name"] == "testuser"
|
||||
assert origin["thread_id"] == THREAD_ID
|
||||
assert origin["profile"] == "orion"
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def test_row_recoverable_by_peer_lookup(self):
|
||||
"""find_latest_gateway_session_for_peer must find the lazily-created row."""
|
||||
from hermes_state import SessionDB
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db = SessionDB(db_path=Path(tmpdir) / "t.db")
|
||||
try:
|
||||
_make_gateway_agent(db)
|
||||
# The peer lookup keys on session_key + source; the regression
|
||||
# left session_key NULL so this returned None on every restart.
|
||||
found = db.find_latest_gateway_session_for_peer(
|
||||
session_key=SESSION_KEY,
|
||||
source="telegram",
|
||||
)
|
||||
assert found is not None
|
||||
assert found["id"] == SESSION_ID
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
def test_cli_session_stays_identity_less(self):
|
||||
"""A plain CLI agent (no gateway fields) keeps the old shape — no origin_json."""
|
||||
from hermes_state import SessionDB
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
db = SessionDB(db_path=Path(tmpdir) / "t.db")
|
||||
try:
|
||||
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}):
|
||||
from run_agent import AIAgent
|
||||
|
||||
agent = AIAgent(
|
||||
api_key="test-key",
|
||||
base_url="https://openrouter.ai/api/v1",
|
||||
model="test/model",
|
||||
quiet_mode=True,
|
||||
session_db=db,
|
||||
session_id=SESSION_ID + "-cli",
|
||||
skip_context_files=True,
|
||||
skip_memory=True,
|
||||
)
|
||||
agent._ensure_db_session()
|
||||
row = db.get_session(SESSION_ID + "-cli")
|
||||
assert row is not None
|
||||
assert row["session_key"] is None
|
||||
assert row["chat_id"] is None
|
||||
assert row["origin_json"] is None
|
||||
finally:
|
||||
db.close()
|
||||
Reference in New Issue
Block a user