Agent pane: other agents' messages draw as cards from their sender, not your prompt
When another agent messages a cmux NEXT chat, the pane drew the message as if the person had typed it. That covers acpmux's own deliveries (send_to_session from #21858 and cmux agent message, which carry _meta.provenance.kind: "agent_message"), and an adopted Claude Code session (acpmux records its history with client: "adopt") showed its history's <cross-session-message from=⦠from-name=ā¦>, <task-notification> and <system-reminder> prompts as raw XML.
Now:
- A message from another agent draws as the existing message card (
MessageCard's look,cv-message), turned around: sender ā This agent, two lines with Show more. With acpmux provenance the sender is a chat here, so the card has Open , which opens it beside this chat (chat.offer.open, the same path a started task's row uses). - A Claude Code
<cross-session-message>shows itsfrom-name(else its address) and the body only. Claude Code's trailing note to the agent is dropped from the card. Opening the card shows the whole prompt as the agent got it. - A
<task-notification>draws as one quiet tool-row line with its summary. A prompt that's only reminders, local-command caveats or prompt-submit hook text gets no row. A typed prompt behind them draws without them. - These text envelopes are read only from adopted history. A live prompt's text is always the person's, so a client can't pass its words off as another agent's. Only acpmux's socket-only provenance can do that (
prompt_provenance.rs). - A row keeps the sender's words, or the cleaned prompt, as its text, so copy and the minimap don't show raw XML.
- A message from another agent doesn't come back through ā prompt recall. Task results (
offer_result) are excluded from it now too.
No new strings: the card reuses message.label, message.self, message.more/less and subagents.open. Nothing new is gated, since the session tools ship on by default.
Parsing is in conversation/incomingMessage.ts. direct.ts sets row.incoming on a user_message row and App.tsx draws it.
Verification
conversation/incomingMessage.test.ts(bun): red on 7ccd9e866d99 (the module didn't exist yet), green on the fix. It covers provenance with and withoutfromName, a person's prompt that quotes an envelope (stays the person's), an unclosed envelope, entity decoding, and the notification and hidden cases.test/agent-pane-suggested-tasks.test.tsgains a "messages from other agents" block in the real pane against the stand-in daemon, with the suggested-tasks toggle off: the provenance card with Open, the cross-session card with no raw XML and the envelope reachable when opened, and the notification and reminder cases. It's a browser-lane test, so it runs in CI only. I haven't run it locally.tsc --noEmitand the existingconversation/bun tests pass.verify-local.py --affectedfails only atlocalization, with 34k parity errors in catalogs this PR doesn't touch (same on base).- A correctness review subagent found four issues: a reminder before a typed prompt hid the prompt, bookkeeping-only rows left an empty focusable gap, live prompts could forge a card, and numeric entities didn't decode. All four are fixed in 3d13829d603, with unit and browser cases added.
- Also:
send_to_sessioncalls from the cmux-sessions MCP server (#21858) draw as message cards, not raw MCP rows (agentMessages.test.ts, red on 7c2f0e9c6c98). - Not dogfooded in the app. A queued agent message still shows in the composer queue as an ordinary queued prompt.
Changelog
- Changed: cmux NEXT draws another agent's message to a chat as a card from that agent, not as your own prompt, and no longer shows Claude Code's raw message envelopes.
š¤ Generated with Claude Code