diff --git a/src/openjarvis/system.py b/src/openjarvis/system.py index 92dbf40c..b3074cb0 100644 --- a/src/openjarvis/system.py +++ b/src/openjarvis/system.py @@ -63,6 +63,7 @@ class JarvisSystem: tools: Optional[List[str]] = None, system_prompt: Optional[str] = None, operator_id: Optional[str] = None, + prior_messages: Optional[List[Message]] = None, ) -> Dict[str, Any]: """Execute a query through the system and return a result dict.""" if temperature is None: @@ -106,6 +107,7 @@ class JarvisSystem: max_tokens, system_prompt=system_prompt, operator_id=operator_id, + prior_messages=prior_messages, ) # Direct engine mode @@ -133,6 +135,7 @@ class JarvisSystem: *, system_prompt=None, operator_id=None, + prior_messages=None, ) -> Dict[str, Any]: """Run through an agent.""" from openjarvis.agents._stubs import AgentContext @@ -153,6 +156,11 @@ class JarvisSystem: # Build context ctx = AgentContext() + # Seed prior conversation turns (channel session history) + if prior_messages: + for msg in prior_messages: + ctx.conversation.add(msg) + # Inject memory context messages into the agent conversation if messages and len(messages) > 1: # Context messages were prepended by inject_context @@ -297,7 +305,6 @@ class JarvisSystem: A connected :class:`~openjarvis.channels._stubs.BaseChannel` instance whose ``on_message`` method accepts a callable. """ - from openjarvis.agents._stubs import AgentContext from openjarvis.core.types import Message, Role from openjarvis.sessions.session import SessionStore @@ -320,14 +327,13 @@ class JarvisSystem: channel_user_id=cm.sender, ) - # Rebuild prior conversation turns into AgentContext - ctx = AgentContext() + prior_msgs: List[Message] = [] for sm in session.messages: try: role = Role(sm.role) except ValueError: role = Role.USER - ctx.conversation.add(Message(role=role, content=sm.content)) + prior_msgs.append(Message(role=role, content=sm.content)) reply = "" try: @@ -336,10 +342,15 @@ class JarvisSystem: cm.content, context=False, agent=_system.agent_name, + prior_messages=prior_msgs, ) reply = result.get("content", "") else: - result = _system.ask(cm.content, context=False) + result = _system.ask( + cm.content, + context=False, + prior_messages=prior_msgs, + ) reply = result.get("content", "") except Exception: logger.exception("Channel message handler error") diff --git a/tests/sessions/test_wire_channel_history.py b/tests/sessions/test_wire_channel_history.py new file mode 100644 index 00000000..1ee0e38d --- /dev/null +++ b/tests/sessions/test_wire_channel_history.py @@ -0,0 +1,115 @@ +"""Tests for wire_channel session history""" + +from __future__ import annotations + +from types import SimpleNamespace +from unittest.mock import MagicMock + +import pytest + +from openjarvis.core.config import JarvisConfig +from openjarvis.core.events import EventBus +from openjarvis.core.types import Role +from openjarvis.system import JarvisSystem + + +@pytest.fixture() +def minimal_system(): + engine = MagicMock() + engine.generate.return_value = {"content": "ok", "usage": {}} + return JarvisSystem( + config=JarvisConfig(), + bus=EventBus(), + engine=engine, + engine_key="mock", + model="mock-model", + agent_name="none", + ) + + +class TestWireChannelHistory: + def test_prior_messages_passed_to_ask(self, tmp_path, minimal_system): + """Session history is forwarded as prior_messages to ask().""" + from openjarvis.sessions.session import SessionStore + + db = tmp_path / "sessions.db" + store = SessionStore(db_path=db) + minimal_system.session_store = store + + session_key = "telegram:chat123" + session = store.get_or_create( + session_key, channel="telegram", channel_user_id="u1" + ) + store.save_message(session.session_id, "user", "hello", channel="telegram") + store.save_message( + session.session_id, "assistant", "hi there", channel="telegram" + ) + + captured: list = [] + + def capturing_ask(query, **kwargs): + captured.append(kwargs.get("prior_messages", [])) + return {"content": "reply"} + + minimal_system.ask = capturing_ask + + bridge = MagicMock() + handler_ref: list = [] + + def capture_handler(fn): + handler_ref.append(fn) + + bridge.on_message = capture_handler + minimal_system.wire_channel(bridge) + + cm = SimpleNamespace( + channel="telegram", + conversation_id="chat123", + sender="u1", + content="second message", + ) + handler_ref[0](cm) + + assert len(captured) == 1 + msgs = captured[0] + assert len(msgs) == 2 + assert msgs[0].role == Role.USER + assert msgs[0].content == "hello" + assert msgs[1].role == Role.ASSISTANT + assert msgs[1].content == "hi there" + + def test_empty_session_passes_empty_prior_messages(self, tmp_path, minimal_system): + """First message in a new session passes prior_messages=[].""" + from openjarvis.sessions.session import SessionStore + + db = tmp_path / "sessions.db" + store = SessionStore(db_path=db) + minimal_system.session_store = store + + captured: list = [] + + def capturing_ask(query, **kwargs): + captured.append(kwargs.get("prior_messages", None)) + return {"content": "reply"} + + minimal_system.ask = capturing_ask + + bridge = MagicMock() + handler_ref: list = [] + + def capture_handler(fn): + handler_ref.append(fn) + + bridge.on_message = capture_handler + minimal_system.wire_channel(bridge) + + cm = SimpleNamespace( + channel="telegram", + conversation_id="new-chat", + sender="u2", + content="first message", + ) + handler_ref[0](cm) + + assert len(captured) == 1 + assert captured[0] == []