File size: 4,404 Bytes
b5b9c2e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""Tests for session reset completeness (fixes #2635).

/clear and /new must not carry stale state into the next session.
Two fields were added after reset_session_state() was written and were
therefore never cleared:
  - ContextCompressor._previous_summary
  - AIAgent._user_turn_count
"""
import sys
import types
from pathlib import Path

import pytest

# Ensure repo root is importable
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent))

# Stub out optional heavy dependencies not installed in the test environment
sys.modules.setdefault("fire", types.SimpleNamespace(Fire=lambda *a, **k: None))
sys.modules.setdefault("firecrawl", types.SimpleNamespace(Firecrawl=object))
sys.modules.setdefault("fal_client", types.SimpleNamespace())

from run_agent import AIAgent
from agent.context_compressor import ContextCompressor


def _make_minimal_agent() -> AIAgent:
    """Return an AIAgent constructed with the absolute minimum args.

    We pass dummy values that bypass network calls and filesystem access.
    The object is never used to make API calls — only its attributes and
    reset_session_state() are exercised.
    """
    agent = AIAgent.__new__(AIAgent)  # skip __init__ entirely

    # Seed the exact attributes that reset_session_state() writes
    agent.session_total_tokens = 0
    agent.session_input_tokens = 0
    agent.session_output_tokens = 0
    agent.session_prompt_tokens = 0
    agent.session_completion_tokens = 0
    agent.session_cache_read_tokens = 0
    agent.session_cache_write_tokens = 0
    agent.session_reasoning_tokens = 0
    agent.session_api_calls = 0
    agent.session_estimated_cost_usd = 0.0
    agent.session_cost_status = "unknown"
    agent.session_cost_source = "none"

    # The two fields under test
    agent._user_turn_count = 0
    agent.context_compressor = None  # will be set per-test as needed

    return agent


class TestResetSessionState:
    """reset_session_state() must clear ALL session-scoped state."""

    def test_previous_summary_cleared_on_reset(self):
        """Compression summary from old session must not leak into new session."""
        agent = _make_minimal_agent()
        compressor = ContextCompressor.__new__(ContextCompressor)
        compressor._previous_summary = "Old session summary about unrelated topic"
        # Seed counter attributes that reset_session_state touches
        compressor.last_prompt_tokens = 100
        compressor.last_completion_tokens = 50
        compressor.last_total_tokens = 150
        compressor.compression_count = 3
        compressor._context_probed = True

        agent.context_compressor = compressor

        agent.reset_session_state()

        assert compressor._previous_summary is None, (
            "_previous_summary must be None after reset; got: "
            f"{compressor._previous_summary!r}"
        )

    def test_user_turn_count_cleared_on_reset(self):
        """Turn counter must reset to 0 on new session."""
        agent = _make_minimal_agent()
        agent._user_turn_count = 7  # simulates turns accumulated in previous session
        agent.context_compressor = None

        agent.reset_session_state()

        assert agent._user_turn_count == 0, (
            f"_user_turn_count must be 0 after reset; got: {agent._user_turn_count}"
        )

    def test_both_fields_cleared_together(self):
        """Both stale fields are cleared in a single reset_session_state() call."""
        agent = _make_minimal_agent()
        agent._user_turn_count = 3

        compressor = ContextCompressor.__new__(ContextCompressor)
        compressor._previous_summary = "Stale summary"
        compressor.last_prompt_tokens = 0
        compressor.last_completion_tokens = 0
        compressor.last_total_tokens = 0
        compressor.compression_count = 0
        compressor._context_probed = False
        agent.context_compressor = compressor

        agent.reset_session_state()

        assert agent._user_turn_count == 0
        assert compressor._previous_summary is None

    def test_reset_without_compressor_does_not_raise(self):
        """reset_session_state() must not raise when context_compressor is None."""
        agent = _make_minimal_agent()
        agent._user_turn_count = 2
        agent.context_compressor = None

        # Must not raise
        agent.reset_session_state()

        assert agent._user_turn_count == 0