Bolt Assistant: wake-phrase voice assistant for Bolt

This commit is contained in:
2026-09-26 22:59:33 -06:00
commit 8bcd539b31
23 changed files with 2726 additions and 0 deletions
View File
+359
View File
@@ -0,0 +1,359 @@
"""Bolt Assistant without a mic, speaker or server: synthetic audio frames,
a fake transcriber, fake converse/tts/playback. Covers wake matching, the
voice-activity detector, the listen -> ask -> speak flow, and the command
rules."""
import array
import json
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
import pytest
from bolt_assistant import config, server
from bolt_assistant.assistant import Assistant, annotate
from bolt_assistant.listener import Segmenter
from bolt_assistant.stt import Transcription, parse_result
from bolt_assistant.tools import Tools
from bolt_assistant.wake import find_wake
LOUD, QUIET = 3000, 20
def frame(level, samples=1280):
return array.array("h", [level, -level] * (samples // 2)).tobytes()
# ── wake phrases ────────────────────────────────────────────────────────────
W = config.WAKE_WORDS
@pytest.mark.parametrize("said, expected", [
("Hey Bolt, what's the weather?", (True, "what's the weather?")),
("Bolt.", (True, "")),
("Thunderbolt turn on the lights", (True, "turn on the lights")),
("hey thunderbolt!", (True, "")),
("um, hey Bolt what time is it", (True, "what time is it")),
("Hey assistant. Set a timer", (True, "Set a timer")),
("Hey volt, play music", (True, "play music")), # a common mishearing, after "hey"
("okay so bolt, hi", (True, "hi")),
("tighten that bolt", (False, "")),
("the bolt on the door", (False, "")),
("I was bold", (False, "")), # the mishearing alone isn't a wake word
("assistant set a timer", (False, "")), # "assistant" needs the "hey"
("boltage is high", (False, "")),
("", (False, "")),
])
def test_find_wake(said, expected):
assert find_wake(said, W) == expected
# ── voice-activity detection ────────────────────────────────────────────────
def seg(**kw):
args = dict(threshold=500, start_frames=3, silence_end_sec=0.4, min_sec=0.3, max_sec=2.0)
args.update(kw)
return Segmenter(**args)
def feed(s, frames):
events = []
for f in frames:
events += s.feed(f)
return events
def test_silence_never_starts_an_utterance():
assert feed(seg(), [frame(QUIET)] * 50) == []
def test_speech_starts_with_pre_roll_and_ends_after_silence():
s = seg()
events = feed(s, [frame(QUIET)] * 6 + [frame(LOUD)] * 6 + [frame(QUIET)] * 5)
kinds = [e.kind for e in events]
assert kinds[0] == "start" and kinds[-1] == "end"
start = events[0]
assert len(start.frames) == 4 + 3 # pre-roll kept the first syllable, plus the trigger
end = events[-1]
assert len(end.audio) == (4 + 6 + 5) * 2560 # pre-roll + speech + trailing silence
def test_a_click_is_not_speech_and_a_short_burst_is_dropped():
s = seg(min_sec=0.5) # needs 6+ loud frames
assert feed(s, [frame(LOUD), frame(QUIET)] * 5) == [] # isolated clicks
events = feed(s, [frame(LOUD)] * 3 + [frame(QUIET)] * 5)
assert [e.kind for e in events if e.kind != "frame"] == ["start", "dropped"]
def test_long_speech_is_cut_off_at_the_cap():
events = feed(seg(max_sec=1.0), [frame(LOUD)] * 30)
ends = [e for e in events if e.kind == "end"]
assert ends and ends[0].seconds <= 1.0 + 1e-6
# ── the flow ────────────────────────────────────────────────────────────────
class FakeTranscription:
"""Each utterance returns the next scripted transcript."""
script = []
def __init__(self):
self.frames = 0
self.cancelled = False
def add(self, frames):
self.frames += len(frames)
def finish(self, audio):
return FakeTranscription.script.pop(0)
def cancel(self):
self.cancelled = True
class Clock:
def __init__(self):
self.t = 1000.0
def __call__(self):
return self.t
def utterance():
return [frame(LOUD)] * 8 + [frame(QUIET)] * 12
def make(replies, commands="read", streaming=False, **kw):
asked, played, logs = [], [], []
def converse(message, on_command, on_say=None):
asked.append(message)
reply = replies.pop(0)
if callable(reply):
return reply(on_command)
return server.Reply(reply)
clock = Clock()
a = Assistant(transcription=FakeTranscription, converse=converse,
converse_stream=None if not streaming else kw.pop("stream"),
tts=lambda text, voice: [text.encode()], play=lambda chunks: played.extend(chunks),
tools=Tools(mode=commands, log=logs.append), log=logs.append, clock=clock, **kw)
return a, asked, played, logs, clock
def say(a, transcript):
FakeTranscription.script.append(transcript)
for f in utterance():
a.process(f)
def spoken(played):
return [p.decode() for p in played if not p.startswith(b"\x00") and len(p) < 2000]
@pytest.fixture(autouse=True)
def _config(monkeypatch):
monkeypatch.setattr(config, "STREAMING_REPLIES", False)
monkeypatch.setattr(config, "VAD_RMS_THRESHOLD", 500)
FakeTranscription.script = []
def test_ignores_speech_without_the_wake_phrase():
a, asked, played, logs, _ = make([])
say(a, "so anyway I was telling him about the bolt")
assert asked == [] and played == []
def test_wake_phrase_and_request_in_one_breath():
a, asked, played, logs, _ = make(["It's three o'clock."])
say(a, "Hey Bolt, what time is it?")
assert asked[0].startswith("what time is it?\n\n[Bolt Assistant: a voice-only assistant")
assert "It's three o'clock." in spoken(played)
assert a.state == "idle"
def test_bare_wake_phrase_chimes_then_takes_the_next_utterance():
a, asked, played, logs, clock = make(["Sure."])
say(a, "Hey Bolt")
assert a.state == "command" and asked == []
assert played # the chime
clock.t += 1
say(a, "turn it up") # no wake phrase needed now
assert asked[0].startswith("turn it up")
def test_the_command_window_closes():
a, asked, played, logs, clock = make([])
say(a, "Thunderbolt")
clock.t += config.COMMAND_WAIT_SEC + 5
say(a, "so then we left")
assert asked == [] and a.state == "idle"
def test_a_question_keeps_listening_for_the_answer():
a, asked, played, logs, clock = make(["Which room, the kitchen or the bedroom?", "Done."])
say(a, "Hey Bolt, turn on the lights")
assert a.state == "follow_up"
clock.t += 2
say(a, "the kitchen")
assert asked[1].startswith("the kitchen") and a.state == "idle"
def test_mic_audio_during_speech_is_discarded():
a, asked, played, logs, clock = make(["Hello there."])
a.on_mic_frame(frame(LOUD)) # queued while... (simulating backlog)
say(a, "hey bolt hello")
assert a.frames.empty() # what the mic heard while Bolt talked is gone
assert not a.segmenter.in_speech
def test_streamed_sentences_are_spoken_as_they_arrive():
def stream(message, on_say, on_command):
on_say("First.")
on_say("Second.")
return server.Reply("First. Second.", streamed=True)
a, asked, played, logs, _ = make([], streaming=True, stream=stream)
a._stream = stream
say(a, "hey bolt tell me two things")
assert spoken(played)[-2:] == ["First.", "Second."]
def test_server_failure_chimes_and_goes_back_to_listening():
def boom(on_command):
raise server.ServerError("down")
a, asked, played, logs, _ = make([boom])
say(a, "hey bolt anything")
assert any("down" in line for line in logs) and a.state == "idle"
def test_markdown_is_not_read_aloud():
a, asked, played, logs, _ = make(["**Sure** — see https://example.com for `details`."])
say(a, "hey bolt help")
text = spoken(played)[-1]
assert "**" not in text and "https" not in text and "`" not in text
def test_the_note_describes_what_bolt_may_do():
assert "can't run shell commands" in annotate("x", "read")
assert "`command: <shell command>`" in annotate("x", "all")
assert "can't act on this computer" in annotate("x", "off")
# ── commands relayed by the server ──────────────────────────────────────────
def filectl(**payload):
return "filectl " + json.dumps(payload)
def test_read_mode_lists_files_but_refuses_shell_and_writes(tmp_path):
(tmp_path / "a.txt").write_text("hi")
ran = []
tools = Tools(mode="read", log=lambda _m: None, shell=lambda c: ran.append(c) or "[exit 0]\n")
assert "a.txt" in tools(filectl(op="list", path=str(tmp_path)))
assert "hi" in tools(filectl(op="read", path=str(tmp_path / "a.txt")))
assert "Not run" in tools("rm -rf /")
assert "Not run" in tools(filectl(op="write", path=str(tmp_path / "b.txt"), content="x"))
assert ran == [] and not (tmp_path / "b.txt").exists()
def test_all_mode_runs_commands_and_off_runs_nothing(tmp_path):
ran = []
shell = lambda c: ran.append(c) or "[exit 0]\n" # noqa: E731
assert Tools(mode="all", log=lambda _m: None, shell=shell)("command: echo hi") == "[exit 0]\n"
assert ran == ["echo hi"] # the stray "command:" label was stripped
assert "Not run" in Tools(mode="off", log=lambda _m: None, shell=shell)(filectl(op="list", path="."))
assert "isn't available" in Tools(mode="all", log=lambda _m: None)("petctl emote wave")
def test_unknown_mode_falls_back_to_read():
assert Tools(mode="everything", log=lambda _m: None).mode == "read"
def test_a_relayed_command_goes_through_the_rules(tmp_path):
(tmp_path / "notes.txt").write_text("buy milk")
def reply_with_tool(on_command):
output = on_command(filectl(op="read", path=str(tmp_path / "notes.txt")))
assert "buy milk" in output
return server.Reply("Your note says buy milk.")
a, asked, played, logs, _ = make([reply_with_tool])
say(a, "hey bolt read my notes")
assert "Your note says buy milk." in spoken(played)
# ── server pieces ───────────────────────────────────────────────────────────
def test_whole_samples_carries_an_odd_byte_across_chunks():
out = b"".join(server.whole_samples([b"\x01", b"\x02\x03", b"\x04\x05"]))
assert out == b"\x01\x02\x03\x04" # the lone trailing byte waits for its partner
def test_unsaid_drops_what_was_already_spoken():
reply = server.unsaid(server.Reply("One. Two.\n\nThree."), ["One.", "Two."])
assert reply == server.Reply("Three.", streamed=False)
def test_tts_needs_a_voice(monkeypatch):
monkeypatch.setattr(config, "VOICE_ID", "")
with pytest.raises(server.TtsError, match="ELEVENLABS_VOICE_ID"):
server.tts_stream("hi")
def test_tts_request_shape(monkeypatch):
monkeypatch.setattr(config, "SERVER_URL", "http://s")
monkeypatch.setattr(config, "API_KEY", "k")
monkeypatch.setattr(config, "VOICE_ID", "v1")
response = MagicMock()
response.iter_content.return_value = [b"\x00\x01"]
with patch.object(server.requests, "post", return_value=response) as post:
assert list(server.tts_stream("hello", voice_id="v2")) == [b"\x00\x01"]
assert post.call_args.args[0] == "http://s/desk/tts"
assert post.call_args.kwargs["json"]["voice_id"] == "v2" # a speak_as pick wins
assert post.call_args.kwargs["headers"] == {"X-Desk-Api-Key": "k"}
def test_parse_result():
msg = json.dumps({"type": "Results", "is_final": True, "channel": {"alternatives": [{"transcript": "hey bolt"}]}})
assert parse_result(msg) == ("hey bolt", True)
assert parse_result("junk") == ("", False)
def test_transcription_falls_back_to_one_upload():
class Sock:
def __init__(self):
self.audio, self.replies = [], [json.dumps({"is_final": True, "channel": {
"alternatives": [{"transcript": "hey bolt"}]}}), ""]
def send_binary(self, data):
self.audio.append(data)
def send(self, _text):
pass
def recv(self):
return self.replies.pop(0)
def close(self):
pass
late = Sock()
calls = iter([ConnectionError("down"), late])
def connect(_rate):
item = next(calls)
if isinstance(item, Exception):
raise item
return item
t = Transcription(connect)
t.add([frame(LOUD)]) # nowhere to go: the live socket never opened
assert t.finish(frame(LOUD) * 2) == "hey bolt"
assert b"".join(late.audio) == frame(LOUD) * 2
+343
View File
@@ -0,0 +1,343 @@
"""filectl parsing + execution — the pseudo-commands the server can relay to
read/write/edit local files instead of a raw shell heredoc.
filectl {"op": ...} is a single-line JSON envelope (not a multi-line
marker block) because it rides the "command" tool marker, which the main
repo's tool-call extractor only captures up to the next newline — see the
module docstring in bolt_assistant/file_ops.py."""
import json
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from bolt_assistant import file_ops
def _cmd(payload: dict) -> str:
return "filectl " + json.dumps(payload)
# ── parsing ──────────────────────────────────────────────────────────────────
def test_non_file_commands_are_left_alone():
assert file_ops.parse("ls -la") is None
assert file_ops.parse("systemctl restart nginx") is None
assert file_ops.parse("") is None
# "filed" must not be mistaken for the "file" prefix
assert file_ops.parse("filed --list") is None
def test_list_parses_defaults():
assert file_ops.parse(_cmd({"op": "list", "path": "/tmp"})) == {
"action": "list", "path": "/tmp", "pattern": "*", "recursive": False,
}
def test_list_parses_pattern_and_recursive():
assert file_ops.parse(_cmd({"op": "list", "path": "/tmp", "pattern": "*.py", "recursive": True})) == {
"action": "list", "path": "/tmp", "pattern": "*.py", "recursive": True,
}
def test_list_requires_a_path():
with pytest.raises(file_ops.FileOpError):
file_ops.parse(_cmd({"op": "list"}))
def test_list_rejects_empty_pattern():
with pytest.raises(file_ops.FileOpError):
file_ops.parse(_cmd({"op": "list", "path": "/tmp", "pattern": ""}))
def test_read_parses_path_and_optional_line_range():
assert file_ops.parse(_cmd({"op": "read", "path": "/tmp/a.txt"})) == {
"action": "read", "path": "/tmp/a.txt", "start": None, "end": None,
}
assert file_ops.parse(_cmd({"op": "read", "path": "/tmp/a.txt", "start": 10, "end": 40})) == {
"action": "read", "path": "/tmp/a.txt", "start": 10, "end": 40,
}
def test_read_requires_a_path():
with pytest.raises(file_ops.FileOpError):
file_ops.parse(_cmd({"op": "read"}))
def test_read_rejects_non_numeric_line_args():
with pytest.raises(file_ops.FileOpError):
file_ops.parse('filectl {"op": "read", "path": "/tmp/a.txt", "start": "start"}')
def test_write_parses_path_and_content():
assert file_ops.parse(_cmd({"op": "write", "path": "/tmp/a.txt", "content": "hello\nworld"})) == {
"action": "write", "path": "/tmp/a.txt", "content": "hello\nworld",
}
def test_write_allows_empty_content():
assert file_ops.parse(_cmd({"op": "write", "path": "/tmp/a.txt", "content": ""})) == {
"action": "write", "path": "/tmp/a.txt", "content": "",
}
def test_write_without_content_raises():
with pytest.raises(file_ops.FileOpError):
file_ops.parse(_cmd({"op": "write", "path": "/tmp/a.txt"}))
def test_edit_parses_old_and_new():
assert file_ops.parse(_cmd({"op": "edit", "path": "/tmp/a.txt", "old": "foo\nbar", "new": "baz"})) == {
"action": "edit", "path": "/tmp/a.txt", "old": "foo\nbar", "new": "baz",
}
def test_edit_rejects_identical_old_and_new():
with pytest.raises(file_ops.FileOpError):
file_ops.parse(_cmd({"op": "edit", "path": "/tmp/a.txt", "old": "same", "new": "same"}))
def test_edit_missing_fields_raises():
with pytest.raises(file_ops.FileOpError):
file_ops.parse(_cmd({"op": "edit", "path": "/tmp/a.txt"}))
def test_content_with_embedded_quotes_and_shell_metacharacters_survives():
payload = 'echo "hi $USER" `whoami` && rm -rf /'
command = _cmd({"op": "write", "path": "/tmp/a.txt", "content": payload})
assert "\n" not in command # stays on one line, as the command marker requires
assert file_ops.parse(command) == {"action": "write", "path": "/tmp/a.txt", "content": payload}
def test_multiline_content_stays_on_one_physical_line():
content = "line one\nline two\nline three with \"quotes\" and \\backslashes\\"
command = _cmd({"op": "write", "path": "/tmp/a.txt", "content": content})
assert "\n" not in command
assert file_ops.parse(command)["content"] == content
def test_help():
assert file_ops.parse("filectl help") == {"action": "help"}
assert file_ops.parse("filectl") == {"action": "help"}
assert file_ops.parse(_cmd({"op": "help"})) == {"action": "help"}
def test_invalid_json_raises():
with pytest.raises(file_ops.FileOpError):
file_ops.parse("filectl {not valid json")
def test_non_object_json_raises():
with pytest.raises(file_ops.FileOpError):
file_ops.parse("filectl [1, 2, 3]")
def test_unknown_op_raises():
with pytest.raises(file_ops.FileOpError):
file_ops.parse(_cmd({"op": "frobnicate", "path": "/tmp/a.txt"}))
# ── execution ────────────────────────────────────────────────────────────────
def test_list_shows_files_and_subdirectories(tmp_path):
(tmp_path / "a.txt").write_text("hi")
(tmp_path / "sub").mkdir()
(tmp_path / "sub" / "b.txt").write_text("nested")
action = file_ops.parse(_cmd({"op": "list", "path": str(tmp_path)}))
output = file_ops.execute(action)
assert "a.txt\t2B" in output
assert "sub/" in output
assert "b.txt" not in output # non-recursive: nested file not shown
def test_list_recursive_finds_nested_files(tmp_path):
(tmp_path / "sub").mkdir()
(tmp_path / "sub" / "b.txt").write_text("nested")
action = file_ops.parse(_cmd({"op": "list", "path": str(tmp_path), "recursive": True}))
output = file_ops.execute(action)
assert "sub/b.txt" in output or "sub\\b.txt" in output # os-dependent separator
def test_list_pattern_filters_entries(tmp_path):
(tmp_path / "a.py").write_text("x")
(tmp_path / "b.txt").write_text("y")
action = file_ops.parse(_cmd({"op": "list", "path": str(tmp_path), "pattern": "*.py"}))
output = file_ops.execute(action)
assert "a.py" in output
assert "b.txt" not in output
def test_list_empty_directory_says_so(tmp_path):
action = file_ops.parse(_cmd({"op": "list", "path": str(tmp_path)}))
output = file_ops.execute(action)
assert "no entries" in output
def test_list_missing_directory_raises():
with pytest.raises(file_ops.FileOpError):
file_ops.execute({"action": "list", "path": "/no/such/dir", "pattern": "*", "recursive": False})
def test_list_rejects_a_file_path(tmp_path):
target = tmp_path / "a.txt"
target.write_text("hi")
with pytest.raises(file_ops.FileOpError):
file_ops.execute({"action": "list", "path": str(target), "pattern": "*", "recursive": False})
def test_list_truncates_past_the_entry_cap(tmp_path, monkeypatch):
monkeypatch.setattr(file_ops, "_MAX_LIST_ENTRIES", 3)
for i in range(5):
(tmp_path / f"f{i}.txt").write_text("x")
action = file_ops.parse(_cmd({"op": "list", "path": str(tmp_path)}))
output = file_ops.execute(action)
assert "truncated" in output
assert output.count(".txt") == 3
def test_write_then_read_round_trips(tmp_path):
target = tmp_path / "notes.txt"
write_action = file_ops.parse(_cmd({"op": "write", "path": str(target), "content": "line one\nline two"}))
result = file_ops.execute(write_action)
assert target.read_text() == "line one\nline two"
assert str(target) in result
read_action = file_ops.parse(_cmd({"op": "read", "path": str(target)}))
output = file_ops.execute(read_action)
assert "line one" in output
assert "line two" in output
def test_read_missing_file_raises():
with pytest.raises(file_ops.FileOpError):
file_ops.execute({"action": "read", "path": "/no/such/file.txt", "start": None, "end": None})
def test_read_respects_line_range(tmp_path):
target = tmp_path / "a.txt"
target.write_text("\n".join(f"line{i}" for i in range(1, 11)))
action = file_ops.parse(_cmd({"op": "read", "path": str(target), "start": 3, "end": 5}))
output = file_ops.execute(action)
assert "line3" in output and "line5" in output
assert "line1" not in output and "line6" not in output
def test_edit_replaces_a_unique_match(tmp_path):
target = tmp_path / "a.py"
target.write_text("def foo():\n return 1\n")
action = file_ops.parse(_cmd({"op": "edit", "path": str(target), "old": "return 1", "new": "return 2"}))
file_ops.execute(action)
assert target.read_text() == "def foo():\n return 2\n"
def test_edit_fails_when_text_not_found(tmp_path):
target = tmp_path / "a.py"
target.write_text("def foo():\n return 1\n")
action = file_ops.parse(_cmd({"op": "edit", "path": str(target), "old": "nope", "new": "x"}))
with pytest.raises(file_ops.FileOpError):
file_ops.execute(action)
assert target.read_text() == "def foo():\n return 1\n" # untouched
def test_edit_fails_when_text_is_ambiguous(tmp_path):
target = tmp_path / "a.py"
target.write_text("x = 1\nx = 1\n")
action = file_ops.parse(_cmd({"op": "edit", "path": str(target), "old": "x = 1", "new": "x = 2"}))
with pytest.raises(file_ops.FileOpError):
file_ops.execute(action)
assert target.read_text() == "x = 1\nx = 1\n" # untouched
def test_write_creates_parent_directories(tmp_path):
target = tmp_path / "nested" / "dir" / "a.txt"
action = file_ops.parse(_cmd({"op": "write", "path": str(target), "content": "hi"}))
file_ops.execute(action)
assert target.read_text() == "hi"
# ── lenient JSON (see relay_json) ───────────────────────────────────────────
# Machine-written JSON fails in a small, repeatable set of ways. Observed live
# 2026-07-30: a stray quote after `false` cost a whole desk turn — rejected,
# re-sent identically, rejected again, then abandoned with a promise to the
# user that nothing fulfilled.
def test_the_stray_quote_that_cost_a_live_turn_now_parses():
action = file_ops.parse(
'filectl {"op":"list","path":"/home/maji/Documents","pattern":"*","recursive":false"}'
)
assert action["action"] == "list"
assert action["path"] == "/home/maji/Documents"
assert action["recursive"] is False
assert action["_repairs"] == ["removed a stray quote after a bare value"]
@pytest.mark.parametrize("payload,expected", [
('{"op": "read", "path": "/tmp/a.txt",}', "removed a trailing comma"),
("{'op': 'read', 'path': '/tmp/a.txt'}", "converted single-quoted strings to double-quoted"),
('{“op”: “read”, “path”: “/tmp/a.txt”}', "replaced smart quotes with straight ones"),
('```json {"op": "read", "path": "/tmp/a.txt"} ```', "stripped a markdown code fence"),
])
def test_common_model_json_mistakes_are_repaired(payload, expected):
action = file_ops.parse("filectl " + payload)
assert action["action"] == "read"
assert action["path"] == "/tmp/a.txt"
assert expected in action["_repairs"]
def test_python_literals_are_converted():
action = file_ops.parse('filectl {"op": "list", "path": "/tmp", "recursive": True}')
assert action["recursive"] is True
def test_a_repair_is_reported_in_the_output_never_hidden(tmp_path):
"""Silently fixing it would work today and guarantee the same broken call
tomorrow — the model has to be told while it still has the turn."""
(tmp_path / "a.txt").write_text("hello", encoding="utf-8")
action = file_ops.parse(
'filectl {"op":"list","path":%s,"recursive":false"}' % json.dumps(str(tmp_path))
)
output = file_ops.execute(action)
assert "a.txt" in output
assert "your JSON was malformed" in output
assert "stray quote" in output
def test_valid_json_gets_no_repair_note(tmp_path):
(tmp_path / "a.txt").write_text("hello", encoding="utf-8")
action = file_ops.parse('filectl {"op": "list", "path": %s}' % json.dumps(str(tmp_path)))
assert "_repairs" not in action
assert "malformed" not in file_ops.execute(action)
def test_genuinely_unparseable_json_points_at_the_character():
""""Expecting ',' delimiter: char 74" is not something a model can act on."""
with pytest.raises(file_ops.FileOpError) as excinfo:
file_ops.parse('filectl {"op": "read", "path": "/tmp/a.txt" "extra": 1}')
message = str(excinfo.value)
assert "^" in message # caret under the offending character
assert '"extra"' in message # ...and the fragment around it
def test_a_raw_windows_path_is_repaired_and_reported():
r"""Models type Windows paths with single backslashes. `\U` isn't a JSON
escape, so strict parsing would reject the whole call."""
action = file_ops.parse(r'filectl {"op": "read", "path": "C:\Users\sam\data.txt"}')
assert action["path"] == r"C:\Users\sam\data.txt"
assert any("backslash" in repair for repair in action["_repairs"])
def test_the_backslash_repair_leaves_valid_escapes_alone():
# The escaped pair and the valid \n survive; only the lone \U is doubled.
action = file_ops.parse(r'filectl {"op": "read", "path": "C:\\ok\Users\n.txt"}')
assert action["path"] == "C:\\ok\\Users\n.txt"
+111
View File
@@ -0,0 +1,111 @@
"""Sanitizing chat-formatted replies into speakable prose. Pure string logic
— no audio hardware, no Qt (see the testing conventions in CLAUDE.md)."""
import sys
from pathlib import Path
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from bolt_assistant.speech_text import for_display, for_speech, is_question
def test_bold_markers_are_not_spoken():
assert "*" not in for_speech("Here's the **maji-desktop** snapshot")
assert for_speech("Here's the **maji-desktop** snapshot") == "Here's the maji-desktop snapshot"
def test_italics_and_underscores_dropped():
assert for_speech("that is _really_ odd") == "that is really odd"
assert for_speech("***everything*** is fine") == "everything is fine"
def test_bullet_list_becomes_sentences():
spoken = for_speech(
"System status:\n"
"* **OS:** Linux 7.0.0\n"
"* **Uptime:** 1 day\n"
)
assert "*" not in spoken
assert spoken == "System status: OS: Linux 7.0.0. Uptime: 1 day."
def test_emoji_and_symbols_removed():
assert for_speech("✅ Done 🚀 — all good") == "Done all good"
assert for_speech("→ next step") == "to next step"
def test_urls_and_code_are_not_read_out_character_by_character():
assert for_speech("see https://example.com/x?y=1 for docs") == "see link for docs"
assert for_speech("run `sudo reboot` now") == "run sudo reboot now"
assert for_speech("here:\n```\nls -la\n```\n") == "here: (code)."
def test_headings_quotes_and_rules_stripped():
assert for_speech("## Summary\n---\n> quoted bit") == "Summary. quoted bit."
def test_ampersand_and_percent_are_spoken_as_words():
assert for_speech("R&D at 50% capacity") == "R and D at 50 percent capacity"
def test_blank_and_symbol_only_input():
assert for_speech("") == ""
assert for_speech(None) == ""
assert for_speech("***") == ""
def test_display_keeps_emoji_but_drops_markdown():
assert for_display("**Done** ✅") == "Done ✅"
assert for_display("* one\n* two") == "• one • two"
def test_is_question_only_fires_on_a_trailing_question():
assert is_question("Ready to run a command or start a project?")
assert is_question("It's 7:15 AM. Want me to set a timer?")
assert not is_question("It's 7:15 AM on July 23, 2026.")
assert not is_question("What time is it? It's 7:15 AM.") # asked in passing
def test_is_question_ignores_trailing_decoration():
assert is_question("Ready to go? 🚀")
assert is_question('Shall I continue?"')
assert is_question("Want me to fix it? **")
def test_is_question_ignores_question_marks_that_are_not_spoken():
# The '?' here is inside a URL query string, which for_speech strips.
assert not is_question("Docs are at https://example.com/x?y=1")
assert not is_question("")
assert not is_question(None)
# ── ElevenLabs v3 delivery tags (observed live 2026-07-31) ──────────────────
# "[laughing] That one came through clean" was spoken as "laughing That one
# came through clean": the tags mean something to the dialogue model inside a
# dialoguectl scene, and nothing at all to the ordinary reply voice.
def test_delivery_tags_are_never_spoken_aloud():
spoken = for_speech("[laughing] That one came through clean.")
assert "laughing" not in spoken
assert spoken.startswith("That one came through clean")
def test_the_bubble_does_not_caption_a_laugh_nobody_heard():
assert "[laughing]" not in for_display("[laughing] All good.")
@pytest.mark.parametrize("tag", [
"[whispering]", "[cheerfully]", "[sighs]", "[laughs]", "[clears throat]",
"[nervously]", "[excited]", "[pause]", "[shouting]",
])
def test_the_common_tags_are_all_covered(tag):
assert "" == for_speech(tag).strip()
def test_ordinary_bracketed_text_survives():
"""The tags are matched narrowly on purpose — real bracketed content is
part of what the user asked to hear."""
assert "1" in for_speech("See reference [1] for details.")
assert "docs" in for_speech("It's in [the docs] somewhere.")