Skip to content

fix(cli): expose direct-message enqueue state - #1468

Open
khaliqgant wants to merge 4 commits into
mainfrom
fix/relay-1467-dm-delivery
Open

fix(cli): expose direct-message enqueue state#1468
khaliqgant wants to merge 4 commits into
mainfrom
fix/relay-1467-dm-delivery

Conversation

@khaliqgant

@khaliqgant khaliqgant commented Aug 8, 2026

Copy link
Copy Markdown
Member

Fixes #1467

Summary

  • distinguish durable DM enqueue from delivery/read confirmation in MCP and CLI send receipts
  • resolve the requested DM recipient independently from the workspace directory and require a full exact name match before sending
  • turn an empty get_message_readers result into an explicit queued_or_unread signal
  • document wait (on-idle) versus steer (immediate, possibly interrupting) at every public choice point
  • add red/green unit, CLI, SDK, and MCP protocol regressions

#1466 is intentionally not included: Relay's local broker/CLI/SDK has no mention tokenizer; mention resolution occurs in the upstream Relaycast service. The receipt helper does defensively flag a supplied recipient mismatch.

Verification

  • changed MCP/CLI tests: exit 0, 32 passed
  • full SDK tests: exit 0, 163 passed
  • root typecheck: exit 0
  • CLI lint: exit 0, 0 errors
  • broker wait and steer mode tests: exit 0
  • Prettier and diff check: exit 0
  • verified-secret scan: exit 0, 0 findings

The runtime dependency audit still exits 1 on 11 existing findings; this branch changes no dependencies. Veto was unavailable in the lane, so no Veto verdict is claimed. This PR is not merged.

Review follow-up

The first revision echoed the requested SDK target as though it were independently resolved. Commit 0f9a6201 fixes that P2: MCP and CLI now resolve against the workspace directory before sending, exact matching handles hyphenated names and strict-prefix agents correctly, a missing exact name fails before enqueue, and an unavailable resolver reports recipient_unresolved rather than claiming a match.

Commit 49f8ba3b closes the remaining receipt gaps found in review: unresolved receipts discard any untrusted response target, CLI reader output now carries the same queued_or_unread signal as MCP, and the changelog entry is split into short impact-first bullets.

Documentation-only follow-up 3698f0f7 restores the exact MCP and CLI interface names required by the changelog guidance. Remote HEAD is confirmed as 3698f0f7f6a34fae69d6cf3d81097fb1dc74b0f5; all 11 Actions workflows triggered for that exact SHA completed successfully. Codegen Models, Test Build Scripts, Test Install Script, Rust Auto-Format, and Cancel PR Jobs on Merge were absent due their event/path conditions and are not counted as passing.

@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds direct-message delivery receipts across CLI and MCP tools. Receipts report recipient resolution, delivery mode, enqueue status, and reader confirmation. Documentation defines wait and steer behavior.

Changes

Direct-message delivery reporting

Layer / File(s) Summary
Delivery and reader receipt logic
packages/cli/src/cli/lib/message-delivery-receipts.ts, packages/cli/src/cli/mcp/messaging-tools.delivery.test.ts
Adds recipient resolution and receipt utilities for queued, injected, unresolved, mismatched, read, and unread states.
CLI and MCP receipt integration
packages/cli/src/cli/mcp/messaging-tools.ts, packages/cli/src/cli/agent-relay-mcp.ts, packages/cli/src/cli/commands/message.ts, packages/cli/src/cli/mcp/messaging-tools.protocol.test.ts, packages/cli/src/cli/commands/relaycast-groups.test.ts
Updates messaging handlers and schemas to return delivery receipts and reader status. Wires workspace-agent lookup and validates CLI output.
Messaging behavior documentation
.agents/skills/using-agent-relay/SKILL.md, .claude/skills/using-agent-relay/SKILL.md, packages/cli/README.md, packages/sdk-py/README.md, packages/sdk/src/messaging/types.ts, CHANGELOG.md
Documents wait, steer, enqueue confirmation, and reader confirmation semantics.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Sender
  participant send_dm
  participant AgentDirectory
  participant directMessageReceipt
  participant messageReadersReceipt
  Sender->>send_dm: Send message with mode
  send_dm->>AgentDirectory: Resolve exact recipient
  AgentDirectory-->>send_dm: Return matching agent
  send_dm->>directMessageReceipt: Normalize enqueue response
  directMessageReceipt-->>Sender: Return delivery receipt
  Sender->>messageReadersReceipt: Check message readers
  messageReadersReceipt-->>Sender: Return read or queued_or_unread status
