forked from Rockachopa/Timmy-time-dashboard
feat: add thought_search tool for querying Timmy's thinking history (#260)
Co-authored-by: Kimi Agent <kimi@timmy.local> Co-committed-by: Kimi Agent <kimi@timmy.local>
This commit is contained in:
@@ -833,6 +833,115 @@ def test_thinking_chain_api_404(client):
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Thought search
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_search_thoughts_basic(tmp_path):
|
||||
"""search_thoughts should find thoughts by content substring."""
|
||||
from timmy import thinking
|
||||
|
||||
engine = _make_engine(tmp_path)
|
||||
engine._store_thought("I wonder about sovereignty and freedom.", "existential")
|
||||
engine._store_thought("The swarm is performing well today.", "swarm")
|
||||
engine._store_thought("True sovereignty comes from local execution.", "sovereignty")
|
||||
|
||||
with patch.object(thinking, "thinking_engine", engine):
|
||||
result = thinking.search_thoughts("sovereignty")
|
||||
assert "Found 2 thought(s)" in result
|
||||
assert "sovereignty" in result.lower()
|
||||
|
||||
|
||||
def test_search_thoughts_with_seed_type(tmp_path):
|
||||
"""search_thoughts should filter by seed_type when provided."""
|
||||
from timmy import thinking
|
||||
|
||||
engine = _make_engine(tmp_path)
|
||||
engine._store_thought("I wonder about sovereignty and freedom.", "existential")
|
||||
engine._store_thought("True sovereignty comes from local execution.", "sovereignty")
|
||||
|
||||
with patch.object(thinking, "thinking_engine", engine):
|
||||
result = thinking.search_thoughts("sovereignty", seed_type="sovereignty")
|
||||
assert "Found 1 thought(s)" in result
|
||||
assert '[seed_type="sovereignty"]' in result
|
||||
|
||||
|
||||
def test_search_thoughts_no_matches(tmp_path):
|
||||
"""search_thoughts should return helpful message when no matches found."""
|
||||
from timmy import thinking
|
||||
|
||||
engine = _make_engine(tmp_path)
|
||||
engine._store_thought("A thought about memory.", "memory")
|
||||
|
||||
with patch.object(thinking, "thinking_engine", engine):
|
||||
result = thinking.search_thoughts("xyz_nonexistent")
|
||||
assert "No thoughts found" in result
|
||||
|
||||
|
||||
def test_search_thoughts_limit(tmp_path):
|
||||
"""search_thoughts should respect the limit parameter."""
|
||||
from timmy import thinking
|
||||
|
||||
engine = _make_engine(tmp_path)
|
||||
for i in range(5):
|
||||
engine._store_thought(f"Sovereignty thought number {i}.", "sovereignty")
|
||||
|
||||
with patch.object(thinking, "thinking_engine", engine):
|
||||
result = thinking.search_thoughts("sovereignty", limit=3)
|
||||
assert "Found 3 thought(s)" in result
|
||||
|
||||
|
||||
def test_search_thoughts_limit_bounds(tmp_path):
|
||||
"""search_thoughts should clamp limit to valid bounds."""
|
||||
from timmy import thinking
|
||||
|
||||
engine = _make_engine(tmp_path)
|
||||
engine._store_thought("A test thought.", "freeform")
|
||||
|
||||
with patch.object(thinking, "thinking_engine", engine):
|
||||
# These should not raise errors - just clamp internally
|
||||
result_low = thinking.search_thoughts("test", limit=0)
|
||||
result_high = thinking.search_thoughts("test", limit=100)
|
||||
# Both should execute (may return no results, but shouldn't crash)
|
||||
assert isinstance(result_low, str)
|
||||
assert isinstance(result_high, str)
|
||||
|
||||
|
||||
def test_search_thoughts_case_insensitive(tmp_path):
|
||||
"""search_thoughts should be case-insensitive (SQLite LIKE is case-insensitive)."""
|
||||
from timmy import thinking
|
||||
|
||||
engine = _make_engine(tmp_path)
|
||||
engine._store_thought("The SWARM is active today.", "swarm")
|
||||
|
||||
with patch.object(thinking, "thinking_engine", engine):
|
||||
result_lower = thinking.search_thoughts("swarm")
|
||||
result_upper = thinking.search_thoughts("SWARM")
|
||||
result_mixed = thinking.search_thoughts("Swarm")
|
||||
|
||||
assert "Found 1 thought(s)" in result_lower
|
||||
assert "Found 1 thought(s)" in result_upper
|
||||
assert "Found 1 thought(s)" in result_mixed
|
||||
|
||||
|
||||
def test_search_thoughts_returns_formatted_output(tmp_path):
|
||||
"""search_thoughts should return formatted output with timestamps and seed types."""
|
||||
from timmy import thinking
|
||||
|
||||
engine = _make_engine(tmp_path)
|
||||
engine._store_thought("A memorable thought about existence.", "existential")
|
||||
|
||||
with patch.object(thinking, "thinking_engine", engine):
|
||||
result = thinking.search_thoughts("memorable")
|
||||
# Should contain timestamp-like content (year in 2026)
|
||||
assert "2026-" in result or "2025-" in result
|
||||
# Should contain seed type
|
||||
assert "existential" in result
|
||||
# Should contain the thought content
|
||||
assert "memorable thought" in result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _call_agent uses skip_mcp=True (#72)
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -884,8 +993,7 @@ async def test_call_agent_strips_think_tags(tmp_path):
|
||||
mock_agent = AsyncMock()
|
||||
mock_run = AsyncMock()
|
||||
mock_run.content = (
|
||||
"<think>Let me reason about this carefully...</think>"
|
||||
"The actual thought content."
|
||||
"<think>Let me reason about this carefully...</think>The actual thought content."
|
||||
)
|
||||
mock_agent.arun.return_value = mock_run
|
||||
|
||||
@@ -903,10 +1011,7 @@ async def test_call_agent_strips_multiline_think_tags(tmp_path):
|
||||
|
||||
mock_agent = AsyncMock()
|
||||
mock_run = AsyncMock()
|
||||
mock_run.content = (
|
||||
"<think>\nStep 1: analyze\nStep 2: synthesize\n</think>\n"
|
||||
"Clean output here."
|
||||
)
|
||||
mock_run.content = "<think>\nStep 1: analyze\nStep 2: synthesize\n</think>\nClean output here."
|
||||
mock_agent.arun.return_value = mock_run
|
||||
|
||||
with patch("timmy.agent.create_timmy", return_value=mock_agent):
|
||||
|
||||
Reference in New Issue
Block a user