Found on the live end-to-end run: Nisse Dev Testing: re-plan wasn't recognised as re-plan, because the Matrix transport passed the client's mention prefix through as part of the text. The same would have made an addressed approve count as rejection feedback, which throws the plan away and re-plans.
Leading mention of the agent → removed before command matching. Recognised forms: an HTML mention pill in formatted_body (it carries the exact user ID), the agent's user ID, or its display name followed by :. The display name is learnt from the agent's own m.room.member event in sync, so no extra endpoint is used.
Leading mention of anyone else → the message is skipped. In a shared room, Nisse Ops: approve or @alice:…: looks good is addressed elsewhere and must not reach this agent as gate input.
Only the start of the message counts.approve, cc @alice is unchanged.
The record keeps what was sent.InboundMessage gains an optional original, and the canonical log stores it; command matching uses the stripped text.
Sender verification is unchanged. The sender still comes from the event's sender field, set by the homeserver from the authenticated session. The operator allowlist and gates check that field only, never the text.
Tests: five new transport cases (pill, typed display name, typed user ID, someone else's mention skipped, mention mid-text left alone) and one orchestrator case (matched as stripped, recorded as sent). The new cases fail on main, except the mid-text one, which guards against over-stripping. 431 unit tests and the live Matrix test pass; lint and format clean.
Found on the live end-to-end run: `Nisse Dev Testing: re-plan` wasn't recognised as `re-plan`, because the Matrix transport passed the client's mention prefix through as part of the text. The same would have made an addressed `approve` count as rejection feedback, which throws the plan away and re-plans.
**Changes** (`nisse-dev/src/nisse_dev/matrix.py`, `transport.py`, `control.py`)
- **Leading mention of the agent → removed before command matching.** Recognised forms: an HTML mention pill in `formatted_body` (it carries the exact user ID), the agent's user ID, or its display name followed by `:`. The display name is learnt from the agent's own `m.room.member` event in sync, so no extra endpoint is used.
- **Leading mention of anyone else → the message is skipped.** In a shared room, `Nisse Ops: approve` or `@alice:…: looks good` is addressed elsewhere and must not reach this agent as gate input.
- **Only the start of the message counts.** `approve, cc @alice` is unchanged.
- **The record keeps what was sent.** `InboundMessage` gains an optional `original`, and the canonical log stores it; command matching uses the stripped text.
**Sender verification is unchanged.** The sender still comes from the event's `sender` field, set by the homeserver from the authenticated session. The operator allowlist and gates check that field only, never the text.
**Tests:** five new transport cases (pill, typed display name, typed user ID, someone else's mention skipped, mention mid-text left alone) and one orchestrator case (matched as stripped, recorded as sent). The new cases fail on `main`, except the mid-text one, which guards against over-stripping. 431 unit tests and the live Matrix test pass; lint and format clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found on the live end-to-end run:
Nisse Dev Testing: re-planwasn't recognised asre-plan, because the Matrix transport passed the client's mention prefix through as part of the text. The same would have made an addressedapprovecount as rejection feedback, which throws the plan away and re-plans.Changes (
nisse-dev/src/nisse_dev/matrix.py,transport.py,control.py)formatted_body(it carries the exact user ID), the agent's user ID, or its display name followed by:. The display name is learnt from the agent's ownm.room.memberevent in sync, so no extra endpoint is used.Nisse Ops: approveor@alice:…: looks goodis addressed elsewhere and must not reach this agent as gate input.approve, cc @aliceis unchanged.InboundMessagegains an optionaloriginal, and the canonical log stores it; command matching uses the stripped text.Sender verification is unchanged. The sender still comes from the event's
senderfield, set by the homeserver from the authenticated session. The operator allowlist and gates check that field only, never the text.Tests: five new transport cases (pill, typed display name, typed user ID, someone else's mention skipped, mention mid-text left alone) and one orchestrator case (matched as stripped, recorded as sent). The new cases fail on
main, except the mid-text one, which guards against over-stripping. 431 unit tests and the live Matrix test pass; lint and format clean.🤖 Generated with Claude Code
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>