Loading

Possibly related PRs

Suggested reviewers: willwashburn

Poem

A rabbit sends a message neat,
wait queues softly, steer moves fleet.
Receipts show the enqueue state,
Readers mark the message read or late.
Hop by hop, the signals glow.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #1467 by exposing enqueue versus read state, documenting wait and steer modes, signaling unread queues, and enforcing exact recipient resolution.
Out of Scope Changes check ✅ Passed The implementation, documentation, tests, and changelog changes directly support the objectives in issue #1467, with no unrelated code changes identified.
Title check ✅ Passed The title clearly summarizes the primary change: exposing direct-message enqueue state in the CLI.
Description check ✅ Passed The description includes a clear summary and detailed verification results, although it uses a Verification section instead of the template's Test Plan section.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/relay-1467-dm-delivery

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@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: 38496ccbf5

ℹ️ 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 packages/sdk/src/messaging/relaycast.ts Outdated
})
);
return this.normalizeDirectResponse(response, 'dm');
return this.normalizeDirectResponse(response, 'dm', undefined, input.to);

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 Preserve the response recipient before declaring a match

normalizeDirectResponse receives the requested input.to as agentName and emits it as the message target, while discarding any recipient information from the raw response. Because directMessageReceipt prioritizes this target, agent-relay message dm send will always report recipientMatched: true and echo the request as resolvedRecipient, even when the backend response identifies a different or canonicalized recipient. Preserve the response-derived recipient or report it as unresolved rather than presenting the request value as independently resolved.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 0f9a620. The receipt no longer trusts the SDK/request target as an independent resolution. MCP and CLI now list the workspace directory, require a full exact agent-name match before sending, and pass that independently resolved name into the receipt. With no resolver the helper reports recipient_unresolved; the SDK target echo change was removed. Added exact hyphenated, strict-prefix, missing-name, mismatch, and unresolved regressions. Local follow-up: 32/32 changed tests, 163/163 SDK tests, root typecheck, lint, diff check, and secret scan all exit 0.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Line 12: Rename the changelog heading containing “Unreleased - Patch” to the
required exact heading “Unreleased,” preserving the new entry and all other
changelog content unchanged.

In `@packages/cli/src/cli/commands/message.ts`:
- Line 11: Update the inbox get_readers handler to import messageReadersReceipt
alongside directMessageReceipt, wrap messages.readers(messageId) with
messageReadersReceipt, and pass the resulting receipt to printJson so empty
reader lists retain the queued_or_unread signal.

In `@packages/sdk/src/messaging/relaycast.ts`:
- Line 288: Update the direct-message response flow around
normalizeDirectResponse so the requested input.to and the server-resolved
recipient remain separate, with message.target representing the server value
used by directMessageReceipt. Ensure recipient validation can return
recipient_mismatch when resolution differs, and add an end-to-end test covering
a response that resolves to a different recipient.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f7a1e053-4d44-4337-9299-611847879109

📥 Commits

Reviewing files that changed from the base of the PR and between 7a42f3b and 38496cc.

📒 Files selected for processing (15)
  • .agents/skills/using-agent-relay/SKILL.md
  • .claude/skills/using-agent-relay/SKILL.md
  • CHANGELOG.md
  • packages/cli/README.md
  • packages/cli/src/cli/commands/message.ts
  • packages/cli/src/cli/commands/relaycast-groups.test.ts
  • packages/cli/src/cli/lib/message-delivery-receipts.ts
  • packages/cli/src/cli/mcp/messaging-tools.delivery.test.ts
  • packages/cli/src/cli/mcp/messaging-tools.protocol.test.ts
  • packages/cli/src/cli/mcp/messaging-tools.ts
  • packages/sdk-py/README.md
  • packages/sdk/src/__tests__/messaging.test.ts
  • packages/sdk/src/messaging/normalize.ts
  • packages/sdk/src/messaging/relaycast.ts
  • packages/sdk/src/messaging/types.ts

Comment thread CHANGELOG.md Outdated
Comment thread packages/cli/src/cli/commands/message.ts Outdated
Comment thread packages/sdk/src/messaging/relaycast.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

2 issues found across 15 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/cli/src/cli/mcp/messaging-tools.protocol.test.ts">

<violation number="1" location="packages/cli/src/cli/mcp/messaging-tools.protocol.test.ts:10">
P3: The mocked `dm` response carries no target/recipient identifier, so `resolvedRecipient` here is just the `requestedRecipient` fallback in `resolvedDirectRecipient`; the assertion `resolvedRecipient: 'chief-khaliq'` passes regardless of whether receipt resolution works. Since the PR explicitly aims to "preserve and assert the exact requested/resolved DM recipient", this regression test doesn't exercise that path and would not catch a broken resolve.</violation>
</file>

