Skip to content

fix(native-chat): no saved outbox on the desktop; one send at a time, and the host owns what it accepted - #25959

Merged
brennanb2025 merged 51 commits into
mainfrom
brennanb2025/nc-host-send-queue
Oct 7, 2026
Merged

brennanb2025 merged 51 commits into
mainfrom
brennanb2025/nc-host-send-queue

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 101 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3454 $\color{#cf222e}{\Huge{\mathbf{−}}}$​9645 $\color{#cf222e}{\Huge{\mathbf{−}}}$​6191
Prod 94 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2050 $\color{#cf222e}{\Huge{\mathbf{−}}}$​3017 $\color{#cf222e}{\Huge{\mathbf{−}}}$​967

ELI5

In Orca's native chat, the desktop kept every message you sent in a saved list on your computer and sent the list in order. When one message's fate was unknown, Orca marked it "Message delivery is unconfirmed." and everything you typed after it waited behind it, forever, even after quitting and reopening Orca. Retry didn't free it.

Now the desktop doesn't keep a saved list. It sends one message at a time straight to the machine running the agent (the host), which already records and queues every message. Each message is sent once. If its answer is lost, or nothing answers within 30 seconds, the text goes straight back into your message box with a note to check the chat. Orca never sends it again on its own. Nothing is ever stuck behind a message whose fate is unknown.

What Changed

The problem.

  • Each structured chat on the desktop kept its unsent messages in a saved outbox (localStorage) and sent them in order. A message whose outcome was unknown (an answer lost on a dropped connection, or a host that recorded it as "unknown" after a Stop whose interrupt failed) became a barrier: every later message waited behind it, across restarts. Retry resent the same record and changed nothing (fix(native-chat): a message whose delivery is in doubt no longer holds every later send #25028).
  • Because the outbox was saved, a message typed while the host was unreachable could go out hours later when the chat reopened, long after the person had moved on. A failed message couldn't be discarded.
  • The host already records every message it accepts (its journal) and owns the queue of messages waiting for the agent, so the desktop's saved copy was a second owner of the same facts.

What you see now.

Case Before (main) After
You press Enter The message joins the saved outbox and shows "Sending…" until the host confirms it Unchanged: "Sending…" until the host confirms it, normally a few milliseconds
You press Enter again while a message is still on its way The second message waits in the outbox behind the first Enter doesn't send until the first one settles, and your text stays in the box. The send button is disabled, or shows Stop where the agent's host can stop the whole conversation
You send while a /clear is still running The message joined the outbox; the host refused it and it was left with the old conversation, out of sight Send shows busy and Enter doesn't send; your text stays in the box until the /clear finishes. (Text still in the box when the /clear moves the chat to its new conversation stays with the old one, as on main; tracked as its own fix.)
You send while a new chat is still starting The message waits in the outbox until the chat is up Send is disabled and your text stays in the box until the chat is up. A launch prompt shows as "Sending…" from the click and goes first
You send into a chat whose start failed The chat restarts and the saved message goes once it is up Unchanged for text: the chat restarts and your message goes as its first message once it is up. If the restart fails again, the text comes back to the box. A message with images restarts the chat and stays in the box; you press Send again once the chat is up
A new chat fails to start before its first message (launch prompt) went out The prompt waited in the outbox and went out on Retry, or when the host came back The prompt goes back into the box with the reason. Retry restarts the chat; you press Send to send it. Nothing sends on its own after it has come back to you
The connection drops before the host answers Orca resends from the saved outbox, with no time limit; after some failures "Message delivery is unconfirmed." with Retry, and every later message waits The text goes back into the box at once (or 30 seconds after Enter if nothing answers at all), after anything you've typed, with "Orca couldn't confirm your message reached the agent. Check the chat, then send it again if needed." Nothing is sent again on its own. If the host did record it, it is also in the chat. The same applies to a host restart mid-send and to an answer that proves nothing
The host refuses the message before recording it "Your message was not sent." with Retry The text goes back into the box with the reason. No Retry: you send it again yourself
The host recorded the message and then rejected it Shown as not sent on the message's own row, in the host's words (#24710) Unchanged. That row is the only "not sent" place; the text is not also put back in the box
The host recorded the message as in doubt after a Stop Message became a barrier; every later send waited (#25028) It's the host's message like any other; later sends go out
You press Stop before your message has left this window (Orca is still checking the connection, normally milliseconds) Its text goes back into the box if the box is empty; otherwise it stays as "Message was not sent." with Retry (#25051) It is never sent; its text goes back into the box after anything you've typed. No Retry
You press Stop after your message went out and the host takes it back It stays in the chat with one "Stopped before the agent started" row; the box is untouched (#25051) Unchanged
You close the chat's tab while a message is on its way The saved copy was discarded The send still finishes: if the host took it, it's in the chat; if not, the text is in that conversation's saved draft
Notes sent to a chat that already has a message on its way, or is still starting Joined the saved outbox Refused as "not ready"; the notes keep their text. Notes whose send can't be confirmed keep their text and say "Orca couldn't confirm…" rather than "not accepted"
Images sent from an SSH workspace that come back to the box n/a (kept in the outbox) Keep their remote connection, so they still open on the remote host
A message an older Orca saved in the outbox Sent on its own when the chat opened Never sent. The first time the chat opens, each one the host doesn't hold goes back into the box once; ones the host recorded stay on their row. The saved copy is then deleted

The mechanism.

  • One in-memory sender (structured-agent-session-message-sender.ts) is the only path to agentSession.send for the composer, launch prompts and messages sent from outside the chat. It keeps at most one send per chat, in memory only, until the host's answer or its journal settles it. structured-agent-session-pending-sends.ts holds that state and the chat's last hand-back reason; the composer reads sendOut to disable Send. A launch prompt, or a message sent into a chat whose start failed, reserves that one send from the click and goes out once the chat is published (structured-agent-session-send-slot.ts, lib/structured-agent-session-launch-message.ts).
  • Each send makes exactly one request. src/shared/structured-agent-session-send-evidence.ts reads its answer one of three ways: recorded (a row in any state, including rejected, withdrawn or unknown, or a queued card), not recorded (a refusal the host returned, or a call it turned away before running it) which hands the text back with the reason, or uncertain (a timeout, a dropped connection, a thrown refusal, "outcome unknown") which hands it back with "couldn't confirm… check the chat". A failure before the request goes out (the compatibility check, the history read) hands the text back at once as not sent, with its reason.
  • A Stop or a tab close (stopStructuredAgentSessionSends) hands a send that has not left the window back to the draft, and it is never sent. A request already out settles from its answer, or at the 30 s deadline. A message the host recorded and a Stop then withdrew stays in the chat with its stop row (main's rule since feat(native-chat): keep a message a Stop took back on screen, with one stop row after it #25051). Only a cancelled launch, or purging a worktree whose launch is still in progress, drops a send.
  • structured-agent-session-legacy-outbox.ts reads an older build's saved outbox once per chat, asks the host's conversation outline which messages it holds, hands back the rest, never sends, and deletes the copy once the hand-back is saved. Temporary: remove once builds that wrote it have aged out.
  • The saved outbox, its retry, reconcile and settlement code and their tests are deleted (about 8,700 lines net). No wire, host or saved-field change.

Phone. The phone never had the desktop outbox: it sends each message directly to the host, which owns any queue, so a message in doubt holds nothing back there. Nothing changes on the phone.

Why

  • The host already owns accepted messages. Its journal records each one under its id and its queue holds the ones waiting for the agent. A second saved copy in the window can only disagree with it, and every bug above came from that disagreement.
  • In memory, cleared at Enter, settled after the chat closes, put back into that conversation's draft on failure: this is the common pattern for desktop chat clients, by mechanism.
  • One message at a time with Enter refused meanwhile, rather than a line of messages in the window: also the common pattern. A line in the window is a client-side queue, and a message waiting in it could run out of time without ever being sent.
  • Alternatives considered: keep the outbox and stop treating "in doubt" as a barrier (fix(native-chat): a message whose delivery is in doubt no longer holds every later send #25028, fix(native-chat): every chat send ends one of three ways, decided by the host's answer #25207). Both keep a saved second owner of what the host accepted, with its own migration, Retry and settlement rules. This PR replaces them.

Differences from the common pattern

  • Follows the common pattern: one request per send. A lost or unproving answer hands the text back at once; nothing is ever sent again on its own.
  • Intended: every send ends within 30 seconds. The common pattern has no deadline while the connection stays up, so a hung host would hold the send, and the Send button, forever. That is the same kind of freeze as fix(native-chat): a message whose delivery is in doubt no longer holds every later send #25028.
  • Intended: returned text goes after whatever is already in the box. The common pattern restores only into an empty box, which drops text typed while the message was sending.
  • Follows the common pattern, listed because it was an open choice: a crash in the few milliseconds between Enter and the host recording the message loses it. The box is cleared at Enter and the message is held only in memory, as in the common pattern. Saving a "sending" copy at Enter was considered and not taken, to keep a single owner of accepted messages.
  • Intended: a Stop takes back, in the window, a send whose request has not left yet. The common pattern either runs the Stop after the send or has the server refuse it. Orca's host accepts messages while a turn is stopping, so a request leaving after the Stop would start or queue work the person just stopped. The window is the send's pre-checks, normally milliseconds. The text goes back to the box (after anything typed) rather than a "not sent" row with Retry, because this layer has no Retry and keeps no copy to draw.
  • Temporary: Stop while a new chat is still starting takes its first message back into the box. This rule is kept from main (feat(native-chat): Grok as a structured chat over the Agent Client Protocol #25225, ported onto the in-memory sender here): Stop is shown before the host has published the chat, and pressing it puts the first message back into the box unsent. The common pattern doesn't offer Stop until the host holds the first message; Stop then cancels the start on the host, and the message stays in the chat with its stop row, never going back to the box. Save a new chat's first message during create #26332 switches to that behaviour and removes this rule.
  • Temporary: the one-time read of an older build's saved outbox. Builds before this change wrote it. In the first release on or after 2027-04-01, this read is replaced by a one-time removal of every saved key.

Linked Issue

No issue. Supersedes #25207 and #25028 (both closed with pointers here). Keeps main's #25051 behaviour as is: a stopped message stays in the chat with one stop row, and the box is untouched.

Visual Proof

Live checks in a hidden test build of Orca with a stand-in Codex agent (no real agent). Each heading names the commits its shots came from. The current head 330c8d7 is 7656657 with main 81ba5c3 merged in. On 330c8d7 these passed: normal send, Enter while a send is out, a first prompt from a new agent tab and from a desktop AI button ("Explain changes" on a commit), Stop before the chat has started, and a send during a slow /clear. The other cases passed on 7656657: dropped and hanging requests, Stop while a send is out, a tab closed mid-send, a failed start (twice), older saved messages (seeded, and a real upgrade from main), and the #25028 freeze with a relaunch.

Pressing Enter while an earlier message is still being sent

main (81ba5c3) this PR (330c8d7)
c2 main c2 PR
"c2 third" leaves the box and waits as a second "Sending…" bubble; afterwards the chat stays on "Working…". "c2 third" stays in the box until "c2 second" settles; the next Enter sends it once.

Sending while /clear is still running

main (81ba5c3) this PR (330c8d7)
c4 main c4 PR
The message shows "Sending…", then the clear wipes it: never sent, never returned, no notice. Send is disabled during the clear and the text stays in the box; nothing reaches the old conversation.

c4 PR after
This PR (330c8d7), after the clear: the new conversation's box is empty because the text is saved as the old conversation's draft; carrying it over is a separate follow-up.

Upgrading with messages left in main's saved outbox (same profile)

main (385fe8a) this PR (7656657)
c9b main c9b PR
After a Stop the interrupt fails: "Message delivery is unconfirmed." with Retry, and both later messages sit on "Sending…". After upgrading, both come back to the box once with "Your message was not sent. Send it again."; nothing old is sent, and a new message goes.

Stop with a refused interrupt, then two more messages (the #25028 freeze)

main (d6b155d) this PR (7656657)
c10 main c10 PR
"Message delivery is unconfirmed." plus Retry, and both later messages stuck on "Sending…" (still stuck after a restart). Both later messages reach the agent; no stuck notice.

c10 PR relaunch
This PR (7656657), the same chat after quitting and reopening Orca: all four messages, no notice.

A failed chat that fails again when you send

main (d6b155d) this PR (7656657)
c8b main c8b PR
The restart fails and the message sits on "Sending…" forever; it never reaches the agent. The restart fails, the text stays in the box under "Chat could not be started.", and nothing is sent (60 s later).

Stop while a second message is still being sent (unchanged from main)

main (51062e6) this PR (7656657)
c6 main c6 PR
The withdrawn message stays with "Stopped before the agent started"; the box is left alone. Same: one stop row, box untouched, chat idle again 8 s after the Stop.

This PR only

c3 AI button
330c8d7: "Explain changes" on a commit opens a chat whose prompt shows "Sending…" from the click; text typed while it starts stays in the box, and the prompt is the agent's first message.

c3 new agent tab
330c8d7: the same for a first prompt from a new agent tab.

c11 stop before start
330c8d7: Stop while the first prompt waits for the chat to start; the prompt goes back into the box after the typed text and is never sent, even once the chat is up.

c5b PR
7656657: a send whose request hangs comes back to the box 30 s after Enter, with "Orca couldn't confirm…" above it; it is sent once, never again.

c7 PR
7656657: the tab was closed while "c7 second" was on its way; reopening the conversation shows it as a draft: "Not sent yet — press Send to send it."

c9a PR
7656657: an older build's saved message comes back to the box once; the one the host rejected stays only on its row; nothing is sent.

Windows (d5a40b5), Windows high spec

Windows c5b
Windows, d5a40b5: a send whose request hangs comes back to the box 30 s after Enter with "Orca couldn't confirm…" above it; sent once.

Windows c11
Windows, d5a40b5: Stop while a new chat's first prompt waits for the start; the prompt goes back into the box after the typed text and is never sent.

Windows c2
Windows, d5a40b5: Enter while "c2 second" is still being sent leaves "c2 third" in the box.

Paired Orca server (Windows high spec), head 6afd0c6. The desktop client is the PR build. The chat runs on a headless paired Orca server, built from either this PR or main 80d4509 (the main this head merged).

server client check result
PR 6afd0c6 PR 6afd0c6 normal send one bubble, one request
PR 6afd0c6 PR 6afd0c6 request hangs (server's agent restarting slowly) back in the box at ~30 s with "Orca couldn't confirm…"; one request (delivered late, at ~41 s)
PR 6afd0c6 PR 6afd0c6 server re-paired while sending not reached: re-pairing an existing server through addFromPairingCode is refused ("A server named … already exists"), so no send raced a pairing change
main 80d4509 PR 6afd0c6 normal send one bubble, one request
main 80d4509 PR 6afd0c6 request hangs back in the box at ~30 s with "Orca couldn't confirm…"; one request

paired PR normal send
Server PR, client PR: normal send to a chat on the paired server.

paired PR hanging request
Server PR, client PR: the hanging send is back in the box with "Orca couldn't confirm…"; the server delivered it late, so it is also in the chat.

paired PR re-pair
Server PR, client PR: re-pair attempts were refused, so every send simply went through (the "not sent" path was not reached live).

paired main normal send
Server main 80d4509, client PR: normal send.

paired main hanging request
Server main 80d4509, client PR: the hanging send is back in the box with "Orca couldn't confirm…"; one request.

Attachments (Windows high spec), head 7df3e6e

chat attachment result
on a paired Orca server (server PR, client PR) text file, dropped onto the box one bubble; one request whose text carries the message and the file's path on the server; the uploaded file is on the server with the same SHA-256 as the original
local, no pairing PNG image, dropped onto the box one bubble with the image; one request with the text and the image (as a local image path)

attach remote
Paired server, 7df3e6e: a dropped text file is uploaded to the server and sent as its server path with the message, in one request.

attach local
Local chat, 7df3e6e: a dropped image is sent with the message in one request.

Testing

  • Unit tests pin each rule above. Each was checked by removing its fix and seeing the tests fail: the fix(native-chat): a message whose delivery is in doubt no longer holds every later send #25028 freeze (an in-doubt send never given back), refusing a second send, Send staying disabled until the send settles, one request per send (nothing is ever sent twice), a Stop taking back a send still being readied while a send already out settles from its answer, a tab close not dropping a send, a host rejection not also handed back, SSH images keeping their connection, notes refused while busy, and old saved entries handed back once and never sent.

  • On the final head (330c8d7): tc:node, tc:web and tc:cli pass; 325 affected test files (4,802 tests) pass; localization, reliability gates and code-quality checks pass; CI is green on the first attempt. Mobile typecheck and test-typecheck ratchet pass.

  • I manually tested these changes locally (live QA on a second Mac against main; screenshots above)

  • Automated tests added/updated, or explained why not below

Review

Review loop and live QA done; summary in the status comment: #25959 (comment)

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • SSH: the sender targets whichever host runs the chat; returned images keep their SSH connection. Loss of contact is read as "couldn't confirm", never as "not sent".
  • Mixed versions: no wire change. A send is one request whatever the host's version.
  • Follow-up: Save a new chat's first message during create #26332, a new chat's first message is recorded by the create itself. The create returns before the agent starts, Stop after create goes to the host, and older hosts keep this PR's behaviour. It replaces the Temporary Stop-before-start rule above.
  • Follow-up, not in this PR (phone, unchanged here): the phone keeps the id of a message the host left in doubt, so it never sends that exact text twice. If the host never drops that record, re-sending the same text in that chat keeps answering "Delivery unconfirmed" with no way out. Track it as its own phone fix.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…aved outbox

The desktop kept each structured chat's unsent messages in localStorage and
sent them in order, so one message whose fate was unknown froze every later
send, Retry dropped it silently, failed sends could not be discarded, and an
offline chat could send hours later. The host already records every message
and owns the queue; the window now only sends.

- One in-memory sender for composer, launch prompts and messages sent from
  outside the chat. One send in flight per chat; a transport failure resends
  the same id for up to 30 s; a refusal that proves nothing was recorded puts
  the text back in the composer with the reason; a send that went out and was
  never answered shows an in-doubt line with Send again and holds nothing up.
- The host's "unknown" rows get the same in-doubt line and Send again, from
  the journal, in every window.
- A message an older build left in localStorage is never sent: the host's
  conversation outline decides what goes back to the composer, and the copy is
  deleted once that is saved.
…its timeouts

The sender called the runtime RPC directly, skipping the per-method timeouts
every other structured chat call gets. Only a remote host's request skips the
compatibility check the sender already ran.
… no new row line

The common pattern draws nothing extra on a message whose delivery is in
doubt once its turn is over, and puts a failed send's text back in the
composer with the reason. So a send nobody answered in time, or one a host
answers in a way that proves nothing, goes back to the composer worded as
unconfirmed, and a host "unknown" row shows nothing extra. Removes the in-doubt
phase, Send again, and its strings.

A host's made-up row for an id its journal lost now reads as unconfirmed, not
as recorded, so that message comes back instead of vanishing.
…send-queue

# Conflicts:
#	src/renderer/src/components/native-chat/NativeChatMessageList.unsent-message.test.tsx
#	src/renderer/src/components/native-chat/NativeChatMessageRow.tsx
#	src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.probe.test.tsx
#	src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx
#	src/renderer/src/components/native-chat/structured-agent-session-withdrawn-message-restore.ts
#	src/renderer/src/components/native-chat/use-structured-agent-session-outbox-rejection-cause.test.tsx
#	src/renderer/src/components/native-chat/use-structured-agent-session.ts
…p, and keep remote images

- A normal tab close lets sends on their way settle; what the host never took
  goes back to the conversation's draft. Only a cancelled launch or a worktree
  purge drops them.
- After any Stop, a send already on its way is never sent again under its id:
  a doubtful answer, or a resend that was due, hands its text back worded as
  unconfirmed.
- A returned image keeps the SSH connection it lives on; the connection never
  goes to the host.
- An older build's saved message the host recorded and then rejected is left
  to the host's own row, never handed back.
…out, and a kept card is the card's

A Stop that landed while a same-id resend was being readied (its timer
fired, its request not out yet) let that resend go out after the Stop.
The sender now tracks whether a request is awaiting its answer: a Stop
hands back every send between attempts as unconfirmed, lets one whose
request is out settle from its answer, and never issues a request after
it. A normal tab close withdraws the same way instead of doing nothing,
so nothing more goes out and nothing is dropped.

A send the host rejected but kept as a card (keptAsQueuedMessageId) is
the card's, from its reply or the journal, and never goes back to the
composer.

Adds the freeze tests: a send whose fate is unknown holds later sends
no longer than its deadline, and one the host can neither confirm nor
deny releases the next at once.
…proven

Ports the send-answer proof contract. A call the host turned away before
running it (method_not_found, invalid_argument, unauthorized) proves only
that this request wrote nothing, so it reads as never sent on a first
attempt only; on a resend an earlier attempt may have landed, and it goes
again under the same id. A resent id the host says was already used for
other content (messageIdReused) proves nothing either, like an expired or
conflicting id.

Pins the rest of the contract: any row the host returns is its own, a
thrown error is no answer whatever its code, and an older build's
refused, held or outlived-Stop copy is handed back once and never sent.
…ttachment type

Keeps NativeChatStructuredSession.tsx within the file length limit.
A kept send's row is a rejection that was never a Stop's, so the sender
already reads it as recorded, from its reply or the journal. The test
that pins it stays; the two extra checks only covered a kept row that is
also a withdrawal, which the host never writes.
…er the sender calls

The sender sends through callStructuredAgentSession, but these launch
tests replaced that module with a factory that answered nothing (and still
named a probe that no longer exists), so every launch prompt resent until
its 30 s deadline and the tests timed out. Each factory now hands sends to
the runtime RPC mock the tests already answer, and expectations of a local
send no longer ask for the remote-only compatibility option.
…s attachment hook

The worktree purge reaches the launch prompt, which hands text back, and
the attachment hook's imports reach the store. A test that builds the
real store behind a mocked one then waited on itself and hung. Handing
back now writes images to the draft store directly, as the hook's helper
did, and a test pins that each returned image keeps its SSH connection.
A chat with a send out took further messages into an in-memory line and
sent them one by one. A message typed behind one in doubt then hit its
own 30 s deadline and came back as not sent without ever going out.

Now, as the common pattern does, a chat takes one send at a time: while
it is out, Send is disabled and Enter leaves the text in the box. The
sender refuses a second send instead of lining it up, so the 30 s
deadline always runs from the send itself. A Stop or a tab close stops
the one send: settled from its answer if its request is out, handed back
as unconfirmed between attempts, or silently if it never went out.

Notes sent from outside the chat while its send is out stay with their
sender (not ready); a launch prompt that meets the person's own first
message waits in the composer instead of being lost.
…s notes were cleared

Notes sent from outside a chat clear once their message is recorded. A
message the host recorded as pending and a Stop then withdrew came back
only to its sender, which had already let go of it, so the text was
lost. The chat's draft now takes it, as an earlier build's outbox did.
…d the chat for its launch prompt

- A send whose checks failed before its request went out (an unreachable
  or incompatible host, an unreadable history) was tried again at once,
  about a thousand times a second for 30 s. Attempts are now paced by
  the attempts made, whether or not their request went out.
- A host that refuses every resend by throwing (native chat turned off,
  a journal that won't open) gave back only "couldn't confirm". The
  host's reason now comes first, still without claiming not sent.
- A launch's prompt now holds the chat's one send from the click, drawn
  as sending, so a message typed while the chat starts can't overtake
  it; it goes out once the chat exists, and a cancelled launch frees it.
- Notes whose send nobody could confirm say so instead of "did not
  accept", and notes launched into a new chat let go of their text once
  that chat's composer holds it.
- An open chat keeps drawing a recorded send until its row arrives, so a
  reply that beats the history no longer makes the message flicker out.
…a failed chat's message with its restart

A message sent to a chat still starting went out at once to a host that
had no record of the chat yet, read for a fence it could not get, and
came back as not sent. A message sent to a chat whose start failed
restarted it but no longer went with the restart.

While a chat starts, Send stays off and Enter leaves the text in the box,
as with a send already out. A text message sent to a chat whose start
failed restarts it and goes as the restart's first message, through the
same staged-prompt path a launch prompt takes: it holds the chat's one
send slot, drawn as sending, until the chat exists, and comes back to
the composer if the restart fails again. Notes wait while a chat starts
and ride a failed chat's restart, keeping their text until it is sent.
…hand-offs; drop an unused clear

- Pin which sends ask the host to queue, the moved rejection wording,
  and that a queue send whose card was handed off and then refused or
  withdrawn, or whose replay names a withdrawn card, is never handed
  back.
- The send-at-most-once gate now says what the desktop promises after a
  reload: it never sends the id again, and hands the text back.
- The legacy read names when it goes, and drops the notice clear nothing
  called.
… once what can't go out

- Cleaning up a launch prompt released its send slot even after the
  prompt had gone out, which deleted the entry the sender keeps once the
  host records it. A launch prompt recorded as pending and then
  withdrawn by a Stop was lost from both the chat and the box, and an
  open chat dropped the new chat's first message before its row arrived.
  The slot now gives back only a reservation that was never sent.
- A send stopped before its request went out by something trying again
  won't clear (this client and the server can't talk, or the host
  refused the history read) kept Send off for 30 s and then said Orca
  couldn't reach the agent. It now comes back at once with its own
  cause: not sent, since nothing went out, or unconfirmed if an earlier
  attempt did. Transport errors keep the paced retries.
Notes sent to a New agent whose start failed came back to the notes and
also sat in the new chat's composer, so sending both delivered the text
twice. The notes keep their text until it goes out (they hold it from
the click), so the launch now says so and no composer gets a copy, on a
failed start or a refused prompt alike. The tests that asserted the copy
in the composer pinned the old double ownership and now assert the
notes are its only owner.

Notes that rode a failed chat's restart and ended unconfirmed now say
Orca couldn't confirm them, as notes sent directly do, instead of that
the agent did not accept them.
…ross a reload

The desktop keeps a send's id in memory only: a send still unsettled at a
reload or crash is not resent and not handed back. The coverage notes no
longer credit the renderer tests with durable identity across a remount.
…brennanb2025/nc-host-send-queue

- Queue UI (#24586) taken as main has it: paused header, Resume, Steer, clear-queue question,
  `queueSendsNext` working state, composer Resume and queue hold.
- The chat's send answers 'queued' when the transcript draws it as a card (main's rule, read through
  the in-memory sends' own projection), so the composer moves the reader only for a bubble.
- The failed-start send wrapper passes that answer through; the submit reveal hook drops the
  per-message Retry this branch removed (launch Retry still reveals).
- The pending-send projection takes any send shape, so the node project's queued-card test does not
  reach the renderer store.
- Deleted outbox files stay deleted; #24514's queued-card admission test is ported to the sends hook.
…follows the chat

A /clear moves the chat to a new conversation, and its host refuses any send while it runs. A
message sent then went out, came back refused into the old conversation's draft with its line, and
vanished when the chat moved on: the box and the line now belong to the new conversation.

- A /clear holds the chat's one send slot while it runs: Enter does nothing, Send shows busy and
  the text stays in the box, as for a send that is out. Notes sent from outside get "busy".
- Once it moves the chat, the old conversation keeps taking no send until the view leaves it, and
  what is left of its draft moves into the new conversation's draft, after anything there: when the
  composer's /clear settles, and again when the view moves.
…send is out

While a send is out (or a /clear runs) the chat takes no message, yet Enter over a held queue still
opened "Send message?", and Clear queue deleted every card before its message was refused. Enter now
does nothing there and the text stays; Clear queue re-checks and deletes nothing if a send went out
after the question opened.
Moving the old conversation's draft into the one a /clear replaces it with fixes a bug main has too
(text left in the box during a /clear stays with the old conversation), so it goes in its own change.
Kept here: a /clear holds the chat's send slot while it runs, and the conversation it moved away from
takes no send until the view leaves it.
- A view that unmounted while a /clear that moves the chat was out left the old conversation
  holding its sends until a reload: the late reply kept the hold for a view that was gone. The
  reply now releases it.
- The local call for a conversation command has no deadline of its own, so a /clear that never
  answers kept Send off for good. The hold now also ends at the command's deadline (195 s, the one
  the remote call already uses), whichever comes first.
No conflicts. Main's host outage notice (#24898) merges into the chat view beside this branch's send state; nothing in main's 7 commits sends through the desktop's outbox.
Main's #25225 moved the composer transport into
use-native-chat-structured-composer-transport.ts and Stop into
structured-agent-session-stop-control.ts, both built on the saved outbox
this branch removes. Both now read the in-memory sender: the transport
sends through sendThroughLaunch (keeps image connectionIds, sendOut while
starting), and Stop before the chat is published gives the launch's
unsent text back to the composer (takeBackStructuredLaunchPrompts) and
asks nothing of the host, as main's rule does for its outbox.
Takes main's notice card (#25835) as main has it; the chat's send errors
reach it through controller.error. The outbox Stop-withdrawal module stays
deleted: #25217 only exported its Retry test for the projection's
awaitsRetry flag, and no in-memory send ever waits on a Retry. Main's
opening-send test is ported to optimistic messages, minus its two
waiting-on-Retry cases.

Also: a launch text handed back by a Stop before publish (or a failed
start) is guarded like the sender's hand-back, so a draft write that throws
is reported and never skips the Stop; the Stop-before-publish cases for a
notes launch, an already-sent launch and a throwing hand-back are tested;
the legacy outbox comment no longer names a build number.
…k its copy in send order

Pins, through the real chat hook, sends, legacy recovery and draft store,
what opening such a chat does: the queued messages behind a message the
host recorded in doubt come back to the composer once, in order, with the
"not sent" or "couldn't confirm" wording; the chat is free to send under
its read's fence; nothing happens while the chat has no fence here.

The legacy reader now hands entries back in the order they were sent
(queuedAt), as the older build's reader did, instead of array order.
@brennanb2025

brennanb2025 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Status: review loop and live QA done (final head d5a40b5)

The problem. The desktop kept each native chat's unsent messages in a saved list (an outbox in localStorage) and sent them in order. A message whose fate was unknown blocked every later message in that chat, forever and across restarts, and Retry didn't free it (#25028). A message typed while the agent's machine was unreachable could go out hours later. The machine that runs the agent (the host) already records and queues every message it accepts, so the saved list was a second, disagreeing owner of the same facts.

What changes for you.

  • One message at a time per chat, sent straight to the host. While it's on its way, Enter doesn't send and your text stays in the box.
  • Each message is sent once. If its answer is lost or nothing answers within 30 seconds, the text comes straight back into the box with "Orca couldn't confirm your message reached the agent. Check the chat, then send it again if needed." Nothing is ever sent again on its own, and nothing is stuck behind a message whose fate is unknown.
  • If the host refuses a message before recording it, the text comes back with the reason. A message the host recorded and then rejected shows as not sent on its own row only.
  • While a new chat starts, or while a /clear runs, Send is disabled and your text stays in the box.
  • Messages an older Orca saved are never sent. The first time each chat opens, the ones the host doesn't hold come back into the box once.
  • No Retry and no new notices. A reason for a message coming back shows as a row in the existing notice card (feat(native-chat): one notice card above the message box for the chat's own errors #25835).

How it was reviewed. Four review rounds, an architecture review, a readiness checklist and five narrow checks (fresh reviewers each time), plus live QA on a second Mac. The design changed twice during review on the owner's rulings:

  • the window keeps no line of messages: Enter is refused while one is out, which is the common pattern;
  • the 30-second same-id resend was dropped: each message is sent exactly once.

Fixed during review (each with a test that fails when the fix is removed):

Deferred (P3, not blocking):

  • In-memory records for chats that are never reopened aren't cleaned up.
  • A few nits in comments and tests.
  • The old-outbox read has two unproven edge cases: it could hand an entry back twice if saving fails, and a stopped entry outside the loaded history page could be handed back.
  • Fence caching on older hosts only.
  • The recovery effect re-runs when the chat's target object changes identity (a cheap read).

Follow-ups:

Verified.

  • Live QA on a second Mac, every case run fresh on 7656657 and compared against main (screenshots in the description). All pass:
    • normal send;
    • Enter while a send is out;
    • a new chat with and without a first prompt;
    • dropped and hanging requests (back in the box at 30 s with the couldn't-confirm line, one request);
    • Stop while a send is out (stop row, box untouched);
    • closing the tab mid-send and reopening;
    • a failed start, including the restart failing again;
    • old saved messages, both seeded and a real upgrade from a main build on the same profile;
    • the fix(native-chat): a message whose delivery is in doubt no longer holds every later send #25028 freeze, with a relaunch.
  • Main fails these: it freezes in the fix(native-chat): a message whose delivery is in doubt no longer holds every later send #25028 case, sticks on "Sending…" when a restart fails again, queues a second "Sending…" bubble out of order, and shows "Message delivery is unconfirmed." with stuck messages after the upgrade.
  • Re-checked after the last main merge (81ba5c3, which changed how desktop AI buttons launch, Codex Stop settlement and the draft cache), live on the final head 330c8d7:
    • normal send;
    • Enter while a send is out;
    • the first prompt from a new agent tab and from a desktop AI button (Source Control → Explain changes): it shows "Sending…" from the click, typed text stays in the box, and the prompt is the first turn;
    • Stop before the chat starts: the prompt goes back to the box and is never sent;
    • sending during a slow /clear: Send is disabled and the text stays in the box. Main loses that message.
      All pass. The launch, Stop, draft and deleted-path behaviour of that merge were also checked independently in code.
  • After the SSH change on main (Phase 3: every SSH host runs a managed Orca server (orcad), replacing the relay, and orca serve runs on it #24863: every SSH host runs a managed Orca server), merged at d5a40b5: one fix. When a server has been re-paired, which that change makes routine after its updates, this window refuses the send before writing it. That used to read as "couldn't confirm"; it now says the message was not sent, with the reason. It was checked in code that this refusal is only ever raised before the message goes out. A dropped link still reads "couldn't confirm".
  • A frozen chat on a live Orca built from main (an unconfirmed message plus two queued ones) was traced against this branch, with a test of the real hooks: it unsticks on first open.
  • tc:node, tc:web and tc:cli pass. 325 affected test files pass. The lint, localization and reliability gates pass.

Not verified.

  • A server being re-paired mid-send wasn't reached live: re-pairing refuses an existing server, so no send ever raced it. The "not sent" answer for that case is covered by its unit test and a code review only.
  • When several older saved messages come back, they land in one box separated by a blank line, so they could be sent as one message. This is the shared returned-text rule.
  • A queued card in the live app: the host's queueing is switched off for now, so no build can show one.
  • Windows and Linux weren't run live. A live send over an SSH host wasn't run: no SSH target was available without new keys.
  • The short live re-check on d5a40b5 (normal send, Enter while sending, hanging request, /clear, Stop before start) was started, but the test Mac became unreachable mid-run, so it has no result yet. The full live QA passed on 330c8d7, and the only changes since are the formatting-only merge and the error-wording fix above.
  • A crash in the few milliseconds after Enter loses the message. This follows the common pattern and was decided.

CI on d5a40b5 (final): all green on the first attempt: unit full https://github.com/stablyai/orca/actions/runs/37608569362, Mobile Checks, PR test LoC and the headless Node server. No reds.

@coderabbitai

coderabbitai Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ed301520-f04c-4419-988e-d9ac96f6fa4f
📥 Commits

Reviewing files that changed from the base of the PR and between d5a40b5 and 74e6079.

📒 Files selected for processing (1)
  • src/renderer/src/components/native-chat/NativeChatMessageRow.tsx

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The desktop structured-session send path now uses in-memory pending sends and issues one send request per message. The sender tracks request and journal outcomes, applies a 30-second deadline, and returns eligible unsent text to the composer. Launch prompts use the sender, and transcript projection uses optimistic-message records. The change also updates queue handling, Stop and clear-command behavior, delivery notices, legacy outbox recovery, and related tests and configuration.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 74e60

A queued follow-up can briefly appear missing after the composer clears it during a narrow turn transition. Delivery loss is not established, so this is a bounded visibility issue rather than a broad merge blocker.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 47.62% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 105 functions across 52 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the primary change: replacing the desktop saved outbox with one-send-at-a-time behavior owned by the host. It is somewhat long but remains specific and relevant.
Description check ✅ Passed The description is comprehensive and covers the requested explanation, user-visible behavior, implementation, rationale, testing, visual proof, compatibility notes, and checklist. It identifies no sin…
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@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: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 70bc2872-82a7-4657-9e57-3a9c61e87496
📥 Commits

Reviewing files that changed from the base of the PR and between a3ecee0 and 78fbdee.

📒 Files selected for processing (195)
  • config/reliability-gates.jsonc
  • config/scripts/pr-code-change-scope-cross-version-send-path.test.mjs
  • config/scripts/pr-code-change-scope.mjs
  • mobile/src/session/mobile-structured-agent-session-send.ts
  • mobile/src/session/mobile-structured-queued-message-cards.ts
  • mobile/src/session/mobile-structured-send-delivery.ts
  • mobile/src/session/use-mobile-structured-send-with-outcome.ts
  • src/main/claude/claude-structured-history-window.test.ts
  • src/main/native-chat/agent-session-journal/journal-dispatch-doubt-reasons.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-host-test-data.ts
  • src/main/native-chat/agent-session-wire/structured-agent-session-unsent-send-hold.test.ts
  • src/main/runtime/rpc/methods/agent-launch-structured-prompt.test.ts
  • src/main/runtime/rpc/methods/agent-launch-structured-prompt.ts
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.opening-send.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageList.unsent-message.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageRow.test.tsx
  • src/renderer/src/components/native-chat/NativeChatMessageRow.tsx
  • src/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsx
  • src/renderer/src/components/native-chat/NativeChatRewindAction.target.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.launch-lifecycle.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.probe.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx
  • src/renderer/src/components/native-chat/agent-session-failure-words-text.test.ts
  • src/renderer/src/components/native-chat/native-chat-composer-draft-journal.ts
  • src/renderer/src/components/native-chat/native-chat-composer-draft-storage.ts
  • src/renderer/src/components/native-chat/native-chat-composer-types.ts
  • src/renderer/src/components/native-chat/native-chat-draft-cache.ts
  • src/renderer/src/components/native-chat/native-chat-queue-send-confirm.test.tsx
  • src/renderer/src/components/native-chat/native-chat-structured-send-composition-clear.test.tsx
  • src/renderer/src/components/native-chat/native-chat-submission-navigation.test.tsx
  • src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.kept-card.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-delivery-notices.ts
  • src/renderer/src/components/native-chat/structured-agent-session-legacy-outbox.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-legacy-outbox.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-hand-back.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-hand-back.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.older-host.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.recorded-copy.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.rejected-in-place.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-sender.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-sender.ts
  • src/renderer/src/components/native-chat/structured-agent-session-operation-id.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-dispatch.owner-change.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-dispatch.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-entry-watch.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-retry.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-retry.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-storage.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-storage.ts
  • src/renderer/src/components/native-chat/structured-agent-session-pending-sends.ts
  • src/renderer/src/components/native-chat/structured-agent-session-queue-request.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-queue-request.ts
  • src/renderer/src/components/native-chat/structured-agent-session-queued-cards.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-queued-cards.ts
  • src/renderer/src/components/native-chat/structured-agent-session-send-attempt.ts
  • src/renderer/src/components/native-chat/structured-agent-session-send-slot.ts
  • src/renderer/src/components/native-chat/structured-agent-session-stop-control.ts
  • src/renderer/src/components/native-chat/structured-agent-session-transcript-order.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-withdrawn-message-restore.test.tsx
  • src/renderer/src/components/native-chat/structured-agent-session-withdrawn-message-restore.ts
  • src/renderer/src/components/native-chat/use-native-chat-held-queue-composer-send.ts
  • src/renderer/src/components/native-chat/use-native-chat-provisional-launch.ts
  • src/renderer/src/components/native-chat/use-native-chat-rewind.ts
  • src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts
  • src/renderer/src/components/native-chat/use-native-chat-structured-composer-transport.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-command-send-gate.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-command-start-failure.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-command-write.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-context-usage.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-delivery-notices.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-legacy-recovery.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-messages.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-messages.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-mutate.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-mutate.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-admission.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-failed-here.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-fence.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-owner-change.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-ownership.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-rejection-cause.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-relaunch-hold.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-unconfirmed-probe.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-withdrawal.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.batch-churn.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.draft-hand-off.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.external-send.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.queue-delivery.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.rejected-reply.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.stop-parks-in-doubt.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-paired-runtime.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-prompt-cancel.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-prompt-state.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-provisional.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-question-answers.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.resume.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-sends.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-sends.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-stop.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-stop.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-transport.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-transport.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-write-refusal.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.clear-send.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.cut-turn-notice.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.queue-while-stopping.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.queued-gating.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.rejected-card.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.rewind-outbox.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.rewind.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session.ts
  • src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.test.ts
  • src/renderer/src/components/native-chat/use-structured-native-chat-submit-reveal.ts
  • src/renderer/src/components/native-chat/use-undelivered-structured-agent-session-outbox.ts
  • src/renderer/src/components/tab-bar/QuickLaunchButton.launch-status.test.tsx
  • src/renderer/src/components/tab-bar/QuickLaunchButton.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/lib/active-agent-note-send-diagnostics.ts
  • src/renderer/src/lib/active-agent-note-send-result.ts
  • src/renderer/src/lib/agent-message-send.test.ts
  • src/renderer/src/lib/agent-message-send.ts
  • src/renderer/src/lib/agent-session-launch-plan.ts
  • src/renderer/src/lib/launch-agent-in-new-tab.ts
  • src/renderer/src/lib/launch-agent-structured-chat-guard.test.ts
  • src/renderer/src/lib/new-agent-prompt-outcome.test.ts
  • src/renderer/src/lib/new-agent-prompt-outcome.ts
  • src/renderer/src/lib/structured-agent-session-empty-chat-reuse.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-after-failure.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-callers.ts
  • src/renderer/src/lib/structured-agent-session-launch-cancellation.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-close-race.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-different-request.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-empty-chat.ts
  • src/renderer/src/lib/structured-agent-session-launch-join-delivery.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-message.ts
  • src/renderer/src/lib/structured-agent-session-launch-prompt-stop.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-prompt.test.ts
  • src/renderer/src/lib/structured-agent-session-launch-prompt.ts
  • src/renderer/src/lib/structured-agent-session-launch-request.ts
  • src/renderer/src/lib/structured-agent-session-launch.test.ts
  • src/renderer/src/lib/structured-agent-session-launch.ts
  • src/renderer/src/lib/structured-agent-session-paired-admission.ts
  • src/renderer/src/runtime/paired-host-structured-launch-bookkeeping.test.ts
  • src/renderer/src/runtime/structured-agent-session-accepted-send-capability.test.tsx
  • src/renderer/src/runtime/structured-agent-session-accepted-send-capability.ts
  • src/renderer/src/runtime/structured-agent-session-client.ts
  • src/renderer/src/runtime/structured-agent-session-host-capability.ts
  • src/renderer/src/runtime/structured-agent-session-tab-retirement.test.ts
  • src/renderer/src/runtime/structured-agent-session-tab-retirement.ts
  • src/renderer/src/store/slices/ui-agent-send-target.test.ts
  • src/renderer/src/store/slices/worktrees/teardown/worktree-purge-state.ts
  • src/shared/agent-session-conversation-command.ts
  • src/shared/agent-session-refusal-notice.test.ts
  • src/shared/agent-session-refusal-notice.ts
  • src/shared/agent-session-refusal-retry.test.ts
  • src/shared/native-chat-transcript-projection.ts
  • src/shared/structured-agent-session-draft-hand-off.test.ts
  • src/shared/structured-agent-session-draft-hand-off.ts
  • src/shared/structured-agent-session-message-projection.test.ts
  • src/shared/structured-agent-session-message-projection.ts
  • src/shared/structured-agent-session-outbox-admission.ts
  • src/shared/structured-agent-session-outbox-delivery.test.ts
  • src/shared/structured-agent-session-outbox-delivery.ts
  • src/shared/structured-agent-session-outbox-reconcile.test.ts
  • src/shared/structured-agent-session-outbox-reconcile.ts
  • src/shared/structured-agent-session-outbox-retry-hold.test.ts
  • src/shared/structured-agent-session-outbox-stop-withdrawal.ts
  • src/shared/structured-agent-session-outbox-unconfirmed-resend.ts
  • src/shared/structured-agent-session-outbox-withdrawn-send.test.ts
  • src/shared/structured-agent-session-outbox.queue-delivery.test.ts
  • src/shared/structured-agent-session-outbox.ts
  • src/shared/structured-agent-session-rejection-words.test.ts
  • src/shared/structured-agent-session-rejection-words.ts
  • src/shared/structured-agent-session-send-disposition.test.ts
  • src/shared/structured-agent-session-send-disposition.ts
  • src/shared/structured-agent-session-send-evidence.test.ts
  • src/shared/structured-agent-session-send-evidence.ts
  • src/shared/structured-agent-session-send-mutation.ts
  • tests/e2e/cross-version-wire/structured-agent-session-surface-manifest.ts
💤 Files with no reviewable changes (55)
  • src/renderer/src/components/native-chat/use-undelivered-structured-agent-session-outbox.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-dispatch.owner-change.test.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.external-send.test.tsx
  • src/shared/structured-agent-session-outbox-reconcile.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-storage.test.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-rejection-cause.test.tsx
  • src/shared/structured-agent-session-outbox-withdrawn-send.test.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session.rewind-outbox.test.tsx
  • src/renderer/src/components/native-chat/native-chat-submission-navigation.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.probe.test.tsx
  • src/shared/structured-agent-session-outbox-retry-hold.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-retry.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-withdrawn-message-restore.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.stop-parks-in-doubt.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.rejected-reply.test.tsx
  • src/renderer/src/components/native-chat/structured-agent-session-withdrawn-message-restore.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.recorded-copy.test.ts
  • src/renderer/src/runtime/structured-agent-session-accepted-send-capability.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-withdrawal.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-admission.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.batch-churn.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-relaunch-hold.test.tsx
  • src/shared/structured-agent-session-outbox.queue-delivery.test.ts
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.ts
  • src/renderer/src/i18n/locales/fr.json
  • src/shared/structured-agent-session-outbox-reconcile.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.queue-delivery.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox.draft-hand-off.test.tsx
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-fence.test.tsx
  • config/scripts/pr-code-change-scope.mjs
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-dispatch.ts
  • src/shared/structured-agent-session-outbox-admission.ts
  • src/renderer/src/i18n/locales/zh.json
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-unconfirmed-probe.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-owner-change.test.tsx
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-retry.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-entry-watch.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-ownership.ts
  • src/shared/structured-agent-session-outbox-unconfirmed-resend.ts
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/es.json
  • src/shared/agent-session-refusal-retry.test.ts
  • src/renderer/src/components/native-chat/use-structured-agent-session-outbox-failed-here.ts
  • src/shared/structured-agent-session-outbox-delivery.test.ts
  • src/renderer/src/runtime/structured-agent-session-accepted-send-capability.ts
  • src/shared/structured-agent-session-outbox-stop-withdrawal.ts
  • src/shared/structured-agent-session-outbox-delivery.ts
  • src/renderer/src/components/native-chat/NativeChatStructuredSessionDelivery.test.tsx
  • src/shared/structured-agent-session-outbox.ts
  • src/renderer/src/components/native-chat/structured-agent-session-message-projection.older-host.test.ts
  • src/renderer/src/components/native-chat/structured-agent-session-outbox-storage.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

[queueCapability, queueEnabled]
)
const outboxController = useStructuredAgentSessionOutbox({
const sends = useStructuredAgentSessionSends({

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Give the send hook the same isWorking that the transcript uses.

useStructuredAgentSessionSends decides whether send returns 'queued' by running pendingSendsOutsideQueuedCards with transportState.isWorking (Line 148). The transcript filters the same pending sends with transportState.isWorking || transportState.queueSendsNext (Lines 192-195). Assume queueSendsNext is true and no turn runs. Then send returns true, and the composer reveals the transcript for a bubble. The transcript hides that same queue-if-active entry because it treats the entry as a card. The reader is scrolled to the bottom, and no bubble is drawn there. The comment in use-structured-agent-session-sends.ts says "The same projection that draws the transcript decides it". The inputs differ, so the two decisions can differ.

Compute the combined isWorking before calling useStructuredAgentSessionSends, and pass it to that hook.

Proposed fix
+  // A queued send is a card, never a transcript bubble.
+  const isWorking = transportState.isWorking || transportState.queueSendsNext
   const sends = useStructuredAgentSessionSends({
     sessionId,
     target,
     fence: transportState.fence,
     submissions: transportState.submissions,
     queuedMessageIds,
     queue,
     historyLoaded: transportEnabled && state.status === 'ready',
-    isWorking: transportState.isWorking,
+    isWorking,
     stopping: stopControl.stopping
   })
 ...
-  // A queued send is a card, never a transcript bubble.
-  const isWorking = transportState.isWorking || transportState.queueSendsNext

…er comes back as not sent

A managed server's update rotates its pairing, and this window's main
process then answers the next call itself, before forwarding anything,
with runtime_environment_changed. The send read that thrown answer as
proving nothing, so the person was told Orca couldn't confirm a message
that never left. It is now handed back as not sent, with the reason.
Every other thrown answer still reads as unconfirmed once the request
may have gone out.
…back with its file and why

A paired server checks every stored file a message names when it admits it,
and refuses the whole message before recording it when one has expired. The
sender hands such a message back to the chat's draft, file included, with the
refusal's words, whether or not a view shows the chat, so it can be removed
and attached again. Ported from the saved-outbox test that came with
attaching files to a structured chat on a paired server.
@brennanb2025
brennanb2025 merged commit 0f9f199 into main Oct 7, 2026
34 of 35 checks passed
brennanb2025 added a commit that referenced this pull request Oct 7, 2026
…nto brennanb2025/rsc-resume-reconnect

#25652: each workspace is a bordered box drawn in the content area, a
nested child's box sits inside its parent's, and rows have no dividers.
Under a machine row everything a machine holds moves in one level while
every checkbox stays in the outside column. The machine row is the header
over its boxes, never boxed: the project's band in the content area, set
off from the previous machine's boxes by twice the list's gap.

Main via the base: the in-chat "why Orca stopped" row and Continue
(#25675) and the desktop send layer (#25959).
- Host: main's restart-resume contract module now also declares this
  branch's listed dismissal; the branch's own copy of that type is gone.
  continueInterrupted and the listed dismissal both stay.
- Client: one options argument carries both the pairing fence and the
  compatibility skip; restartContinue keeps its 195 s.
- Continue is hidden while a resume carries the chat on its own machine
  (resumes are per machine here), and the launch's resume decision is
  marked where main marks it: when this computer's resume names its
  chats, and when the launch read ends.
brennanb2025 added a commit that referenced this pull request Oct 8, 2026
Main removed the desktop's saved outbox (#25959): the renderer's delivery notices now come
from in-memory sends and the host's rows. PR A's start-row reading (a row keyed by its
message speaks for it alone; otherwise any loaded row with the same failure, but a
command's) is kept on the new shape, and the start-row test moves to the new API; its
outbox-only and released-host-batch cases are gone. Main's new Claude sign-in prompt
counts start-failure rows through their fact.
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.

1 participant