cmux NEXT agent chat: a sent message that did not arrive says why, never Sent

Merged
TE
13d ago
+423-694

Summary

Follow-up to #23962. Leo saw a sent-message card in claude-sr2's chat that read "โœ“ Sent", but its SendMessage result was success:false ("was stopped by the user and won't be resumed"). The card was lying.

Before #23962, main's MessageCard looked only at the tool status. A completed call with a success:false result read as Sent. #23962 changed that to one word, "Failed", with no reason. This PR says why, in a muted warning tone at the line's end, and never shows the raw result:

  • Not delivered: agent stopped: the result says the agent was stopped, won't be resumed, isn't running or has exited.
  • Not delivered: no such agent: no agent or session by that name (also matches no_session, "not found" and "unknown agent").
  • Not delivered: any other failure, including a result that says it "could not be delivered" or "failed to deliver". Those used to match "delivered".

Also from #23962's review notes:

  • An empty error field is not an error.
  • The agentId is now the last agentId: in an Agent call's result, so a subagent report that mentions another id can't win.
  • A nested subagent's agentId is captured too.
  • The opened message's fade follows a resize.
  • The catalog keys the sent row no longer uses are gone (message.delivered, message.sending, message.channel.*).
  • The transcript gallery note is updated.

The shared AgentMessageRow API is unchanged.

Gallery

The new outcomes state on agent-pane.agent-message-row is a synthetic coordinator chat:

  • one SendMessage delivered to "Fix grid jump";
  • one to a stopped "Time-cap pre-push owner check", which reads "Not delivered: agent stopped";
  • a "Received message from grid-lane" row.

Before/after: https://cmux-lawrences-mac-mini.tail137216.ts.net:18796/compare/tp-sent-card-real-outcome/?entry=agent-pane.agent-message-row

Tests

  • webviews/test/transcript-row-message.test.tsx: three synthetic success:false results (stopped, no such agent, no reason) show their own words, and none of the result's text shows, closed or opened.
  • agentMessages.test.ts:
    • notDeliveredReason for JSON and plain-text results;
    • "could not be delivered", "not delivered" and "failed to deliver" never read as delivered;
    • an empty error field is no error.
  • subagentFold.test.ts: the last agentId: wins.

Observed / Not run

  • Observed: scripts/web-lint.sh passed. For #23962, a fleet run of these three test files passed at its head.
  • Not run locally: bun tests (this Mac's hook sends them to the fleet). The fleet run for this head is linked in a comment.

Changelog

Fixed: a message to an agent that was stopped, or doesn't exist, no longer reads "Sent". Its line says "Not delivered" and why.

Risk: risk:normal (webview UI only).

Leo-ask: ask-0033ce890a

Agent session: cosmic-gray

๐Ÿค– Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Fixes the agent pane so a sent message that never arrived no longer reads "โœ“ Sent" โ€” its line now says "Not delivered" and why, in a muted warning tone, and never shows the raw result.

  • A stopped agent reads "Not delivered: agent stopped"; no matching agent reads "Not delivered: no such agent"; any other failure reads "Not delivered".
  • Results saying "could not be delivered", "not delivered", "failed to deliver", or "wasn't delivered" are treated as failed, never delivered; a recipient's name can't trigger a failure word.
  • A mailbox write that echoes the message it wrote still reads as Sent, and only whole echoed lines and a recipient's name are cut from a receipt, so a short message like "not" never erases the receipt's words; a message saying "success" or "ok" can't erase a JSON receipt's failure flag.
  • An empty error field is no longer treated as an error.
  • The agentId taken from an Agent call's result is the last one it names, and nested subagent ids are captured too.
  • The opened message's fade now follows a resize, and the now-unused catalog keys were removed.