<file name="packages/cli/src/cli/lib/message-delivery-receipts.ts">

<violation number="1" location="packages/cli/src/cli/lib/message-delivery-receipts.ts:69">
P3: The new `messageReadersReceipt` helper (which turns an empty reader list into an explicit `queued_or_unread` signal) is wired only into the MCP `get_message_readers` tool, not into the CLI's `inbox get_readers` / `dm get_readers` command in `commands/message.ts`. The CLI still prints the raw `readers` array with no delivery signal, so at the CLI public choice point an empty result is not surfaced as `queued_or_unread` — inconsistent with the PR's goal of exposing this signal. Consider applying the same receipt in the CLI `get_readers` handlers for a consistent contract across surfaces.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


describe('messaging delivery receipts over MCP', () => {
it('exposes enqueue state on send and an explicit signal for an empty reader list', async () => {
const dm = vi.fn(async () => ({ id: 'msg_1', text: 'hello' }));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The mocked dm response carries no target/recipient identifier, so resolvedRecipient here is just the requestedRecipient fallback in resolvedDirectRecipient; the assertion resolvedRecipient: 'chief-khaliq' passes regardless of whether receipt resolution works. Since the PR explicitly aims to "preserve and assert the exact requested/resolved DM recipient", this regression test doesn't exercise that path and would not catch a broken resolve.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/cli/mcp/messaging-tools.protocol.test.ts, line 10:

<comment>The mocked `dm` response carries no target/recipient identifier, so `resolvedRecipient` here is just the `requestedRecipient` fallback in `resolvedDirectRecipient`; the assertion `resolvedRecipient: 'chief-khaliq'` passes regardless of whether receipt resolution works. Since the PR explicitly aims to "preserve and assert the exact requested/resolved DM recipient", this regression test doesn't exercise that path and would not catch a broken resolve.</comment>

<file context>
@@ -0,0 +1,51 @@
+
+describe('messaging delivery receipts over MCP', () => {
+  it('exposes enqueue state on send and an explicit signal for an empty reader list', async () => {
+    const dm = vi.fn(async () => ({ id: 'msg_1', text: 'hello' }));
+    const readers = vi.fn(async () => []);
+    const server = new McpServer({ name: 'messaging-test', version: '1.0.0' });
</file context>

@khaliqgant khaliqgant Aug 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 0f9a620. The protocol test now supplies an independent workspace-directory callback containing both chief and chief-khaliq; the send receipt exact recipient comes from that directory lookup, not the DM response.

Comment thread CHANGELOG.md Outdated
};
}

export function messageReadersReceipt(readers: unknown[]): {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P3: The new messageReadersReceipt helper (which turns an empty reader list into an explicit queued_or_unread signal) is wired only into the MCP get_message_readers tool, not into the CLI's inbox get_readers / dm get_readers command in commands/message.ts. The CLI still prints the raw readers array with no delivery signal, so at the CLI public choice point an empty result is not surfaced as queued_or_unread — inconsistent with the PR's goal of exposing this signal. Consider applying the same receipt in the CLI get_readers handlers for a consistent contract across surfaces.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/cli/src/cli/lib/message-delivery-receipts.ts, line 69:

<comment>The new `messageReadersReceipt` helper (which turns an empty reader list into an explicit `queued_or_unread` signal) is wired only into the MCP `get_message_readers` tool, not into the CLI's `inbox get_readers` / `dm get_readers` command in `commands/message.ts`. The CLI still prints the raw `readers` array with no delivery signal, so at the CLI public choice point an empty result is not surfaced as `queued_or_unread` — inconsistent with the PR's goal of exposing this signal. Consider applying the same receipt in the CLI `get_readers` handlers for a consistent contract across surfaces.</comment>

<file context>
@@ -0,0 +1,84 @@
+  };
+}
+
+export function messageReadersReceipt(readers: unknown[]): {
+  readers: unknown[];
+  delivery: { status: 'read' | 'queued_or_unread'; readConfirmed: boolean; signal: string };
</file context>

@khaliqgant khaliqgant Aug 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 49f8ba3. CLI message inbox get_readers now emits the same read or queued_or_unread receipt contract as the MCP tool, with a dedicated regression.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 9 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/cli/src/cli/lib/message-delivery-receipts.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread CHANGELOG.md Outdated
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.

A DM can return a message ID and sit unread forever: delivery to a queue is not delivery to the agent

1 participant