Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license

This commit is contained in:
2026-09-05 13:26:46 +03:00
commit 03634b1ca3
11340 changed files with 3442369 additions and 0 deletions
@@ -0,0 +1,259 @@
"""Regression tests: a slow gateway handoff dispatch must not be misreported
as "gateway not running" — and no waiter may stomp a claimed (running) row.
Bug shape (live-reproduced on main @1c5ee5815f): /handoff poll-waited a flat
60s for a TERMINAL state. The gateway watcher claims within seconds, but the
dispatch is a FULL synthetic agent turn (whole transcript replay + delivery)
that routinely exceeds 60s. The CLI then printed "Timed out waiting for the
gateway. Is `hermes gateway` running?" (false diagnosis), called
fail_handoff() on the RUNNING row (stomping the gateway's claim), and claimed
"Your CLI session is intact" after switch_session had already re-pointed the
session. The gateway later overwrote failed -> completed: split-brain.
Fix under test:
1. SessionDB.fail_handoff(only_states=...) — CAS: waiters can only fail
rows still in the given states.
2. CLI _handle_handoff_command: 60s deadline applies only to PENDING;
a RUNNING row gets a long (15 min) wait and is never failed by the CLI.
3. tui_gateway handoff.fail: only pending rows can be failed by Desktop.
"""
from __future__ import annotations
import time
import types
from unittest.mock import MagicMock, patch
import pytest
from hermes_state import SessionDB
@pytest.fixture()
def db(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path / ".hermes"))
d = SessionDB(db_path=tmp_path / "state.db")
yield d
d.close()
# ---------------------------------------------------------------------------
# 1. State layer: fail_handoff CAS
# ---------------------------------------------------------------------------
class TestFailHandoffCAS:
def test_cas_fails_pending_row(self, db):
db.ensure_session("s1", "cli")
assert db.request_handoff("s1", "discord")
assert db.fail_handoff("s1", "timed out", only_states=("pending",)) is True
assert db.get_handoff_state("s1")["state"] == "failed"
def test_cas_refuses_running_row(self, db):
"""A waiter timeout must NOT stomp a row the gateway has claimed."""
db.ensure_session("s2", "cli")
assert db.request_handoff("s2", "discord")
assert db.claim_handoff("s2") # gateway claimed: pending -> running
assert db.fail_handoff("s2", "timed out", only_states=("pending",)) is False
assert db.get_handoff_state("s2")["state"] == "running"
# gateway still reaches its own terminal state
db.complete_handoff("s2")
assert db.get_handoff_state("s2")["state"] == "completed"
def test_unconditional_fail_still_available_to_owner(self, db):
"""The gateway watcher (owner of a claimed row) fails unconditionally."""
db.ensure_session("s3", "cli")
assert db.request_handoff("s3", "discord")
assert db.claim_handoff("s3")
assert db.fail_handoff("s3", "dispatch raised") is True
assert db.get_handoff_state("s3")["state"] == "failed"
def test_cas_refuses_terminal_rows(self, db):
db.ensure_session("s4", "cli")
assert db.request_handoff("s4", "discord")
assert db.claim_handoff("s4")
db.complete_handoff("s4")
assert db.fail_handoff("s4", "late timeout", only_states=("pending",)) is False
assert db.get_handoff_state("s4")["state"] == "completed"
# ---------------------------------------------------------------------------
# 2. CLI wait loop
# ---------------------------------------------------------------------------
def _run_handoff(db, session_id, monkeypatch, time_budget=30.0):
"""Drive the real _handle_handoff_command against a real SessionDB.
Gateway config / platform plumbing is stubbed; the poll loop, state
transitions, and fail semantics are exercised for real. time.sleep is
compressed so a simulated 60s pending deadline elapses in well under a
second of wall clock.
"""
from hermes_cli.cli_commands_mixin import CLICommandsMixin
printed: list[str] = []
class Host(CLICommandsMixin):
def __init__(self):
self.session_id = session_id
self._session_db = db
self._agent_running = False
self._should_exit = False
host = Host.__new__(Host)
Host.__init__(host)
home = types.SimpleNamespace(chat_id="123", name="home", thread_id=None)
gw_config = MagicMock()
gw_config.platforms = {}
gw_config.get_home_channel.return_value = home
import gateway.config as gwc
platform_obj = gwc.Platform("discord")
pcfg = types.SimpleNamespace(enabled=True, extra={})
gw_config.platforms = {platform_obj: pcfg}
# Compress time: each sleep(0.5) advances a fake clock by 2.0s.
clock = {"t": time.time()}
def fake_time():
return clock["t"]
def fake_sleep(secs):
clock["t"] += max(secs * 4, 2.0)
import cli as cli_mod
with patch.object(gwc, "load_gateway_config", return_value=gw_config), \
patch("time.time", side_effect=fake_time), \
patch("time.sleep", side_effect=fake_sleep), \
patch.object(cli_mod, "_cprint", side_effect=lambda s="": printed.append(s)):
keep_going = host._handle_handoff_command("/handoff discord")
return keep_going, printed, host
class TestCLIWaitLoop:
def test_pending_timeout_still_reports_gateway_down(self, db, monkeypatch):
"""No watcher ever claims the row -> 60s pending timeout, row failed."""
db.ensure_session("cli-sess-a", "cli")
keep, printed, _ = _run_handoff(db, "cli-sess-a", monkeypatch)
out = "\n".join(printed)
assert keep is True
assert "Timed out waiting for the gateway" in out
assert db.get_handoff_state("cli-sess-a")["state"] == "failed"
def test_running_row_is_never_failed_by_cli(self, db, monkeypatch):
"""Row claimed (running) and never finishing: CLI gives up eventually
but must NOT fail the row and must NOT print the gateway-down line."""
db.ensure_session("cli-sess-b", "cli")
# Pre-claim: by the time the CLI polls, the gateway owns the row.
# request_handoff happens inside the command; claim it from a fake
# watcher the instant it lands via a get_handoff_state side hook.
real_get = db.get_handoff_state
def claiming_get(sid):
row = real_get(sid)
if row and row.get("state") == "pending":
db.claim_handoff(sid)
row = real_get(sid)
return row
db_proxy = MagicMock(wraps=db)
db_proxy.get_handoff_state.side_effect = claiming_get
db_proxy.request_handoff.side_effect = db.request_handoff
db_proxy.fail_handoff.side_effect = db.fail_handoff
keep, printed, _ = _run_handoff(db_proxy, "cli-sess-b", monkeypatch)
out = "\n".join(printed)
assert keep is True
assert "Is `hermes gateway` running?" not in out
assert "taking unusually long" in out
# The row is still owned by the gateway — untouched by the CLI.
assert db.get_handoff_state("cli-sess-b")["state"] == "running"
# Late gateway completion wins cleanly (no split-brain).
db.complete_handoff("cli-sess-b")
assert db.get_handoff_state("cli-sess-b")["state"] == "completed"
def test_slow_dispatch_beyond_60s_completes(self, db, monkeypatch):
"""The exact live-repro shape: claim @~5s, complete @~80s simulated.
Old code timed out at 60s with the false 'gateway running?' message;
new code waits through the running phase and exits on completed."""
db.ensure_session("cli-sess-c", "cli")
t0 = {"polls": 0}
real_get = db.get_handoff_state
def slow_gateway(sid):
t0["polls"] += 1
row = real_get(sid)
state = (row or {}).get("state")
if state == "pending" and t0["polls"] >= 2:
db.claim_handoff(sid)
# each poll ~2s simulated; complete after ~40 polls (~80s+)
if state == "running" and t0["polls"] >= 45:
db.complete_handoff(sid)
return real_get(sid)
db_proxy = MagicMock(wraps=db)
db_proxy.get_handoff_state.side_effect = slow_gateway
db_proxy.request_handoff.side_effect = db.request_handoff
db_proxy.fail_handoff.side_effect = db.fail_handoff
keep, printed, host = _run_handoff(db_proxy, "cli-sess-c", monkeypatch)
out = "\n".join(printed)
assert keep is False # completed -> CLI exits like /quit
assert "Handoff complete" in out
assert "Timed out waiting for the gateway" not in out
assert host._should_exit is True
assert db.get_handoff_state("cli-sess-c")["state"] == "completed"
# ---------------------------------------------------------------------------
# 3. Desktop handoff.fail RPC
# ---------------------------------------------------------------------------
class TestDesktopHandoffFail:
def _call(self, db, session_key):
"""Invoke the handoff.fail handler body with server-global stand-ins."""
import contextlib
from tui_gateway import methods_session as ms
handler = None
for name, fn in ms._registry._pending:
if name == "handoff.fail":
handler = fn
break
assert handler is not None, "handoff.fail handler not registered"
session = {"session_key": session_key}
@contextlib.contextmanager
def fake_session_db(_s):
yield db
globs = dict(handler.__globals__)
globs["_sess_nowait"] = lambda p, r: (session, None)
globs["_session_db"] = fake_session_db
globs["_ok"] = lambda rid, result: {"ok": True, **result}
globs["_db_unavailable_error"] = lambda rid, code: {"error": code}
rebound = types.FunctionType(
handler.__code__, globs, handler.__name__,
handler.__defaults__, handler.__closure__,
)
return rebound("rid", {"error": "poll timeout"})
def test_desktop_fail_refuses_running_row(self, db):
db.ensure_session("d1", "desktop")
assert db.request_handoff("d1", "discord")
assert db.claim_handoff("d1")
res = self._call(db, "d1")
assert res["failed"] is False
assert res["state"] == "running"
assert db.get_handoff_state("d1")["state"] == "running"
def test_desktop_fail_still_fails_pending_row(self, db):
db.ensure_session("d2", "desktop")
assert db.request_handoff("d2", "discord")
res = self._call(db, "d2")
assert res["failed"] is True
assert db.get_handoff_state("d2")["state"] == "failed"