fix(gateway/bluebubbles): align iMessage delivery with non-editable UX
This commit is contained in:
@@ -66,6 +66,37 @@ class TestBlueBubblesHelpers:
|
||||
|
||||
assert check_bluebubbles_requirements() is True
|
||||
|
||||
def test_supports_message_editing_is_false(self, monkeypatch):
|
||||
adapter = _make_adapter(monkeypatch)
|
||||
assert adapter.SUPPORTS_MESSAGE_EDITING is False
|
||||
|
||||
def test_truncate_message_omits_pagination_suffixes(self, monkeypatch):
|
||||
adapter = _make_adapter(monkeypatch)
|
||||
chunks = adapter.truncate_message("abcdefghij", max_length=6)
|
||||
assert len(chunks) > 1
|
||||
assert "".join(chunks) == "abcdefghij"
|
||||
assert all("(" not in chunk for chunk in chunks)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_splits_paragraphs_into_multiple_bubbles(self, monkeypatch):
|
||||
adapter = _make_adapter(monkeypatch)
|
||||
sent = []
|
||||
|
||||
async def fake_resolve_chat_guid(chat_id):
|
||||
return "iMessage;-;user@example.com"
|
||||
|
||||
async def fake_api_post(path, payload):
|
||||
sent.append(payload["message"])
|
||||
return {"data": {"guid": f"msg-{len(sent)}"}}
|
||||
|
||||
monkeypatch.setattr(adapter, "_resolve_chat_guid", fake_resolve_chat_guid)
|
||||
monkeypatch.setattr(adapter, "_api_post", fake_api_post)
|
||||
|
||||
result = await adapter.send("user@example.com", "first thought\n\nsecond thought")
|
||||
|
||||
assert result.success is True
|
||||
assert sent == ["first thought", "second thought"]
|
||||
|
||||
def test_format_message_strips_markdown(self, monkeypatch):
|
||||
adapter = _make_adapter(monkeypatch)
|
||||
assert adapter.format_message("**Hello** `world`") == "Hello world"
|
||||
|
||||
@@ -58,6 +58,13 @@ class ProgressCaptureAdapter(BasePlatformAdapter):
|
||||
return {"id": chat_id}
|
||||
|
||||
|
||||
class NonEditingProgressCaptureAdapter(ProgressCaptureAdapter):
|
||||
SUPPORTS_MESSAGE_EDITING = False
|
||||
|
||||
async def edit_message(self, chat_id, message_id, content) -> SendResult:
|
||||
raise AssertionError("non-editable adapters should not receive edit_message calls")
|
||||
|
||||
|
||||
class FakeAgent:
|
||||
def __init__(self, **kwargs):
|
||||
self.tool_progress_callback = kwargs.get("tool_progress_callback")
|
||||
@@ -502,6 +509,7 @@ async def _run_with_agent(
|
||||
chat_id="-1001",
|
||||
chat_type="group",
|
||||
thread_id="17585",
|
||||
adapter_cls=ProgressCaptureAdapter,
|
||||
):
|
||||
if config_data:
|
||||
import yaml
|
||||
@@ -516,7 +524,7 @@ async def _run_with_agent(
|
||||
fake_run_agent.AIAgent = agent_cls
|
||||
monkeypatch.setitem(sys.modules, "run_agent", fake_run_agent)
|
||||
|
||||
adapter = ProgressCaptureAdapter(platform=platform)
|
||||
adapter = adapter_cls(platform=platform)
|
||||
runner = _make_runner(adapter)
|
||||
gateway_run = importlib.import_module("gateway.run")
|
||||
if config_data and "streaming" in config_data:
|
||||
@@ -666,6 +674,26 @@ async def test_run_agent_interim_commentary_works_with_tool_progress_off(monkeyp
|
||||
assert any(call["content"] == "I'll inspect the repo first." for call in adapter.sent)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_agent_bluebubbles_uses_commentary_send_path_for_quick_replies(monkeypatch, tmp_path):
|
||||
adapter, result = await _run_with_agent(
|
||||
monkeypatch,
|
||||
tmp_path,
|
||||
CommentaryAgent,
|
||||
session_id="sess-bluebubbles-commentary",
|
||||
config_data={"display": {"interim_assistant_messages": True}},
|
||||
platform=Platform.BLUEBUBBLES,
|
||||
chat_id="iMessage;-;user@example.com",
|
||||
chat_type="dm",
|
||||
thread_id=None,
|
||||
adapter_cls=NonEditingProgressCaptureAdapter,
|
||||
)
|
||||
|
||||
assert result.get("already_sent") is not True
|
||||
assert [call["content"] for call in adapter.sent] == ["I'll inspect the repo first."]
|
||||
assert adapter.edits == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_agent_previewed_final_marks_already_sent(monkeypatch, tmp_path):
|
||||
adapter, result = await _run_with_agent(
|
||||
|
||||
@@ -185,6 +185,25 @@ class TestBuildSessionContextPrompt:
|
||||
assert "Telegram" in prompt
|
||||
assert "Home Chat" in prompt
|
||||
|
||||
def test_bluebubbles_prompt_mentions_short_conversational_i_message_format(self):
|
||||
config = GatewayConfig(
|
||||
platforms={
|
||||
Platform.BLUEBUBBLES: PlatformConfig(enabled=True, extra={"server_url": "http://localhost:1234", "password": "secret"}),
|
||||
},
|
||||
)
|
||||
source = SessionSource(
|
||||
platform=Platform.BLUEBUBBLES,
|
||||
chat_id="iMessage;-;user@example.com",
|
||||
chat_name="Ben",
|
||||
chat_type="dm",
|
||||
)
|
||||
ctx = build_session_context(source, config)
|
||||
prompt = build_session_context_prompt(ctx)
|
||||
|
||||
assert "responding via iMessage" in prompt
|
||||
assert "short and conversational" in prompt
|
||||
assert "blank line" in prompt
|
||||
|
||||
def test_discord_prompt(self):
|
||||
config = GatewayConfig(
|
||||
platforms={
|
||||
|
||||
Reference in New Issue
Block a user