diff --git a/tests/hermes_cli/test_web_server.py b/tests/hermes_cli/test_web_server.py index 1b32934c22..0093dfb97c 100644 --- a/tests/hermes_cli/test_web_server.py +++ b/tests/hermes_cli/test_web_server.py @@ -1957,13 +1957,30 @@ class TestPtyWebSocket: def test_pub_broadcasts_to_events_subscribers(self, monkeypatch): """Frame written to /api/pub is rebroadcast verbatim to every /api/events subscriber on the same channel.""" + import time from urllib.parse import urlencode + from hermes_cli import web_server as ws_mod qs = urlencode({"token": self.token, "channel": "broadcast-test"}) pub_path = f"/api/pub?{qs}" sub_path = f"/api/events?{qs}" with self.client.websocket_connect(sub_path) as sub: + # Wait for the subscriber to be registered on the server side. + # websocket_connect returns when ws.accept() completes, but the + # server adds us to ``_event_channels`` in a follow-up await, + # so a publish immediately after connect can race ahead of the + # subscriber registration and the message is dropped. + deadline = time.monotonic() + 5.0 + while time.monotonic() < deadline: + if ws_mod._event_channels.get("broadcast-test"): + break + time.sleep(0.01) + else: + raise AssertionError( + "subscriber did not register on channel within 5s" + ) + with self.client.websocket_connect(pub_path) as pub: pub.send_text('{"type":"tool.start","payload":{"tool_id":"t1"}}') received = sub.receive_text() diff --git a/tests/plugins/test_achievements_plugin.py b/tests/plugins/test_achievements_plugin.py index c12503ac95..782aea7b39 100644 --- a/tests/plugins/test_achievements_plugin.py +++ b/tests/plugins/test_achievements_plugin.py @@ -50,6 +50,12 @@ def plugin_api(tmp_path, monkeypatch): ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) + # Stash monkeypatch so ``_install_fake_session_db`` can use it to + # swap ``sys.modules['hermes_state']`` with auto-restoration. Without + # this, a raw ``sys.modules[...] = fake`` assignment would leak the + # fake into later tests in the same xdist worker — breaking every + # test that does ``from hermes_state import SessionDB``. + module._test_monkeypatch = monkeypatch yield module @@ -107,10 +113,15 @@ class _FakeSessionDB: def _install_fake_session_db(plugin_api, fake_db): - """Inject a fake SessionDB so ``scan_sessions`` finds it via its local import.""" + """Inject a fake SessionDB so ``scan_sessions`` finds it via its local import. + + Uses the monkeypatch stashed on ``plugin_api`` by the fixture, so the + ``sys.modules['hermes_state']`` swap is auto-restored at test teardown + and cannot leak into unrelated tests in the same xdist worker. + """ fake_module = type(sys)("hermes_state") fake_module.SessionDB = lambda: fake_db - sys.modules["hermes_state"] = fake_module + plugin_api._test_monkeypatch.setitem(sys.modules, "hermes_state", fake_module) def test_scan_sessions_default_scans_all_history_not_first_200(plugin_api): diff --git a/ui-tui/src/app/slash/commands/ops.ts b/ui-tui/src/app/slash/commands/ops.ts index 2bf262ee2f..ad9f3e94d1 100644 --- a/ui-tui/src/app/slash/commands/ops.ts +++ b/ui-tui/src/app/slash/commands/ops.ts @@ -82,7 +82,7 @@ export const opsCommands: SlashCommand[] = [ // Parse arg: `now` / `always` skip the confirmation gate. // `always` additionally persists approvals.mcp_reload_confirm=false. const a = (arg || '').trim().toLowerCase() - const params: { session_id: string; confirm?: boolean; always?: boolean } = { + const params: { session_id: string | null; confirm?: boolean; always?: boolean } = { session_id: ctx.sid } if (a === 'now' || a === 'approve' || a === 'once' || a === 'yes') {