From 7aea9c32b7aeef397a4783be2213ac3b4ff3b01c Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sat, 23 May 2026 22:34:00 +0000 Subject: [PATCH 01/12] test(gateway): replace heavy AIAgent with lightweight stub in cache-capacity tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_concurrent_inserts_settle_at_cap created 160 real AIAgent instances (8 threads × 20 inserts). Each real init does tool-registry scan, MCP discovery, config loading and file I/O — ~200 ms — which exhausts the 30 s pytest-timeout budget under parallel xdist load on a saturated runner (load avg 16-37 on 8 CPUs). Replace _real_agent() with a _StubAgent that exposes only the two attributes the cache enforcement logic actually touches: .client (non-None, checked by active-safety tests) and .close() (cleanup). The stub has no init work and runs in microseconds, so the concurrent insert test finishes in <1 s even under xdist pressure. Sister tests test_fill_to_cap_then_spillover and test_spillover_all_active_keeps_cache_over_cap use the same helper and benefit equally from the lighter fixture. Closes #5. --- tests/gateway/test_agent_cache.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/tests/gateway/test_agent_cache.py b/tests/gateway/test_agent_cache.py index abf0ce34..1c025361 100644 --- a/tests/gateway/test_agent_cache.py +++ b/tests/gateway/test_agent_cache.py @@ -857,16 +857,27 @@ class TestAgentCacheSpilloverLive: runner._running_agents = {} return runner + class _StubAgent: + """Lightweight stand-in for AIAgent in cache-capacity tests. + + The cache enforcement logic only needs identity (``id()``) and a + ``.client`` attribute (verified by active-safety tests). Real + ``AIAgent`` instantiation does tool-registry scan, MCP discovery, + config loading and file I/O — ~200 ms each — which makes the + concurrent-insert test (160 agents) too heavy for CI under xdist. + """ + def __init__(self): + self.client = object() # non-None so active-safety assertions pass + + def close(self): + pass + + def release_clients(self): + pass + def _real_agent(self): - """A genuine AIAgent; no API calls are made during these tests.""" - from run_agent import AIAgent - return AIAgent( - model="anthropic/claude-sonnet-4", api_key="test", - base_url="https://openrouter.ai/api/v1", provider="openrouter", - max_iterations=5, quiet_mode=True, - skip_context_files=True, skip_memory=True, - platform="telegram", - ) + """A lightweight stub; replaces the heavy AIAgent for cache tests.""" + return self._StubAgent() def test_fill_to_cap_then_spillover(self, monkeypatch): """Fill to cap with real agents, insert one more, oldest evicted.""" -- 2.52.0 From 076fef7da5616286871215072b3bb839dee07fbd Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sat, 23 May 2026 23:31:05 +0000 Subject: [PATCH 02/12] chore(ci): re-trigger workflow run Empty commit to trigger CI statuses that have not been posted. -- 2.52.0 From 0d7682e7ef81007a0e77d7b5d99f67b818f65a91 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sat, 23 May 2026 23:46:10 +0000 Subject: [PATCH 03/12] chore(release): add agent-dev-a to AUTHOR_MAP Maps dev-engineer-a-kimi@agents.moleculesai.app so the contributor check passes for PRs authored by Engineer-A (Kimi). --- scripts/release.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/release.py b/scripts/release.py index ce029735..15e23c14 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -70,6 +70,7 @@ AUTHOR_MAP = { # Internal molecule-ai Gitea bot identity used by Claude-Code agents # (post-2026-05-06 GitHub suspension; no upstream/GitHub equivalent). "claude-ceo-assistant@agents.moleculesai.app": "claude-ceo-assistant", + "dev-engineer-a-kimi@agents.moleculesai.app": "agent-dev-a", # OpenViking viking_read salvage (April 2026) "hitesh@gmail.com": "htsh", "pty819@outlook.com": "pty819", -- 2.52.0 From 63add6f0cc824091ae75084c5a591a0791ba2791 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 00:30:49 +0000 Subject: [PATCH 04/12] ci: re-trigger tests after monitor timeout -- 2.52.0 From 2b3286f46c6c630fd101af03dea23d5d2d2badc3 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 01:12:51 +0000 Subject: [PATCH 05/12] ci: re-trigger stuck Tests / test runner -- 2.52.0 From dd9bbefffce0499014660807c9475e88e19e55dc Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 02:14:30 +0000 Subject: [PATCH 06/12] ci: re-trigger Tests / test after failure (empty commit) -- 2.52.0 From 3dcb67f88460aacc1aea99fa58206f318b9500dd Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 03:44:26 +0000 Subject: [PATCH 07/12] test(gateway): mock openai module at collection time for hermetic cache tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI/test containers may lack the openai package, causing 11+ failures in TestAgentCacheLifecycle and TestAgentCacheIdleResume where real AIAgent instantiation triggers ``from openai import OpenAI``. Follows the existing _ensure_telegram_mock / _ensure_discord_mock pattern in conftest.py. Idempotent — skips when the real library is already installed. Co-Authored-By: Claude Opus 4.7 --- tests/gateway/conftest.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/gateway/conftest.py b/tests/gateway/conftest.py index da8a2d33..57bd1d7e 100644 --- a/tests/gateway/conftest.py +++ b/tests/gateway/conftest.py @@ -83,6 +83,22 @@ def _ensure_telegram_mock() -> None: sys.modules["telegram.error"] = mod.error +def _ensure_openai_mock() -> None: + """Install a minimal openai mock in sys.modules. + + Idempotent — skips when the real library is already imported. + The mock provides ``openai.OpenAI`` as a MagicMock subclass so + ``AIAgent`` instantiation in cache tests succeeds without a real + OpenAI package (CI/test containers may lack it). + """ + if "openai" in sys.modules and hasattr(sys.modules["openai"], "__file__"): + return # Real library is installed — nothing to mock + + mod = MagicMock() + mod.OpenAI = MagicMock + sys.modules["openai"] = mod + + def _ensure_discord_mock() -> None: """Install a comprehensive discord mock in sys.modules. @@ -218,6 +234,7 @@ def _ensure_discord_mock() -> None: # Run at collection time — before any test file's module-level imports. _ensure_telegram_mock() _ensure_discord_mock() +_ensure_openai_mock() # --------------------------------------------------------------------------- -- 2.52.0 From 15d1cea22287dd61df211b5925a6e38b53b0111c Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 04:47:00 +0000 Subject: [PATCH 08/12] test(tencent): mock OpenRouter metadata to avoid live-network flake The hy3-preview context-length test was making a live call to OpenRouter's /models endpoint, which returns 262144 (256*1024) while the hardcoded fallback is 256000. This caused a deterministic failure on every CI run that reached the OpenRouter probe path. Mock fetch_model_metadata to return {} so the test falls through to the hardcoded default and is hermetic. Co-Authored-By: Claude Opus 4.7 --- tests/hermes_cli/test_tencent_tokenhub_provider.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/hermes_cli/test_tencent_tokenhub_provider.py b/tests/hermes_cli/test_tencent_tokenhub_provider.py index b84666e8..edbfd02f 100644 --- a/tests/hermes_cli/test_tencent_tokenhub_provider.py +++ b/tests/hermes_cli/test_tencent_tokenhub_provider.py @@ -2,6 +2,7 @@ import json import os +from unittest.mock import patch import pytest @@ -302,7 +303,10 @@ class TestTencentTokenhubContextLength: def test_hy3_preview_context_length(self): from agent.model_metadata import get_model_context_length - ctx = get_model_context_length("hy3-preview") + # Mock out the live OpenRouter call so the test is deterministic + # and falls through to the hardcoded default. + with patch("agent.model_metadata.fetch_model_metadata", return_value={}): + ctx = get_model_context_length("hy3-preview") assert ctx == 256000 -- 2.52.0 From 7077ed67113ad08928baaf983db34edd695b7f8c Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 05:11:56 +0000 Subject: [PATCH 09/12] ci: re-trigger stuck Tests / test runner after monitor timeout -- 2.52.0 From 886cdbd85f50e7c4fad9836f10bef3cf873f1b1b Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 06:39:59 +0000 Subject: [PATCH 10/12] fix(tests): use find_spec to check openai availability before mocking The _ensure_openai_mock() helper was checking sys.modules for 'openai', which fails at collection time because the real package hasn't been imported yet. This caused the mock to shadow the real openai module for ALL tests, breaking any test that relies on real openai behavior. Use importlib.util.find_spec('openai') instead, which checks whether the package is available without requiring a prior import. The mock is only installed when the package is genuinely missing. Fixes repo-wide Tests / test failures on PRs #19, #22, #26. --- tests/gateway/conftest.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/gateway/conftest.py b/tests/gateway/conftest.py index 57bd1d7e..b159c1b2 100644 --- a/tests/gateway/conftest.py +++ b/tests/gateway/conftest.py @@ -86,12 +86,14 @@ def _ensure_telegram_mock() -> None: def _ensure_openai_mock() -> None: """Install a minimal openai mock in sys.modules. - Idempotent — skips when the real library is already imported. + Idempotent — skips when the real library is available. The mock provides ``openai.OpenAI`` as a MagicMock subclass so ``AIAgent`` instantiation in cache tests succeeds without a real OpenAI package (CI/test containers may lack it). """ - if "openai" in sys.modules and hasattr(sys.modules["openai"], "__file__"): + import importlib.util + + if importlib.util.find_spec("openai") is not None: return # Real library is installed — nothing to mock mod = MagicMock() -- 2.52.0 From 30ec5643412e081ed7e8bb928b8a54ae603ea491 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 09:11:00 +0000 Subject: [PATCH 11/12] fix(tests): resolve stale cmd_update reference after sys.modules re-import test_env_loader.py and test_skills_subparser.py remove hermes_cli.main from sys.modules and re-import it. Tests that imported cmd_update at module level ended up with a stale function whose __globals__ referenced the old module, causing @patch("hermes_cli.main.sys") and @patch("hermes_cli.main._restore_stashed_changes") to silently miss. Import cmd_update locally inside each test so it always resolves against the current module object in sys.modules. Co-Authored-By: Claude Opus 4.7 --- tests/hermes_cli/test_update_yes_flag.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/tests/hermes_cli/test_update_yes_flag.py b/tests/hermes_cli/test_update_yes_flag.py index e36cc514..17116eb3 100644 --- a/tests/hermes_cli/test_update_yes_flag.py +++ b/tests/hermes_cli/test_update_yes_flag.py @@ -12,7 +12,19 @@ import subprocess from types import SimpleNamespace from unittest.mock import patch -from hermes_cli.main import cmd_update +def _cmd_update(args): + """Freshly resolve cmd_update so patches target the current module object. + + ``test_env_loader.py`` and ``test_skills_subparser.py`` remove + ``hermes_cli.main`` from ``sys.modules`` and re-import it. Tests that + import ``cmd_update`` at module level end up with a stale function whose + ``__globals__`` reference the *old* module, so ``@patch("hermes_cli.main.sys")`` + and ``@patch("hermes_cli.main._restore_stashed_changes")`` silently miss. + Importing locally inside each test fixes the mismatch. + """ + import hermes_cli.main as _main + + return _main.cmd_update(args) def _make_run_side_effect( @@ -74,7 +86,7 @@ class TestUpdateYesConfigMigration: args = SimpleNamespace(yes=True) with patch("builtins.input") as mock_input: - cmd_update(args) + _cmd_update(args) # Never prompted the user. mock_input.assert_not_called() @@ -118,7 +130,7 @@ class TestUpdateYesConfigMigration: ) as mock_sys: mock_sys.stdin.isatty.return_value = True mock_sys.stdout.isatty.return_value = True - cmd_update(args) + _cmd_update(args) # The user was actually prompted. assert mock_input.called prompts = [c.args[0] if c.args else "" for c in mock_input.call_args_list] @@ -156,7 +168,7 @@ class TestUpdateYesStashRestore: args = SimpleNamespace(yes=True) - cmd_update(args) + _cmd_update(args) # _restore_stashed_changes was called, and called with prompt_user=False # every time (so the user never sees "Restore local changes now?"). -- 2.52.0 From eea41507713f3b635c574809196a73a897a8e769 Mon Sep 17 00:00:00 2001 From: "Molecule AI Dev Engineer A (Kimi)" Date: Sun, 24 May 2026 10:17:13 +0000 Subject: [PATCH 12/12] fix(tests): clear stale _event_channels before PTY websocket tests gateway tests (and potentially other suites) that drive the /api/events endpoint can leave stale subscriber state in the process-level _event_channels dict. When xdist places those tests in the same worker as TestPtyWebSocket::test_pub_broadcasts_to_events_subscribers, the stale state causes the broadcast to hang because a dead websocket consumes the message or the channel set is corrupted. Clear _event_channels in the autouse fixture so every PTY test starts with a clean subscription registry. Co-Authored-By: Claude Opus 4.7 --- tests/hermes_cli/test_web_server.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index f2aed86d..da8ca13b 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -1970,6 +1970,9 @@ class TestPtyWebSocket: self.ws_module = ws monkeypatch.setattr(ws, "_DASHBOARD_EMBEDDED_CHAT_ENABLED", True) self.token = ws._SESSION_TOKEN + # Purge any stale subscriber state left behind by other test suites + # that also drive the /api/events endpoint (e.g. gateway tests). + ws._event_channels.clear() self.client = TestClient(ws.app) def _url(self, token: str | None = None, **params: str) -> str: -- 2.52.0