Skip to content

feat(mobile): p-tag all DM participants so agents respond to plain DMs - #3258

Open
zacharytamas wants to merge 4 commits into
block:mainfrom
zacharytamas:auto-trigger-agent-response-on-mobile-direct-messages
Open

feat(mobile): p-tag all DM participants so agents respond to plain DMs#3258
zacharytamas wants to merge 4 commits into
block:mainfrom
zacharytamas:auto-trigger-agent-response-on-mobile-direct-messages

Conversation

@zacharytamas

@zacharytamas zacharytamas commented Jul 28, 2026

Copy link
Copy Markdown

What problems was I solving

On desktop, DMing an agent "just works": a plain message p-tags every other DM participant, and the buzz-acp harness's mention-gated #p subscription treats those tags as the trigger. Mobile only tagged explicit @mentions, so a DM'd agent stayed silent unless the sender selected it from the mention picker.

After this PR, mobile resolves the current channel before publishing and adds every other DM participant as a p tag. A plain, unmentioned DM therefore wakes the recipient agent when channel state is available.

What user-facing changes did I ship

  • Plain mobile DMs now p-tag all known participants except the sender.
  • Thread replies use the same centralized send path and retain their reply e tags alongside recipient p tags.
  • Non-DM channels still emit only explicit mention tags.
  • Recipient pubkeys are emitted in lowercase, deduplicated case-insensitively, and empty values are discarded.
  • A cache miss waits for channelsProvider.future, preventing initial loading from silently dropping DM recipients.
  • If channel lookup ultimately returns no entry or fails, the message still publishes with any explicit mentions instead of blocking an otherwise usable composer.

Implementation

The change remains centralized in two files:

  • mobile/lib/features/channels/send_message_provider.dart
    • resolves the channel from the synchronous cache or authoritative provider future;
    • fans out recipient tags only for a resolved DM;
    • gracefully degrades to explicit mentions when channel resolution is unavailable;
    • canonicalizes, filters, deduplicates, and self-excludes the resulting pubkeys before signing.
  • mobile/test/features/channels/send_message_provider_test.dart
    • verifies the signed event tags at the relay boundary.

No channel-page, thread-navigation, message-row, or action-sheet interfaces are changed.

Verification

flutter test test/features/channels/send_message_provider_test.dart  # 9 passed
just mobile-check                                                    # format, analyze, file-size guard
just mobile-test                                                     # 831 passed, 1 skipped

Closes #2835

@zacharytamas
zacharytamas requested a review from a team as a code owner July 28, 2026 02:17
Match desktop's DM contract: kind-9 messages published in a DM channel
now carry ["p", pk] tags for every other DM participant (union of
explicit @mentions and the channel's participantPubkeys, minus the
sender, deduplicated), so the buzz-acp harness's mention-gated #p
subscription sees plain DM messages and agents respond without an
@mention.

The fan-out lives in SendMessage.call — mobile's single publish-time
chokepoint for kind-9 — via a new injected readChannel closure wired
from the cached channelsProvider state (synchronous; never blocks a
send). If the channel isn't cached yet or isn't a DM, behavior is
unchanged. Non-DM channels keep explicit-mentions-only semantics.

Wire-level tests assert the p tags on the recorded signed event for
plain DM sends, non-DM sends, and the not-yet-cached fallback.

Signed-off-by: Zachary <zachary@tamas.sh>
…d thread replies

Two wire-level tests on SendMessage: a case-mismatched explicit mention
overlapping a DM roster entry dedupes to a single p tag, and a DM thread
reply carries the participant fan-out alongside its root/reply e-tags.

Signed-off-by: Zachary <zachary@tamas.sh>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98d370395b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/buzz-acp/src/base_prompt.md Outdated
@zacharytamas
zacharytamas force-pushed the auto-trigger-agent-response-on-mobile-direct-messages branch from 98d3703 to 4808153 Compare July 28, 2026 02:22

@Baltsat Baltsat left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The central SendMessage path is a cleaner place for this behavior. Two concrete points from the earlier review on #2863 still appear to apply here:

  1. seenMentions.add(pk.toLowerCase()) deduplicates case-insensitively, but emits the original pk and also accepts an empty string. svndco asked for canonical lowercase output and empty-value filtering; the current tests do not cover either case.
  2. When _readChannel(channelId) returns null, a plain DM still emits no recipient p tags. Is that cache-miss behavior acceptable, or should this path have an authoritative fallback so the original #2835 failure cannot recur during provider loading?

It would also help to link/close #2835 in the PR body. I closed #2863 to avoid competing production patches.

Signed-off-by: Zachary <zachary@tamas.sh>
@zacharytamas

Copy link
Copy Markdown
Author

Addressed both review points in 1750a15fc:

  • Recipient pubkeys are now emitted in canonical lowercase, deduplicated case-insensitively, and empty values are filtered. The wire-level test covers uppercase explicit input plus empty explicit/roster entries.
  • A cache miss now awaits channelsProvider.future before constructing the event. If the authoritative result still has no matching channel, the send fails with StateError before local insertion or relay publication rather than silently sending without DM p tags. Tests cover both delayed channel loading and authoritative not-found behavior.
  • Added Closes #2835 to the PR body.

Verification: focused provider suite 8/8; just mobile-check; full mobile suite 830 passed, 1 skipped. The unrelated stale base_prompt.md thread was also answered and is resolved.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1750a15fcb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread mobile/lib/features/channels/send_message_provider.dart Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c0cbff5a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

.read(sendMessageProvider)
.call(
channelId: channelId,
channel: channel,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Send thread replies with the refreshed DM roster

When DM membership changes after this thread page is pushed—for example, a participant or agent is added from desktop—channelsProvider refreshes and rebuilds this widget, but the send path still passes the constructor snapshot channel. SendMessage therefore reads stale participantPubkeys, so plain thread replies omit the new participant's p tag indefinitely until the user closes and reopens the thread. Resolve the current channel from the already-watched channelsAsync, with the route channel only as a fallback, before sending.

Useful? React with 👍 / 👎.

Signed-off-by: Zachary <zachary@tamas.sh>
@zacharytamas
zacharytamas force-pushed the auto-trigger-agent-response-on-mobile-direct-messages branch from 0c0cbff to 7c9fa80 Compare July 28, 2026 13:09
@zacharytamas

Copy link
Copy Markdown
Author

Scope correction: the earlier route-Channel refactor in 0c0cbff5a was intentionally removed and the branch history rewritten. The final fix is 7c9fa80be: channel loading is still awaited for DM fan-out, but a missing or failed lookup now degrades to explicit mentions rather than blocking publication. The final review delta remains confined to send_message_provider.dart and its wire-level tests. Provider suite: 9/9; just mobile-check: passed; full mobile suite: 831 passed, 1 skipped.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c9fa80bee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +67 to +70
try {
channel = await _readChannel(channelId);
} catch (_) {
channel = null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the DM roster when channel lookup fails

When _readChannel throws or returns null while a DM route remains usable—for example, during a channelsProvider refresh error—this fallback treats the DM as a non-DM and publishes a plain message without any p tags. ACP agents subscribe through #p, so the send appears successful but the agent never receives or responds to it; the added failure tests only exercise explicit mentions, which mask this behavior. Fresh evidence relative to the earlier lookup discussion is that the current catch-and-null path now explicitly swallows the failure and selects an empty roster, so the route-resolved Channel should be supplied to preserve DM fan-out.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mobile: DM omits recipient p-tag unless a mention is picked, so agents are never woken

2 participants