# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) 2026 sol pbc
from __future__ import annotations
import asyncio
import threading
from flask import Flask
from solstone.think.voice import brain
from solstone.think.voice.runtime import start_voice_runtime, stop_voice_runtime
def test_extract_instruction():
text = "beforeHello thereafter"
assert brain.extract_instruction(text) == "Hello there"
assert brain.extract_instruction("no tags here") is None
def test_build_init_prompt_uses_agent_name(monkeypatch):
monkeypatch.setattr(brain, "get_config", lambda: {"agent": {"name": "Astra"}})
prompt = brain._build_init_prompt()
assert "voice-session instruction" in prompt
assert "for Astra" in prompt
def test_build_refresh_prompt_uses_agent_name(monkeypatch):
monkeypatch.setattr(brain, "get_config", lambda: {"agent": {"name": "Astra"}})
prompt = brain._build_refresh_prompt()
assert "for Astra" in prompt
def test_start_brain_persists_session(monkeypatch, journal_copy):
async def fake_run_claude(message, extra_args, *, timeout):
assert "voice-session instruction" in message
assert extra_args == ["-n", "voice-brain"]
assert timeout == 300
return "Speak clearly", "session-1"
monkeypatch.setattr(brain, "_run_claude", fake_run_claude)
session_id, instruction = asyncio.run(brain.start_brain())
assert session_id == "session-1"
assert instruction == "Speak clearly"
assert (journal_copy / "health" / "voice-brain-session").read_text(
encoding="utf-8"
) == "session-1"
def test_refresh_brain_touches_session_file(monkeypatch, journal_copy):
session_file = journal_copy / "health" / "voice-brain-session"
session_file.parent.mkdir(parents=True, exist_ok=True)
session_file.write_text("session-1", encoding="utf-8")
async def fake_run_claude(message, extra_args, *, timeout):
assert "for sol" in message
assert extra_args == ["--resume", "session-1"]
assert timeout == 300
return "Fresh voice", "session-1"
monkeypatch.setattr(brain, "_run_claude", fake_run_claude)
before = session_file.stat().st_mtime
instruction = asyncio.run(brain.refresh_brain("session-1"))
assert instruction == "Fresh voice"
assert session_file.stat().st_mtime >= before
def test_ask_brain_uses_resume(monkeypatch):
async def fake_run_claude(message, extra_args, *, timeout):
assert message == "What changed?"
assert extra_args == ["--resume", "session-1"]
assert timeout == 120
return "Short answer", "session-1"
monkeypatch.setattr(brain, "_run_claude", fake_run_claude)
assert asyncio.run(brain.ask_brain("session-1", "What changed?")) == "Short answer"
def test_schedule_start_and_wait_until_ready(monkeypatch, journal_copy):
brain.clear_brain_state()
app = Flask(__name__)
async def fake_run_claude(message, extra_args, *, timeout):
return "Ready voice", "session-2"
monkeypatch.setattr(brain, "_run_claude", fake_run_claude)
start_voice_runtime(app)
try:
assert brain.wait_until_ready(app, 1.0) is True
assert app.voice_brain_session == "session-2"
assert app.voice_brain_instruction == "Ready voice"
assert isinstance(brain.brain_age_seconds(app), int)
finally:
stop_voice_runtime(app)
brain.clear_brain_state()
def test_schedule_refresh_updates_instruction(monkeypatch, journal_copy):
brain.clear_brain_state()
app = Flask(__name__)
app.voice_brain_session = "session-3"
app.voice_brain_instruction = "Old voice"
app.voice_brain_refreshed_at = None
(journal_copy / "health").mkdir(parents=True, exist_ok=True)
(journal_copy / "health" / "voice-brain-session").write_text(
"session-3", encoding="utf-8"
)
async def fake_run_claude(message, extra_args, *, timeout):
return "New voice", "session-3"
monkeypatch.setattr(brain, "_run_claude", fake_run_claude)
refresh_applied = threading.Event()
original_complete = brain._complete_future
def complete_and_signal(app_arg, attr_name, future):
original_complete(app_arg, attr_name, future)
if attr_name == "refresh_future":
refresh_applied.set()
monkeypatch.setattr(brain, "_complete_future", complete_and_signal)
start_voice_runtime(app)
try:
future = brain.schedule_refresh(app, force=True)
assert future.result(timeout=1.0) == ("session-3", "New voice")
assert refresh_applied.wait(timeout=2.0)
assert app.voice_brain_instruction == "New voice"
finally:
stop_voice_runtime(app)
brain.clear_brain_state()
def test_brain_session_file_stays_on_bound_journal(monkeypatch, tmp_path):
brain.clear_brain_state()
initial_journal = tmp_path / "initial-journal"
later_journal = tmp_path / "later-journal"
monkeypatch.setenv("SOLSTONE_JOURNAL", str(initial_journal))
app = Flask(__name__)
async def fake_run_claude(message, extra_args, *, timeout):
await asyncio.sleep(0.01)
return "Ready voice", "session-4"
monkeypatch.setattr(brain, "_run_claude", fake_run_claude)
start_voice_runtime(app)
try:
monkeypatch.setenv("SOLSTONE_JOURNAL", str(later_journal))
assert brain.wait_until_ready(app, 1.0) is True
assert (initial_journal / "health" / "voice-brain-session").read_text(
encoding="utf-8"
) == "session-4"
assert not (later_journal / "health" / "voice-brain-session").exists()
finally:
stop_voice_runtime(app)
brain.clear_brain_state()