forked from Rockachopa/Timmy-time-dashboard
This commit is contained in:
@@ -8,6 +8,62 @@ and WebSocket gateway.
|
||||
import time
|
||||
from datetime import UTC, datetime
|
||||
|
||||
# Valid bark styles for Matrix protocol
|
||||
BARK_STYLES = {"speech", "thought", "whisper", "shout"}
|
||||
|
||||
|
||||
def produce_bark(agent_id: str, text: str, reply_to: str = None, style: str = "speech") -> dict:
|
||||
"""Format a chat response as a Matrix bark message.
|
||||
|
||||
Barks appear as floating text above agents in the Matrix 3D world with
|
||||
typing animation. This function formats the text for the Matrix protocol.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
agent_id:
|
||||
Unique identifier for the agent (e.g. ``"timmy"``).
|
||||
text:
|
||||
The chat response text to display as a bark.
|
||||
reply_to:
|
||||
Optional message ID or reference this bark is replying to.
|
||||
style:
|
||||
Visual style of the bark. One of: "speech" (default), "thought",
|
||||
"whisper", "shout". Invalid styles fall back to "speech".
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
Bark message with keys ``type``, ``agent_id``, ``data`` (containing
|
||||
``text``, ``reply_to``, ``style``), and ``ts``.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> produce_bark("timmy", "Hello world!")
|
||||
{
|
||||
"type": "bark",
|
||||
"agent_id": "timmy",
|
||||
"data": {"text": "Hello world!", "reply_to": None, "style": "speech"},
|
||||
"ts": 1742529600,
|
||||
}
|
||||
"""
|
||||
# Validate and normalize style
|
||||
if style not in BARK_STYLES:
|
||||
style = "speech"
|
||||
|
||||
# Truncate text to 280 characters (bark, not essay)
|
||||
truncated_text = text[:280] if text else ""
|
||||
|
||||
return {
|
||||
"type": "bark",
|
||||
"agent_id": agent_id,
|
||||
"data": {
|
||||
"text": truncated_text,
|
||||
"reply_to": reply_to,
|
||||
"style": style,
|
||||
},
|
||||
"ts": int(time.time()),
|
||||
}
|
||||
|
||||
|
||||
def serialize_presence(presence: dict) -> dict:
|
||||
"""Transform an ADR-023 presence dict into the world-state API shape.
|
||||
|
||||
@@ -4,7 +4,7 @@ from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from infrastructure.presence import produce_agent_state, serialize_presence
|
||||
from infrastructure.presence import produce_agent_state, produce_bark, serialize_presence
|
||||
|
||||
|
||||
class TestSerializePresence:
|
||||
@@ -163,3 +163,109 @@ class TestProduceAgentState:
|
||||
"""When display_name is missing, it's derived from agent_id.title()."""
|
||||
data = produce_agent_state("spark", {})["data"]
|
||||
assert data["display_name"] == "Spark"
|
||||
|
||||
|
||||
class TestProduceBark:
|
||||
"""Tests for produce_bark() — Matrix bark message producer."""
|
||||
|
||||
@patch("infrastructure.presence.time")
|
||||
def test_full_message_structure(self, mock_time):
|
||||
"""Returns dict with type, agent_id, data, and ts keys."""
|
||||
mock_time.time.return_value = 1742529600
|
||||
result = produce_bark("timmy", "Hello world!")
|
||||
|
||||
assert result["type"] == "bark"
|
||||
assert result["agent_id"] == "timmy"
|
||||
assert result["ts"] == 1742529600
|
||||
assert isinstance(result["data"], dict)
|
||||
|
||||
def test_data_fields(self):
|
||||
"""data dict contains text, reply_to, and style."""
|
||||
result = produce_bark("timmy", "Hello world!", reply_to="msg-123", style="shout")
|
||||
data = result["data"]
|
||||
|
||||
assert data["text"] == "Hello world!"
|
||||
assert data["reply_to"] == "msg-123"
|
||||
assert data["style"] == "shout"
|
||||
|
||||
def test_default_style_is_speech(self):
|
||||
"""When style is not provided, defaults to 'speech'."""
|
||||
result = produce_bark("timmy", "Hello!")
|
||||
assert result["data"]["style"] == "speech"
|
||||
|
||||
def test_default_reply_to_is_none(self):
|
||||
"""When reply_to is not provided, defaults to None."""
|
||||
result = produce_bark("timmy", "Hello!")
|
||||
assert result["data"]["reply_to"] is None
|
||||
|
||||
def test_text_truncated_to_280_chars(self):
|
||||
"""Text longer than 280 chars is truncated."""
|
||||
long_text = "A" * 500
|
||||
result = produce_bark("timmy", long_text)
|
||||
assert len(result["data"]["text"]) == 280
|
||||
assert result["data"]["text"] == "A" * 280
|
||||
|
||||
def test_text_exactly_280_chars_not_truncated(self):
|
||||
"""Text exactly 280 chars is not truncated."""
|
||||
text = "B" * 280
|
||||
result = produce_bark("timmy", text)
|
||||
assert result["data"]["text"] == text
|
||||
|
||||
def test_text_shorter_than_280_not_padded(self):
|
||||
"""Text shorter than 280 chars is not padded."""
|
||||
result = produce_bark("timmy", "Short")
|
||||
assert result["data"]["text"] == "Short"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("style", "expected_style"),
|
||||
[
|
||||
("speech", "speech"),
|
||||
("thought", "thought"),
|
||||
("whisper", "whisper"),
|
||||
("shout", "shout"),
|
||||
],
|
||||
)
|
||||
def test_valid_styles_preserved(self, style, expected_style):
|
||||
"""Valid style values are preserved."""
|
||||
result = produce_bark("timmy", "Hello!", style=style)
|
||||
assert result["data"]["style"] == expected_style
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_style",
|
||||
["yell", "scream", "", "SPEECH", "Speech", None, 123],
|
||||
)
|
||||
def test_invalid_style_defaults_to_speech(self, invalid_style):
|
||||
"""Invalid style values fall back to 'speech'."""
|
||||
result = produce_bark("timmy", "Hello!", style=invalid_style)
|
||||
assert result["data"]["style"] == "speech"
|
||||
|
||||
def test_empty_text_handled(self):
|
||||
"""Empty text is handled gracefully."""
|
||||
result = produce_bark("timmy", "")
|
||||
assert result["data"]["text"] == ""
|
||||
|
||||
def test_ts_is_unix_timestamp(self):
|
||||
"""ts should be an integer Unix timestamp."""
|
||||
result = produce_bark("timmy", "Hello!")
|
||||
assert isinstance(result["ts"], int)
|
||||
assert result["ts"] > 0
|
||||
|
||||
def test_agent_id_passed_through(self):
|
||||
"""agent_id appears in the top-level message."""
|
||||
result = produce_bark("spark", "Hello!")
|
||||
assert result["agent_id"] == "spark"
|
||||
|
||||
def test_with_all_parameters(self):
|
||||
"""Full parameter set produces expected output."""
|
||||
result = produce_bark(
|
||||
agent_id="timmy",
|
||||
text="Running test suite...",
|
||||
reply_to="parent-msg-456",
|
||||
style="thought",
|
||||
)
|
||||
|
||||
assert result["type"] == "bark"
|
||||
assert result["agent_id"] == "timmy"
|
||||
assert result["data"]["text"] == "Running test suite..."
|
||||
assert result["data"]["reply_to"] == "parent-msg-456"
|
||||
assert result["data"]["style"] == "thought"
|
||||
|
||||
Reference in New Issue
Block a user