Repository navigation
feat(native-chat): show which agent a chat message is from, and open it - #25888
Conversation
A message another agent sends into a native chat (today the orchestration mail notice) looked exactly like the person's own: a right-hand bubble and a queued card identical to theirs. The sender was recorded only on the queue row, so a message sent to an idle chat, or a card once it was sent, had no sender at all. The sender now lives on the message itself (`from` on the message body): - The send path carries it on both deliveries; an idle chat records it too, and it survives the queue, /clear's carry, restart and history. - No fingerprint covers it: one shared payload projection leaves it out at every fingerprint site, so stored digests are unchanged and the provider's echo still folds into the same bubble. The provider never receives it. - A client cannot set it: its send params stay strict. - One reader normalizes it where it is read (journal load, queued row, client projection); a newer message kind keeps the sender an agent's. - Each sender keeps a name snapshot from names Orca controls: a chat's conversation name or agent label, a terminal's own tab title or agent, a federated sender's task name. Never an agent-set terminal title. - The separate queued-row sender column and its plumbing are removed; it never shipped. Desktop shows such a message as a left-aligned "Message from <name>" row; the name opens the sender: its chat through the existing open-chat flow (after a host lookup that follows /clear and dispatch addresses), or its terminal through the terminal-link path, with the existing feedback when it is gone. A queued card shows a plain "From <name>" line. Mobile shows the same attribution, not yet clickable.
…ssage-sender # Conflicts: # src/shared/native-chat-types.ts # src/shared/structured-agent-session-projection.ts
… for it Review fixes for showing who an agent message is from. - Names: a sender is named by its dispatch's task (local workers too, not only federated ones), else a chat by the label its tab shows, else a terminal by its tab's stored title, else its agent. The conversation name the snapshot read before has no writer, so every chat read "<Agent> Chat". - One sender builder (agentMessageSender) and a public runtime naming object (orchestrationSenderNames) any send site can use; the terminal naming moves out of the unrelated runtime layer into its own module. - Opening a terminal sender always asks the chat's host: a terminal handle is issued per run, so the host finds an earlier run's terminal again through the pane its mail was sent from. The pane key never leaves the host. - Feedback follows what the host found: a gone chat or a gone pane by the kind it lost, "pane no longer available" only when the terminal answer proves it, and the existing "couldn't reach" / "needs a newer Orca" words otherwise. - Another agent's message is not offered rewind (it would put the agent's text in the person's composer), and a Codex rewind keeps the sender on the messages it retains. - The queued card and mobile name every sender, two that share a name included, and count in "+N" only those left out; the transcript row separates names with ", ". - The host no longer normalizes the sender when loading the journal or the queue: nothing on the host reads it, and clients read it through the one reader at their projections.
…pen-sender failures - A worker's own worker_done settles its dispatch before the mail lane names who the report is from, so a terminal worker's completion read "Codex". A local worker is now named by the dispatch it holds, else the one it last held (handles are per run, so that is this run's). The coordinator mail rig now attests the worker's process, so its report is accepted and settles as it does in production. - Opening a sender: a version block or an unknown answer says "Update Orca to open it.", since either side may be the older one; "This needs a newer Orca on the computer running this chat" stays only for a host that predates the lookup. "Orca couldn't reach the agent." is followed by "Try again.", each sentence translated on its own. No new copy.
…essage #24660 records on each submission who it is from (`source.kind`) so a restart or a close keeps only a person's unsent send as a card. That record and its keep rule are kept exactly; the kind is now read off what the send carries, in one place (sendPlan): a person's send ('user': a client's, or a launch's first prompt the host sends for them), another agent's message whose body names its sender ('agent'), or neither (a dispatch preamble, a restart continuation). The queued card's own source column is dropped again: the sender is on the card's body, and kept cards are written without it.
A worker with no active dispatch was named by the newest dispatch ever assigned to its terminal, at any status and any age: one that failed before it ran, or a task finished long ago that its later, unrelated mail kept showing. Now a worker is named by its active dispatch, else by the dispatch its own worker_done in the announced mail names (only if that dispatch is its own), else by its tab or agent as any terminal is.
…d body; mobile card fixtures name their attribution - The rewind's held-sender restore lives with the retained rows and reads both bodies through the journal's message-body check, instead of a shared helper taking any object (anti-slop: no-object-parameters). - Mobile queued-card test fixtures carry the attribution field the card gained.
…builder moves out of the mail lane - A federated sender (dispatch:<id>) runs on another host, which the chat's host cannot open: its name is plain text, and the host's lookup answers 'not found' rather than 'gone' for a dispatch it does not run. Not seeing a worker is no evidence it stopped. - The lookup calls a chat gone only when the identity is proven lost; any other failure reaches the client as 'could not reach'. - A sender open that throws shows 'couldn't reach the agent' instead of an unhandled rejection. - agentMessageSender, the one sender builder, lives in its own module, so the runtime's naming no longer imports from the mail lane.
…ssage-sender # Conflicts: # mobile/src/session/MobileNativeChatMessage.tsx # src/shared/agent-session-journal-types.ts # src/shared/structured-agent-session-projection.ts
Review summaryThe problem. An agent's message into a native chat (today the "You have N orchestration messages" notice) looked exactly like the person's own: a right-hand bubble, and an identical queued card. The sender was recorded only on the queued card's database row. So a notice sent to an idle chat had no sender at all, and once a card was sent its sender was lost. What changes for the user.
How. The sender is stored once, on the message itself ( Fixed during review (four review rounds, an architecture challenge, a readiness pass):
Deferred (none blocks this PR):
Verified:
Not verified live:
Each of these is covered by unit or integration tests only. |
📝 WalkthroughWalkthroughMessage provenance now resides on message bodies, and queued-message storage no longer persists a separate source value. Runtime code resolves sender names and party locations. Native and mobile chat project and display sender labels on transcript messages and queued cards. Attributed messages use agent-specific presentation and are excluded from rewind eligibility. Priority: ➖ Normal Merge Risk: 🔵 Low · up to Agent sender labels can be truncated or fail to scale with chat text size, which makes some senders harder to identify. These are cosmetic and can be fixed in follow-up work. Core messaging and fingerprinting behavior is unaffected. 🚥 Pre-merge checks | ✅ 4 | ❓ 1❌ Failed checks (1 inconclusive)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 54.10% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 61 functions across 50 files. (40 skipped: 6 unsupported, 34 over the file limit.) ✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
620cadad-fa9d-4bd4-869e-b7634c240912
⛔ Files ignored due to path filters (1)
src/shared/rpc-contract/rpc-params-catalog.generated.tsis excluded by!**/*.generated.*
📒 Files selected for processing (93)
mobile/src/session/MobileNativeChatMessage.test.tsmobile/src/session/MobileNativeChatMessage.tsxmobile/src/session/MobileNativeChatQueuedMessages.test.tsxmobile/src/session/MobileNativeChatQueuedMessages.tsxmobile/src/session/mobile-agent-message-attribution.test.tsmobile/src/session/mobile-agent-message-attribution.tsmobile/src/session/mobile-native-chat-message-styles.tsmobile/src/session/mobile-structured-queued-message-cards.test.tsmobile/src/session/mobile-structured-queued-message-cards.tsmobile/src/session/use-mobile-native-chat-queued-slot.test.tsxmobile/src/session/use-mobile-structured-agent-session-queued.test.tsxsrc/main/native-chat/agent-session-journal/journal-queued-messages.tssrc/main/native-chat/agent-session-journal/journal-reducer.test.tssrc/main/native-chat/agent-session-journal/journal-reducer.tssrc/main/native-chat/agent-session-journal/journal-row-builders.tssrc/main/native-chat/agent-session-journal/journal-submission-queued-link.test.tssrc/main/native-chat/agent-session-journal/journal-unsent-send-hold.test.tssrc/main/native-chat/agent-session-journal/journal-unsent-send-hold.tssrc/main/native-chat/agent-session-journal/queued-message-bookkeeping-failure.test.tssrc/main/native-chat/agent-session-journal/queued-message-delivered-echo.test.tssrc/main/native-chat/agent-session-journal/queued-message-delivered-echo.tssrc/main/native-chat/agent-session-journal/queued-message-pause.test.tssrc/main/native-chat/agent-session-journal/queued-message-schema.tssrc/main/native-chat/agent-session-journal/queued-message-store.test.tssrc/main/native-chat/agent-session-journal/queued-message-stored-row.tssrc/main/native-chat/agent-session-journal/queued-message-table.tssrc/main/native-chat/agent-session-wire/structured-agent-session-host-mutations.tssrc/main/native-chat/agent-session-wire/structured-agent-session-mutation-plans.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-message-rig.test-fixture.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-messages.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-messages.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-mutations.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-send.tssrc/main/native-chat/agent-session-wire/structured-agent-session-restore-without-import.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-unsent-send-hold.test.tssrc/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.test.tssrc/main/native-chat/agent-session-wire/structured-rewind-retained-host-rows.tssrc/main/runtime/orca-runtime-stop-requested-pty-ids.tssrc/main/runtime/orchestration/agent-facing-parity.test.tssrc/main/runtime/orchestration/agent-message-sender.tssrc/main/runtime/orchestration/orchestration-party-location.test.tssrc/main/runtime/orchestration/orchestration-party-location.tssrc/main/runtime/orchestration/orchestration-party.tssrc/main/runtime/orchestration/orchestration-sender-name.tssrc/main/runtime/orchestration/send-agent-turn-host.test.tssrc/main/runtime/orchestration/send-agent-turn.test.tssrc/main/runtime/orchestration/send-agent-turn.tssrc/main/runtime/orchestration/structured-mail-source.test.tssrc/main/runtime/orchestration/structured-mail-source.tssrc/main/runtime/orchestration/structured-mailbox-pointer-delivery.test.tssrc/main/runtime/orchestration/structured-mailbox-pointer-delivery.tssrc/main/runtime/orchestration/structured-mailbox-pointer-host.test.tssrc/main/runtime/orchestration/structured-mailbox-pointer-host.tssrc/main/runtime/rpc/methods/agent-launch-structured-prompt.test.tssrc/main/runtime/rpc/methods/agent-launch-structured-prompt.tssrc/main/runtime/rpc/methods/orchestration/caller-show.tssrc/main/runtime/rpc/orchestration-party-addressing.test.tssrc/main/runtime/rpc/orchestration-session-caller.test.tssrc/main/runtime/runtime-orchestration-sender-names.test.tssrc/main/runtime/runtime-orchestration-sender-names.tssrc/main/runtime/structured-chat-coordinator-mail-queue.test.tssrc/main/runtime/structured-chat-coordinator-mail-rig.test-fixture.tssrc/renderer/src/components/native-chat/NativeChatAgentMessageRow.test.tsxsrc/renderer/src/components/native-chat/NativeChatAgentMessageSenders.tsxsrc/renderer/src/components/native-chat/NativeChatMessageRow.tsxsrc/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsxsrc/renderer/src/components/native-chat/native-chat-agent-message-sender-label.tssrc/renderer/src/components/native-chat/native-chat-rewind-eligibility.tssrc/renderer/src/components/native-chat/structured-agent-session-queued-cards.test.tssrc/renderer/src/components/native-chat/structured-agent-session-queued-cards.tssrc/renderer/src/components/native-chat/use-native-chat-rewind.test.tsxsrc/renderer/src/components/terminal-pane/stale-agent-row.tssrc/renderer/src/components/terminal-pane/terminal-handle-links.tssrc/renderer/src/i18n/locales/en.jsonsrc/renderer/src/i18n/locales/es.jsonsrc/renderer/src/i18n/locales/fr.jsonsrc/renderer/src/i18n/locales/ja.jsonsrc/renderer/src/i18n/locales/ko.jsonsrc/renderer/src/i18n/locales/zh.jsonsrc/renderer/src/lib/activate-ai-vault-structured-session.tssrc/renderer/src/lib/open-agent-message-sender.test.tssrc/renderer/src/lib/open-agent-message-sender.tssrc/shared/agent-session-journal-types.tssrc/shared/agent-session-message-source.test.tssrc/shared/agent-session-message-source.tssrc/shared/native-chat-types.tssrc/shared/orchestration-caller-status.tssrc/shared/rpc-contract/orchestration-params.tssrc/shared/structured-agent-session-projection-sender.test.tssrc/shared/structured-agent-session-projection.tssrc/shared/structured-agent-session-send-mutation.test.tssrc/shared/structured-agent-session-send-mutation.tstests/e2e/cross-version-wire/kept-card-downgrade.unit.test.ts
💤 Files with no reviewable changes (3)
- src/main/runtime/orchestration/structured-mailbox-pointer-host.ts
- src/main/native-chat/agent-session-journal/journal-queued-messages.ts
- src/main/native-chat/agent-session-wire/structured-agent-session-queued-send.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 5 remain after this review.
| <Text selectable={INLINE_TEXT_SELECTION} style={styles.agentAttribution}> | ||
| {attribution} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Scale the sender label with fontScale.
When a person enlarges chat text, Prose scales but this new label stays at typography.metaSize. Apply fontScale to the attribution text so the sender remains readable at the selected size.
| <Text style={styles.caption} numberOfLines={1}> | ||
| {card.attribution} |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Keep the full sender attribution readable.
When an agent has a long task name or a card lists multiple senders, numberOfLines={1} truncates the new caption. The mobile card has no hover title to reveal the hidden names. Let this caption wrap, or provide another way to read its full text.
| <span className="max-w-48 truncate">{agentMessageSenderLabel(sender)}</span> | ||
| </Button> | ||
| ) : ( | ||
| <span className="max-w-48 truncate px-2">{agentMessageSenderLabel(sender)}</span> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make full sender names available when labels truncate. Long task-derived names can share a visible prefix. When truncation hides the rest, a user cannot identify the sender before opening it or reading its queued card.
src/renderer/src/components/native-chat/NativeChatAgentMessageSenders.tsx#L40-L43: expose each full name for both the button and plain-text label; support focus as well as hover for the button.src/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsx#L149-L152: expose the full sender line on hover, as the card already does for message text.
📍 Affects 2 files
src/renderer/src/components/native-chat/NativeChatAgentMessageSenders.tsx#L40-L43(this comment)src/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsx#L149-L152
Mobile QA (iOS Simulator)Orca Mobile on the iOS Simulator, paired to an isolated Orca built from this branch (c24fd4d): real orchestration mail into a live native chat, with a stand-in model.
The six mobile test files this PR touches pass (99 tests), and the mobile test-typecheck ratchet passes. Not covered on mobile: Android, a real provider, a chat as the sender, and several senders on one notice. |
…r-stop Main's #25888 shows which agent a queued message is from: the sender moves onto the message body (`from`), and a person's send carries `userSend`. The card keeps this branch's Steer/Send rule beside main's sender line; the queue's card projection imports both. The test rig and its card test take main's `from`; a card-holds test no longer passes the removed `source` field. Locale catalogs keep both sides' new keys.
…ard-ordinary The sender moved off the queued row onto the message body on main (#25888): the row's source plumbing goes, this branch's derived holds stay.
…ueue-after-stop Rebuilt as main plus this branch's own changes against its old base: the branch carried #24586's unsquashed history, so main's squash conflicted on every shared file. The person's-Stop mail check now reads the message body's sender (`from`, from #25888) and the separate card `source` field is gone; the moved queue step takes main's fingerprint and its replay answer.
ELI5
When another agent sends a message into your native chat (today: the "You have N orchestration messages" notice), the chat showed it as if you had typed it. Now it says which agent sent it, and clicking the agent's name takes you to that agent.
The problem
Since #25078, Orca delivers an agent's orchestration notice to a native chat through the chat's own send and queue. In the chat, that notice looked exactly like your own message: a right-hand bubble, and, while the agent was busy, a queued card identical to yours. You couldn't tell that an agent wrote it, or which one.
The cause was where the sender was recorded. #25078 stored it only on the queued card's database row. So:
What changes for you
The name is what Orca showed for the sender when it wrote: the task it is working on (or just reported done), else its tab's name, else the agent's name (for example "Codex"). A notice that covers mail from several agents names each of them; two different agents with the same name stay two.
How it works
The sender now lives on the message itself. A chat message gained one optional field,
from: the senders (each with its Orca session ID or terminal handle and a name snapshot) and the orchestration record it points at (mailbox, dispatch, run and message ids). It is written once, by the host, when the agent's message is sent, on both the idle path and the queued path. The message carries it into the queued card, the sent message, the transcript, every client and /clear. Every display reads it from there. Nothing keeps a second copy: the queued-card column #25078 added (source_json) is removed. It never shipped in a release.fromis refused. A test covers this.from, at every place one is computed. A message withoutfromfingerprints exactly as before; a test pins the value.fromis an agent's), and a card no longer stores a separate copy of who wrote it.orchestration.partyLocation). A chat that was /cleared has a new session; the host follows that. A terminal has a new handle after an Orca restart; the host finds it from the pane the mail was sent from, which never leaves the host. The app then opens the chat with its existing open-chat flow, or focuses the terminal with the existing terminal-link path, and reuses their existing messages when something can't be opened.Why this approach
The alternative was to show the sender from the queued card's row. That fails for any notice sent to an idle chat, and for every message once it's sent. Recording the sender on the message is the only place it survives, and it matches the common pattern: the sender's identity is stored on the message record, and the screen derives the label from it.
Differences from the common pattern
dispatch:<id>) is named by its task but is not clickable. Temporary: this host doesn't track where that worker runs, and saying it is gone would be wrong; the follow-up opens it on its own computer.check, which names each sender.Compatibility
fromand shows the same.orchestration.partyLocationis a new method. On a host that predates it, clicking shows Orca's existing "needs a newer Orca on the computer running this chat" message.from. Intended: this only happens on a downgrade, and only for cards already waiting.What you might notice
orca terminal rename, including by an agent.Linked Issue
N/A: maintainer change, no issue.
Visual Proof
Live run on macOS (an M4 MacBook Air): real orchestration mail from two plain terminal agents into a live native Codex chat. The chat's model was a stand-in, because the isolated test profile has no account. Before =
main; after = this branch.Clicking a name opens that agent's terminal, switching worktree when it's in another one: same worktree, other worktree. Sender closed: one "no longer available" message. Rewind is offered only on your own messages: agent's row, your row.
Testing
Live run (macOS), at the last commit before the final merge from
main. That merge only brought inmain's reasoning-row change.Not run live:
Those paths are covered by the tests below.
Automated tests. Every behaviour test added here was checked by removing the code it covers and watching it fail.
from; the same message withfromfingerprints identically.fromis refused.source_jsoncolumn keeps working.params.sourcebecameparams.personsMessage). A new test proves a launch's first prompt is still kept as yours.The node, web and CLI typechecks, oxlint, the anti-slop audit and the changed-code quality gate pass locally. About 3,200 tests across the touched areas pass. The mobile component tests can't run on this machine; CI's Mobile Checks pass.