mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-21 10:22:18 +00:00
Merge remote-tracking branch 'origin/main' into pr48275-rebase
# Conflicts: # cron/scheduler.py
This commit is contained in:
commit
a58287afcb
162 changed files with 8521 additions and 634 deletions
|
|
@ -83,6 +83,66 @@ def _make_mock_client():
|
|||
return client
|
||||
|
||||
|
||||
def _provider_for_mode(tmp_path, monkeypatch, mode: str):
|
||||
"""Create an initialized provider without pre-seeding its client."""
|
||||
config = {
|
||||
"mode": mode,
|
||||
"apiKey": "test-key",
|
||||
"api_url": "http://localhost:9999",
|
||||
"bank_id": "test-bank",
|
||||
"budget": "mid",
|
||||
"memory_mode": "hybrid",
|
||||
}
|
||||
config_path = tmp_path / "hindsight" / "config.json"
|
||||
config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
config_path.write_text(json.dumps(config))
|
||||
|
||||
monkeypatch.setattr(
|
||||
"plugins.memory.hindsight.get_hermes_home", lambda: tmp_path
|
||||
)
|
||||
|
||||
provider = HindsightMemoryProvider()
|
||||
provider.initialize(session_id="test-session", hermes_home=str(tmp_path), platform="cli")
|
||||
return provider
|
||||
|
||||
|
||||
def _assert_cloud_client_lazy_installed_before_import(tmp_path, monkeypatch, mode: str):
|
||||
"""Cloud/local-external clients must ensure lazy deps before importing."""
|
||||
import builtins
|
||||
|
||||
provider = _provider_for_mode(tmp_path, monkeypatch, mode)
|
||||
ensure_calls = []
|
||||
|
||||
def fake_ensure(feature, prompt=True):
|
||||
ensure_calls.append((feature, prompt))
|
||||
|
||||
class FakeHindsight:
|
||||
def __init__(self, **kwargs):
|
||||
self.kwargs = kwargs
|
||||
|
||||
real_import = builtins.__import__
|
||||
|
||||
def guarded_import(name, globals=None, locals=None, fromlist=(), level=0):
|
||||
if name == "hindsight_client":
|
||||
if ensure_calls != [("memory.hindsight", False)]:
|
||||
raise ModuleNotFoundError("No module named 'hindsight_client'")
|
||||
return SimpleNamespace(Hindsight=FakeHindsight)
|
||||
return real_import(name, globals, locals, fromlist, level)
|
||||
|
||||
monkeypatch.setattr("tools.lazy_deps.ensure", fake_ensure)
|
||||
monkeypatch.setattr(builtins, "__import__", guarded_import)
|
||||
|
||||
client = provider._get_client()
|
||||
|
||||
assert ensure_calls == [("memory.hindsight", False)]
|
||||
assert isinstance(client, FakeHindsight)
|
||||
assert client.kwargs == {
|
||||
"base_url": "http://localhost:9999",
|
||||
"timeout": 120.0,
|
||||
"api_key": "test-key",
|
||||
}
|
||||
|
||||
|
||||
class _FakeSessionDB:
|
||||
def __init__(self, messages=None):
|
||||
self._messages = list(messages or [])
|
||||
|
|
@ -232,6 +292,14 @@ class TestSchemas:
|
|||
|
||||
|
||||
class TestConfig:
|
||||
def test_cloud_client_lazy_installs_dependency_before_import(self, tmp_path, monkeypatch):
|
||||
_assert_cloud_client_lazy_installed_before_import(tmp_path, monkeypatch, "cloud")
|
||||
|
||||
def test_local_external_client_lazy_installs_dependency_before_import(self, tmp_path, monkeypatch):
|
||||
_assert_cloud_client_lazy_installed_before_import(
|
||||
tmp_path, monkeypatch, "local_external"
|
||||
)
|
||||
|
||||
def test_default_values(self, provider):
|
||||
assert provider._auto_retain is True
|
||||
assert provider._auto_recall is True
|
||||
|
|
|
|||
|
|
@ -1975,7 +1975,10 @@ def test_on_session_switch_commits_old_session_and_rotates_id():
|
|||
|
||||
provider.on_session_switch("new-sid", parent_session_id="old-sid")
|
||||
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
assert provider._session_id == "new-sid"
|
||||
assert provider._turn_count == 0
|
||||
|
||||
|
|
@ -1998,7 +2001,10 @@ def test_on_session_switch_commits_pending_tokens_without_turn_count():
|
|||
provider.on_session_switch("new-sid")
|
||||
|
||||
provider._client.get.assert_called_once_with("/api/v1/sessions/old-sid")
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
assert provider._session_id == "new-sid"
|
||||
assert provider._turn_count == 0
|
||||
|
||||
|
|
@ -2051,7 +2057,10 @@ def test_on_session_switch_waits_for_inflight_sync_thread():
|
|||
provider.on_session_switch("new-sid")
|
||||
|
||||
assert join_calls, "expected on_session_switch to join the in-flight sync thread"
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
|
||||
|
||||
def test_on_session_switch_noop_on_empty_new_id():
|
||||
|
|
@ -2186,6 +2195,78 @@ def test_sync_turn_retries_batch_write_with_fresh_client():
|
|||
)]
|
||||
|
||||
|
||||
def test_sync_turn_structured_messages_include_assistant_peer_id():
|
||||
provider = OpenVikingMemoryProvider()
|
||||
provider._client = MagicMock()
|
||||
provider._endpoint = "http://test"
|
||||
provider._api_key = ""
|
||||
provider._account = "acct"
|
||||
provider._user = "usr"
|
||||
provider._agent = "hermes"
|
||||
provider._session_id = "sid-structured"
|
||||
|
||||
captured = []
|
||||
|
||||
class StubClient:
|
||||
def __init__(self, *a, **kw):
|
||||
pass
|
||||
|
||||
def post(self, path, payload=None, **kwargs):
|
||||
captured.append((path, payload))
|
||||
return {}
|
||||
|
||||
import plugins.memory.openviking as _mod
|
||||
|
||||
real_client_cls = _mod._VikingClient
|
||||
_mod._VikingClient = StubClient
|
||||
messages = [
|
||||
{"role": "user", "content": [{"type": "input_text", "text": "u"}]},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "Looking.",
|
||||
"tool_calls": [
|
||||
{
|
||||
"id": "call-1",
|
||||
"type": "function",
|
||||
"function": {"name": "shell_command", "arguments": json.dumps({"cmd": "pwd"})},
|
||||
}
|
||||
],
|
||||
},
|
||||
{"role": "tool", "tool_call_id": "call-1", "name": "shell_command", "content": "ok"},
|
||||
{"role": "assistant", "content": [{"type": "output_text", "text": "a"}]},
|
||||
]
|
||||
try:
|
||||
provider.sync_turn("u", "a", messages=messages)
|
||||
assert provider._drain_writers("sid-structured", timeout=2.0)
|
||||
finally:
|
||||
_mod._VikingClient = real_client_cls
|
||||
|
||||
assert captured == [(
|
||||
"/api/v1/sessions/sid-structured/messages/batch",
|
||||
{
|
||||
"messages": [
|
||||
{"role": "user", "parts": [{"type": "text", "text": "u"}]},
|
||||
{"role": "assistant", "parts": [{"type": "text", "text": "Looking."}], "peer_id": "hermes"},
|
||||
{
|
||||
"role": "assistant",
|
||||
"parts": [
|
||||
{
|
||||
"type": "tool",
|
||||
"tool_id": "call-1",
|
||||
"tool_name": "shell_command",
|
||||
"tool_input": {"cmd": "pwd"},
|
||||
"tool_output": "ok",
|
||||
"tool_status": "completed",
|
||||
}
|
||||
],
|
||||
"peer_id": "hermes",
|
||||
},
|
||||
{"role": "assistant", "parts": [{"type": "text", "text": "a"}], "peer_id": "hermes"},
|
||||
]
|
||||
},
|
||||
)]
|
||||
|
||||
|
||||
def test_sync_turn_noop_when_session_id_blank():
|
||||
provider = OpenVikingMemoryProvider()
|
||||
provider._client = MagicMock()
|
||||
|
|
@ -2206,7 +2287,10 @@ def test_on_session_end_marks_session_clean_after_successful_commit():
|
|||
|
||||
provider.on_session_end([])
|
||||
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
assert provider._turn_count == 0
|
||||
|
||||
|
||||
|
|
@ -2228,7 +2312,10 @@ def test_on_session_end_commits_pending_tokens_without_turn_count():
|
|||
provider.on_session_end([])
|
||||
|
||||
provider._client.get.assert_called_once_with("/api/v1/sessions/old-sid")
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
|
||||
|
||||
def test_end_then_switch_does_not_double_commit():
|
||||
|
|
@ -2241,7 +2328,10 @@ def test_end_then_switch_does_not_double_commit():
|
|||
provider.on_session_switch("new-sid", parent_session_id="old-sid")
|
||||
|
||||
# Exactly one commit call, on the OLD session, fired by on_session_end.
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
assert provider._session_id == "new-sid"
|
||||
assert provider._turn_count == 0
|
||||
|
||||
|
|
@ -2253,7 +2343,10 @@ def test_end_then_switch_with_pending_tokens_does_not_double_commit():
|
|||
provider.on_session_end([])
|
||||
provider.on_session_switch("new-sid", parent_session_id="old-sid")
|
||||
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
assert provider._session_id == "new-sid"
|
||||
assert provider._turn_count == 0
|
||||
|
||||
|
|
@ -2400,7 +2493,10 @@ def test_on_session_switch_does_not_block_caller_on_slow_drain():
|
|||
# Let the finalizer finish so it doesn't leak past the test.
|
||||
release_drain.set()
|
||||
assert provider._drain_finalizers(timeout=5.0)
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
|
||||
|
||||
def test_on_session_switch_defers_old_commit_to_finalizer_thread():
|
||||
|
|
@ -2415,7 +2511,7 @@ def test_on_session_switch_defers_old_commit_to_finalizer_thread():
|
|||
committed = threading.Event()
|
||||
drain_timeouts = []
|
||||
|
||||
def fake_post(path):
|
||||
def fake_post(path, payload=None):
|
||||
committed.set()
|
||||
return {}
|
||||
|
||||
|
|
@ -2433,7 +2529,10 @@ def test_on_session_switch_defers_old_commit_to_finalizer_thread():
|
|||
assert provider._turn_count == 0
|
||||
# The old-session commit lands on the finalizer thread, not inline.
|
||||
assert committed.wait(timeout=5.0), "old session was not finalized off-thread"
|
||||
provider._client.post.assert_called_once_with("/api/v1/sessions/old-sid/commit")
|
||||
provider._client.post.assert_called_once_with(
|
||||
"/api/v1/sessions/old-sid/commit",
|
||||
{"keep_recent_count": 0},
|
||||
)
|
||||
# The finalizer drains with the deferred (longer) budget, not inline 10s.
|
||||
assert drain_timeouts == [_DEFERRED_COMMIT_TIMEOUT]
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue