Handle leading mentions in Matrix messages
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m24s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m24s
Clients put the addressee's name in front of a message that mentions
it ("Nisse Dev Testing: re-plan"), and the transport passed that text
on whole, so no command matched: the live run's re-plan was asked again,
and an addressed 'approve' would have counted as rejection feedback.
- A leading mention of the agent (an HTML pill, its user ID, or its
display name followed by ':') is removed before commands are matched.
The display name is learnt from the agent's own member event.
- A message whose leading mention is someone else is skipped: in a
shared room it is addressed to another agent or person.
- Only the start counts; a mention later in the text is left alone.
- The transcript records the message as sent (InboundMessage.original).
The sender is unaffected: it still comes from the event's homeserver-set
sender field, never from the text.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9620404a2a
commit
b0c5f48be4
5 files changed
+199
-8
No files matched your search
@@ -143,3 +143,97 @@ def test_start_without_handler_raises() -> None:
|
||||
)
|
||||
with pytest.raises(TransportError, match="no message handler"):
|
||||
transport.start()
|
||||
|
||||
|
||||
# --- leading mentions --------------------------------------------------------
|
||||
|
||||
|
||||
def own_member_state(name: str) -> dict:
|
||||
"""The first sync's room state, which carries the agent's display name."""
|
||||
body = sync_body([], "b1")
|
||||
body["rooms"]["join"]["!room:test"]["state"] = {
|
||||
"events": [
|
||||
{
|
||||
"type": "m.room.member",
|
||||
"state_key": "@nisse-dev:test",
|
||||
"sender": "@nisse-dev:test",
|
||||
"content": {"membership": "join", "displayname": name},
|
||||
}
|
||||
]
|
||||
}
|
||||
return body
|
||||
|
||||
|
||||
def pill_event(sender: str, user_id: str, label: str, rest: str) -> dict:
|
||||
"""A message as Element sends one that starts by mentioning user_id."""
|
||||
return {
|
||||
"type": "m.room.message",
|
||||
"sender": sender,
|
||||
"content": {
|
||||
"msgtype": "m.text",
|
||||
"body": f"{label}: {rest}",
|
||||
"format": "org.matrix.custom.html",
|
||||
"formatted_body": (
|
||||
f'<a href="https://matrix.to/#/{user_id}">{label}</a>: {rest}'
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_a_leading_pill_for_the_agent_is_removed_before_matching() -> None:
|
||||
responses = [
|
||||
sync_body([], "b1"),
|
||||
sync_body(
|
||||
[pill_event("@don:test", "@nisse-dev:test", "Nisse Dev", "approve")],
|
||||
"b2",
|
||||
),
|
||||
]
|
||||
[msg] = collect_messages(responses, expected=1)
|
||||
assert msg.sender == "@don:test" # from the event, never from the text
|
||||
assert msg.text == "approve"
|
||||
assert msg.original == "Nisse Dev: approve"
|
||||
|
||||
|
||||
def test_the_agents_display_name_typed_in_front_is_removed() -> None:
|
||||
responses = [
|
||||
own_member_state("Nisse Dev"),
|
||||
sync_body([message_event("@don:test", "Nisse Dev: re-plan")], "b2"),
|
||||
]
|
||||
[msg] = collect_messages(responses, expected=1)
|
||||
assert msg.text == "re-plan"
|
||||
|
||||
|
||||
def test_the_agents_user_id_typed_in_front_is_removed() -> None:
|
||||
responses = [
|
||||
sync_body([], "b1"),
|
||||
sync_body([message_event("@don:test", "@nisse-dev:test: status")], "b2"),
|
||||
]
|
||||
[msg] = collect_messages(responses, expected=1)
|
||||
assert msg.text == "status"
|
||||
|
||||
|
||||
def test_messages_addressed_to_someone_else_are_skipped() -> None:
|
||||
responses = [
|
||||
sync_body([], "b1"),
|
||||
sync_body(
|
||||
[
|
||||
pill_event("@don:test", "@nisse-ops:test", "Nisse Ops", "approve"),
|
||||
message_event("@don:test", "@alice:test: looks good"),
|
||||
message_event("@don:test", "status"),
|
||||
],
|
||||
"b2",
|
||||
),
|
||||
]
|
||||
received = collect_messages(responses, expected=1)
|
||||
assert received == [InboundMessage(sender="@don:test", text="status")]
|
||||
|
||||
|
||||
def test_a_mention_later_in_the_message_is_left_alone() -> None:
|
||||
responses = [
|
||||
sync_body([], "b1"),
|
||||
sync_body([message_event("@don:test", "approve, cc @alice:test")], "b2"),
|
||||
]
|
||||
received = collect_messages(responses, expected=1)
|
||||
assert received == [
|
||||
InboundMessage(sender="@don:test", text="approve, cc @alice:test")
|
||||
]
|
||||
Reference in new issue
Block a user