Repository navigation
fix(native-chat): a /compact sent while the agent works waits in line instead of being refused - #25704
Conversation
… instead of being refused
…nly; a /compact behind an unsent message waits for it quietly
…nly too; a command card offers no Edit
…mmand card offers Send while the agent is idle; /compact behind an unsent message is not taken
…mand-waits-in-line # Conflicts: # src/main/native-chat/agent-session-wire/structured-agent-session-queued-mutations.ts
…y; refuse a queued command without a hand-off, with its reason; a queue send on its way reads as sending
…d at once; a spent card's wait says to run it again; only /compact or /clear give way to returned text
…mand-waits-in-line # Conflicts: # src/renderer/src/components/native-chat/use-structured-agent-session.ts
…mand-waits-in-line
…under the line limit; the send capabilities get their own module
… own unsent message says it is still being sent
…mand-waits-in-line
…nt beside the others; main's send capabilities back to its text
…mand-waits-in-line # Conflicts: # src/renderer/src/components/native-chat/use-structured-agent-session.ts
… on its way holds commands, tested
…mand-waits-in-line # Conflicts: # mobile/src/session/mobile-structured-queued-message-cards.ts # mobile/src/session/use-mobile-structured-queued-message-controls.ts # src/main/native-chat/agent-session-journal/journal-dispatch-settlement.ts # src/main/native-chat/agent-session-journal/queued-message-settlement.ts # src/main/native-chat/agent-session-journal/queued-message-table.ts # src/main/native-chat/agent-session-wire/structured-agent-session-queued-mutations.ts # src/renderer/src/components/native-chat/NativeChatQueuedMessageCard.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
…kept like any card; kept-card captions name only controls shown
…er forces later sends to queue
… is gone A refusal that names something the chat shows (the agent working, a pending prompt, background tasks, an unsent message) is said only while that still holds, derived each render, and dropped once it ends. Other composer errors are unchanged.
… gone The phone's banner keeps a refusal naming the agent working or a pending prompt only while the chat still shows it, dropped each render once it ends. Background tasks, which the phone can't see, and generic errors behave as before. The host-reason mapping is shared with desktop.
…t is being sent or offers Retry Each line now goes when its own state ends: a send that fails stops being sent, and a resend stops offering Retry.
…mand-waits-in-line
…mand-waits-in-line
…k message on screen; the in-doubt case says so
…mand-waits-in-line
…mand-waits-in-line
…mand-waits-in-line
…reads working, as the pane does
…mand-waits-in-line
|
Native chat refuses This PR puts Review fixed commands running after Stop, cards offering unavailable actions, refusal settlement blocking the queue or reporting twice, and a message briefly appearing twice. A command refusal spends its card only when its own turn records the reason; failed starts return the card for retry. The final sync with main at Deferred limits remain in the PR body: attachments can bypass the text queue; Cmd/Ctrl+Enter does nothing when the newest card is a command; turning follow-up queueing off cannot move later messages ahead of a waiting command; and background work can cause an admitted command to be refused when its turn comes. The fresh-chat early Final head: Rendered recovery QA passed on After the final main sync, a fresh review (round 10) found no problems in the merge and listed seven behaviours it could have changed. Rendered QA round 5 then re-checked all seven on One pre-existing main bug found, not caused by this PR: text typed while a successful The later conflict-only sync to Main sync 4 ( |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (30)
🚧 Files skipped from review as they are similar to previous changes (9)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review. 📝 WalkthroughWalkthroughThe changes add capability-gated queued Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to No identified issue currently blocks merging. Queueing remains opt-in, and the checked rewind path preserves command ordering. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 54.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 57 files. (7 skipped: 7 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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.
🧹 Nitpick comments (3)
mobile/src/session/use-mobile-native-chat-send-error.ts (1)
72-78: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReset
refusedWhilewhen the hold timer or a scope change clears the message.
showstoresrefusedWhile. The 4-second timer,clear, and the scope-change effect clear onlymessage. A latershow(next)without a cause sets the cause toundefined, so the banner logic stays correct. The stale cause is still kept in state after other clears. This is a small consistency fix. Clear both values in the same places.mobile/src/session/use-mobile-structured-agent-session.ts (1)
214-228: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueReuse
isWorkinginstead of computing the working state a second time.Line 228 calls
isStructuredAgentSessionMainAgentWorking(turnId, state.submissions, state.fence). Line 167 already computes that value asisWorking. PassisWorkingto the hook instead. This keeps both values tied to one source.♻️ Proposed change
- agentWorking: isStructuredAgentSessionMainAgentWorking(turnId, state.submissions, state.fence), + agentWorking: isWorking,src/renderer/src/components/native-chat/use-structured-agent-session.ts (1)
225-237: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMove the hook call out of the object literal.
Line 230 calls
useStructuredAgentSessionHostQueuesCommands(target)inside the returned object spread. The call is unconditional, so hook order stays stable. Lint rules and later edits can still break it, for example if someone adds a short-circuit before it. Move the call to a top-levelconst.
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
943c2825-f533-4909-a683-db3eb9de4cc1
📒 Files selected for processing (94)
config/tsconfig.node.jsonmobile/src/session/MobileNativeChatQueuedMessages.test.tsxmobile/src/session/MobileNativeChatQueuedMessages.tsxmobile/src/session/mobile-dictation-refused-start-routing.test.tsxmobile/src/session/mobile-native-chat-controller-contract.tsmobile/src/session/mobile-native-chat-merged-snapshot-parity-hooks.test.tsxmobile/src/session/mobile-structured-agent-session-contract.tsmobile/src/session/mobile-structured-agent-session-host-support.test.tsmobile/src/session/mobile-structured-agent-session-host-support.tsmobile/src/session/mobile-structured-composer-command.test.tsmobile/src/session/mobile-structured-composer-command.tsmobile/src/session/mobile-structured-queued-message-cards.test.tsmobile/src/session/mobile-structured-queued-message-cards.tsmobile/src/session/use-mobile-native-chat-controller.tsmobile/src/session/use-mobile-native-chat-send-error.test.tsmobile/src/session/use-mobile-native-chat-send-error.tsmobile/src/session/use-mobile-native-chat-session-lane.tsmobile/src/session/use-mobile-session-native-chat-dictation.tsmobile/src/session/use-mobile-structured-agent-session-prompt-cancel.test.tsxmobile/src/session/use-mobile-structured-agent-session-queued.test-fixture.tsmobile/src/session/use-mobile-structured-agent-session.tsmobile/src/session/use-mobile-structured-queued-message-controls.tsmobile/src/session/use-mobile-structured-send-with-outcome.tsmobile/src/session/use-mobile-structured-turn-indicator.test.tsxsrc/main/native-chat/agent-session-journal/journal-dispatch-settlement.tssrc/main/native-chat/agent-session-journal/queued-message-settlement.tssrc/main/native-chat/agent-session-journal/queued-message-table.tssrc/main/native-chat/agent-session-wire/structured-agent-session-command-turn.tssrc/main/native-chat/agent-session-wire/structured-agent-session-mutation-plans.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-command-refusal.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-compact.test.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-message-rig-provider.test-fixture.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.tssrc/main/native-chat/agent-session-wire/structured-agent-session-queued-mutations.tssrc/main/native-chat/agent-session-wire/structured-agent-session-rewind.test.tssrc/main/native-chat/agent-session-wire/structured-conversation-command-admission.test.tssrc/main/native-chat/agent-session-wire/structured-conversation-command-admission.tssrc/main/native-chat/agent-session-wire/structured-conversation-command.tssrc/main/native-chat/agent-session-wire/structured-conversation-compaction.test.tssrc/main/native-chat/agent-session-wire/structured-conversation-compaction.tssrc/renderer/src/components/native-chat/NativeChatQueuedMessageCard.tsxsrc/renderer/src/components/native-chat/NativeChatQueuedMessageList.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.command-refusal-line.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.notice-card.test.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsxsrc/renderer/src/components/native-chat/NativeChatStructuredSession.tsxsrc/renderer/src/components/native-chat/agent-session-failure-words-text.test.tssrc/renderer/src/components/native-chat/agent-session-failure-words-text.tssrc/renderer/src/components/native-chat/agent-session-write-notice-text.tssrc/renderer/src/components/native-chat/native-chat-composer-types.tssrc/renderer/src/components/native-chat/native-chat-mock-slot-types.test-support.tssrc/renderer/src/components/native-chat/native-chat-structured-composer-dispatch.tssrc/renderer/src/components/native-chat/native-chat-structured-send-composition-clear.test.tsxsrc/renderer/src/components/native-chat/structured-agent-session-prompt-response.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/structured-conversation-command-send.test.tssrc/renderer/src/components/native-chat/structured-conversation-command-send.tssrc/renderer/src/components/native-chat/use-native-chat-composer-notice.tssrc/renderer/src/components/native-chat/use-native-chat-structured-composer-send.test.tsxsrc/renderer/src/components/native-chat/use-native-chat-structured-composer-send.tssrc/renderer/src/components/native-chat/use-structured-agent-session-command-send-gate.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session-command-write.tssrc/renderer/src/components/native-chat/use-structured-agent-session-queued-messages.tssrc/renderer/src/components/native-chat/use-structured-agent-session-write-refusal.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session.queued-gating.test.tsxsrc/renderer/src/components/native-chat/use-structured-agent-session.tssrc/renderer/src/i18n/en-runtime-required.jsonsrc/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/runtime/structured-agent-session-host-capability.tssrc/shared/agent-session-command-refusal-words.tssrc/shared/agent-session-conversation-command.tssrc/shared/agent-session-failure-copy.tssrc/shared/agent-session-failure-words.tssrc/shared/agent-session-refusal-notice.test.tssrc/shared/agent-session-refusal-notice.tssrc/shared/agent-session-write-failure.tssrc/shared/agent-session-write-notice-copy.tssrc/shared/protocol-version.tssrc/shared/returned-draft-text.test.tssrc/shared/returned-draft-text.tssrc/shared/rpc-contract/structured-agent-session-params.tssrc/shared/skill-install-capability.tssrc/shared/structured-agent-session-coalescer.test.tssrc/shared/structured-agent-session-coalescer.tssrc/shared/structured-agent-session-command-refusal-cause.tssrc/shared/structured-agent-session-command-turn-identity.tssrc/shared/structured-agent-session-composer.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.
Send and /compact now route on acceptsWithCommandReceipt: main made /clear and rewind settlesWithWrite plans with their own success receipts, and they stay on ledger settlement. /compact keeps #25704's queued answer under receipt replay.
ELI5
If you type
/compactin a native chat while the agent is busy, Orca refuses it today: "A message you sent earlier isn't confirmed yet. The command didn't run. Wait for your earlier message to go through, or retry it." This PR makes/compactwait its turn in the message queue, like a message typed mid-turn, and run when the agent is free.That waiting reaches users only when the message queue is switched on. The queue is still turned off in shipped builds. Until it launches,
/compactis still refused while the agent is busy, but in plain words. What changes for everyone today is listed under "What changes live today".What Changed
The problem
A native chat refuses
/compactand/clearwhenever anything is still in flight:The user sees one of two sentences. The desktop's own check says "Wait for pending work and messages to finish before using this command." The host says "A message you sent earlier isn't confirmed yet. The command didn't run. …". Neither says whether the message was sent, what "confirmed" means, or what to do.
Why it happens:
/compactis accepted as an ordinary submission, and the delivery loop hands every submission to the agent at once, even mid-turn. A plain message steers into the running turn, but a command can't, so the host refused it whenever anything was in flight. The host-held message queue (the cards above the composer) already waits for exactly these states before it sends anything./compactjust never entered it.What changes live today, for everyone
The
/clearrefusal is said once, in plain words.This covers the host's refusal and the desktop's and phone's own checks, in every shipped language. Background tasks, rewind and handoff keep their words.
A refused
/compactreads the same way: "The agent is still working. Run /compact when it's done.", and the matching question and Retry sentences. On shipped hosts/compactstill refuses while the agent works, until the queue launches. The desktop now also checks the chat's Working state before sending a/compactor/clear. So in the moment between a message being accepted and the agent opening a turn, it answers with the plain sentence locally instead of the host's "isn't confirmed" one.On desktop, a second command pressed while one is on its way says "The agent is still working. Run /… when it's done." Before, it said "Wait for pending work…". The phone is unchanged: it has always taken no second command silently.
A message handed back into a composer holding only
/compactor/clear(a queued card's Edit, the draft hold, or the phone's rejected-send restore) now replaces that command. Before, it became/compactfollowed by the message, which then failed with "Use /compact without arguments". Any other draft is kept, a lone slash word that isn't one of those two commands (/clearly,/tmp) included, with the message after it as before. A Stop no longer hands a message back into a composer that holds text (main's feat(native-chat): keep a message a Stop took back on screen, with one stop row after it #25051): behind a typed command the message stays on screen with its Retry.A refusal line by the composer goes once what it names has ended, on desktop (a row of the notice card above the message box) and phone. "The agent is still working…" goes when the agent stops (and while the queue is about to send its next card, the chat still reads as working), the question/approval line when it is answered, and on desktop "Background tasks are still running…" when they end, "…still being sent…" when that send lands or fails, and "Retry your earlier message…" once it is resent. It is dropped, not hidden, so the state coming back later doesn't bring it back. Before, the line stayed until the next send and could state something no longer true. Other errors keep their existing lifetime: on desktop, until the next send or Dismiss; on phone, a temporary banner or toast. The phone can't see background tasks, so that refusal uses its ordinary timeout too.
The host's "earlier message unsettled" rule (shared by
/clear,/compactfrom older clients, and rewind) is now the chat's Working rule (isUnansweredStructuredAgentSessionDispatch), not a near-copy. It no longer refuses for two things the chat already shows as idle: a handed-over message left by an earlier agent process, and an older host's restart doubt.The new host capability
agent-session.queued-commands.v1is advertised. On its own it does nothing: clients use it only together with the queue's capability.What changes when the message queue is switched on
/compactsent while a turn runs, a question is pending, or an earlier message is unanswered appears at once as a/compactcard in the queue. There is no notice, and the composer clears. The card runs after the earlier work, in send order. Messages sent after it queue behind it, even with "queue follow-ups" off. A command card whose conversion into a send failed is skipped until its Send is retried. Cards kept after a quit or close retain one order with kept messages; reopening sends none of them on its own, and the next accepted turn or Resume releases that pause./compact, as typed./compactpressed while a message from this window is still on its way to the host is not taken. The text stays in the composer, the earlier message reads "Sending…", and the send control is in its busy (Stop) state. Nothing is armed. Once the host has the message, the next Enter sends it.Mechanism
structured-conversation-compaction.ts):agentSession.conversationCommandgains an optionaldelivery: 'queue-if-active', for/compactonly. A/clearsent withdeliveryis refused by the host: its operation fingerprints nodelivery, so it reads as a conflict and nothing queues. A test proves it. With it, the host first runs the queue's own accept rule (maybeQueueStructuredAgentSessionSend, the same one a queue-if-active send uses). If anything holds the queue, the command is stored as a card whose body is the existing/compactbody. The RPC answers at once with{ state: 'completed', queued: {…} }. Otherwise today's path runs unchanged.queued-message-settlement.tsandqueued-message-table.ts): a failure-verdict rejection of a command card's hand-off settles the card as spent (withdrawn) instead of returned, but only when the command's own turn is in the journal, because that turn's row says why. A failed start has no command turn, so its card is returned like any card.structured-agent-session-queued-command-refusal.ts), the host records no hand-off. It writes the ended turn and its row in one batch, then rejects the submission while it is still queued. A crash between leaves a queued submission, which a restart returns to waiting. It never leaves a card gone with no row, or a command that "may have run".agent-session-command-refusal-words.ts), the same sentences the composer line uses, so the direct path's answer reads the same.queuedSendAnswer). It never makes a second card or a second run./cleardrops a waiting command card instead of carrying it to the new conversation. Other cards are carried in order and wait for the new conversation's next turn or Resume./compact(no opt-in) cut short by a restart is never turned into a card; the person runs it again.structured-conversation-command-send.ts,structuredConversationCommandHoldandstructuredConversationCommandRunner):/compactsendsdeliveryonly when the host advertises both capabilities.hasUnsentStructuredAgentSessionOutboxEntry). That second case takes nothing, as described above.canStop, derived every render from the turn, the Working rule and that same unsent check). It ends whenever the earlier entry settles: accepted (the host owns it), refused or never recorded (it waits for its Retry), or taken back by Stop./compactin the composer that message stays on screen with its Retry, and/compactthen goes.structuredAgentSessionDeliveryNotices): a Retry, or "Sending…" for one a Stop kept./compactthat waits in line. The phone has no outbox.clearandcompact(agentSessionWriteKindForMethod) mapturnActive/messagesUnsettled/promptPendingto the plain sentences.structuredConversationCommandHolduses the same sentences for the clients' own checks.body.commandmarks it on the host (no Send while working, spent when refused, dropped by/clear), andcard.commandmarks it on both clients. The stacked/clearfollow-up can reuse it as is.Why
The user asked for the state to be fixed, not the wording: a command should wait in line behind earlier work, like any queued message. The queue already models "wait until the agent is free", with persistence, publication to every device, Delete, pause on Stop and restart, and Resume. Admitting
/compactinto it reuses all of that and adds no new latch.Alternatives considered:
/compactin the composer after it ran. It was replaced by taking nothing while Send is busy./compactuntil idle. A queued submission counts as working itself, so the loop would wait on itself without a special case. It also has no Delete, pause or Resume surface./clearwait in line too. It stops the agent and moves the tab to a new chat, which needs a deferred tab move and a reply that arrives later. That is a stacked follow-up.Differences from the common pattern. In the common pattern, compact is an ordinary queued message that runs in order,
/clearis queued like any message while busy, and Send is disabled while the previous send is still on its way./compactwaits as a host-held queue card. Intended: same mechanism, one ordered host queue.A command card never steers into a running turn. Intended: handed over mid-turn, the host can only refuse it, so offering Steer would offer a guaranteed failure. Send stays available whenever the agent is idle.
On desktop,
/compactbehind a message still on its way takes nothing and arms nothing to send later. That part is the common pattern's disabled-while-submitting mechanism. Three things differ:Temporary: the follow-up has the command ride the outbox in order after fix(native-chat): every chat send ends one of three ways, decided by the host's answer #25207, which removes the client-side check altogether.
/clearis refused while busy. Temporary: the follow-up is "/clear waits in line", stacked on this PR./compactwaiting in line is off until the message queue launches (agent-session.queued-messages.v1advertised). Until then,/compactis refused while busy, in plain words. Temporary: it goes live with the queue launch, with no further change here. This PR doesn't touch what gates the queue's launch.An older host with a new client, or an older client with a new host, keeps today's refusal. Temporary: until those versions age out.
A message with an image skips the text-only queue:
maybeQueueStructuredAgentSessionSendinstructured-agent-session-queued-messages.tsreturns null for any send that isn't text only. So it can run ahead of a waiting /compact card. Temporary: the follow-up is the queue's attachment support.Linked Issue
N/A (maintainer change).
Visual Proof / QA
Final head
47f4087be85— round 5 QAAfter the final main merge, A–F passed: quitting and reopening sends nothing automatically; the next own message or Resume releases kept messages and
/compactin order; the command keeps its action restrictions; a refused command explains itself once and later text proceeds. G's unsettled-clear case also passed. Typing during a successful/clearstill loses the draft from the replacement composer, but this is a pre-existing main bug: the same result reproduces on main parent4f4f021f8a1. It is not introduced by this PR or the merge.Checked October 7–8, 2026 on Windows using hidden windows, private profiles and homes, hooks disabled, mock keychain and a stand-in agent. The message-queue capability was enabled only in the QA artifacts for A–F and the matched parent comparison; shipped gating is unchanged. A separate unmodified-source final artifact with that capability off reproduced the clear failure too. This is separate from the follow-up preference, which was tested on and off for message order. Original uncropped frames, a 50 ms/mutation sampler, provider logs and host journal records back these results; this does not establish phone, SSH or real-provider behavior.
/compact, follow-ups onNo duplicate waiting/sending cards were observed. D's host journal records the accepted kept submission before quit and recovery under the same message ID; a merely local unaccepted send was excluded. E's first frame has the command popup over part of the earlier card; accessible state and the sampler confirm Sending.
A — follow-ups on, quit/reopen and ordered release
Before actual quit: typed message and compact wait.
Quiet reopened chat.
Next own message runs first.
Own message, typed message, then compact complete.
B/C — follow-ups off and restart Resume
Follow-ups off: quiet reopen.
Follow-ups off: saved command-first order.
Real Resume starts continuation first.
Resume continuation then saved waiting order.
D — host-accepted kept message, follow-ups off
Accepted kept text and compact before actual quit.
Kept work remains quiet after reopening.
Next own message precedes kept work.
Own message, kept typed text, then compact complete.
E/F — unsettled earlier send, restrictions and refusal
Earlier Sending; compact remains in the box.
Compact waits with restricted actions.
Stop and queue Resume preserve order.
Waiting command that will be refused.
One refusal explanation and next text completes.
G — pre-existing successful-clear draft disappearance
Each clean deciding probe started after normal Warmup completion, held clear for five seconds, typed
Draftexactly once while clear was pending, and performed no later UI action or Enter on that draft. All three pending frames showDraft; all three replacement frames show an empty composer. Stored text remains under the old conversation's draft scope. Host records show completed clear replacements, no Draft submission or queue card, and no submission in the replacement conversation. The loss is from the visible composer when its conversation scope changes; it is not evidence that the saved bytes were erased. Main capability off was not separately tested; the final on/off control establishes flag independence.The original longer-draft run included later re-entry by the driver. That later action is excluded; the clean one-fill probes below are the retention evidence. An existing rating popup overlays the right side of the main frame, but Draft and the empty replacement placeholder are visible and independently confirmed by accessible state.
Unsettled clear — final head PASS
Unsettled clear keeps typed text with inline feedback.
Text remains after earlier send settles.
Successful clear — final head, message-queue capability on
Final ON: Draft typed while clear is pending.
Final ON: replacement composer is empty.
Successful clear — final head, message-queue capability off
Final OFF: Draft typed while clear is pending.
Final OFF: replacement composer is empty.
Successful clear — main parent `4f4f021f8a1`, message-queue capability on
Main parent: Draft typed while clear is pending.
Main parent: replacement composer is empty.
The successful-clear draft bug remains unfixed and is recorded as main pre-existing. No ready or merge action was taken.
The existing screenshots compare baseline
5cf3585b78dwith stacked PR #25842 atf1f69c818bb, which includes this PR at338a57c898b. The after images show the shared/compactimplementation and queue-off refusal paths; PR #25842 additionally changes/clearonly when queueing is enabled. These images cover admission, send order, Stop/Resume and refusal behavior preserved by this merge, and do not verify its new recovery behavior. How this was checked: on a Windows machine, in an isolated copy of Orca running hidden (its own profile and home folder, never the user's real one), with a stand-in agent instead of a real Claude or Codex, driven by a non-Claude test agent. The message queue is still switched off in shipped builds, so it was switched on in that copy only, to show what happens once the queue launches.Before:
/compactsent right after a message is refused.After:
/compactwaits as a card with Delete and More actions, and no Send, Steer or Edit while the agent works.After the agent's reply, the waiting
/compactruns and its card is gone.Send order is kept: Second, then
/compact, then Third.The same order with follow-ups switched off.
While the earlier message is still on its way, it reads "Sending…" and
/compactstays in the composer.A message sent mid-turn shows as a "Sending…" card until the host holds it.
Stop pauses the queue; Resume runs the waiting
/compactcard.When the agent refuses the compaction, that is said once, in one row, and the queue moves on.
With the queue switched off (what shipped builds do today), a busy
/cleargets the plain refusal line:and so does a busy
/compact:Not verified on screen: the phone. No simulator can pair with the hidden Windows copy; the phone is covered by unit tests. These screenshots were taken before the fix that removes a stale refusal line (the line now goes away once its cause ends) and before the last merges from
main. One of those merges brought in the notice card above the message box, so on desktop the refusal now shows as a row of that card rather than a loose line under the transcript; its words and when it goes away are unchanged.Current recovery verification — round 4
The quit/recovery row now passes at
b6cbc76c74b344c3defd1d55301c69dcf483b797. On the earlier head, a recovered typed message could remain held while a later/compactran after the next own message. On this head, both cards reopen quietly in their submitted order and follow the chat's next accepted turn; both complete without the retired kept-message caption.Checked October 7, 2026 PDT, on the Windows high-spec host, using hidden Orca windows with private profiles and homes, hooks disabled, mock keychain and a stand-in agent. Both builds had the queue capability enabled only for QA; shipped queue gating is unchanged. These are fresh captures, separate from the 11 historical images above. The PR remains a draft.
Kept message, then/compactNext turnNext turn→Kept message→/compact/compact, then typedKept second messageResume 1 chatbutton/compact→Kept second messageActually kept after message, then/compactNext turn after kept/compactThe first two rows use ordinary waiting cards; the label
Kept messageis fixture text. The third row proves actual unused-message recovery: the host journal records that recovery, and the provider received the typed frame but never initialized or started a turn before the realapp.quit(). The valid third-row setup used an existing conversation after a completed warmup turn. Every relaunch used the same profile. All restored current-head queues stayed quiet before release (at least 15 seconds, over 90 seconds for Resume). The pending-start case offered no Resume action, so its next own message was used. The opposite input orders in the first two runs were submitted deliberately; Resume did not reverse the queue.The full, uncropped screenshots below agree with the provider logs, renderer states and read-only journal snapshots.
After b6cbc76 — typed then /compact, released by the next own message
The final capture includes the existing Enjoying Orca rating popover; the delivered transcript remains readable.
After b6cbc76 — /compact then typed message, released by Resume
The prequit capture includes the existing rating popover. The real Resume click showed the existing Resumed 1 chat feedback.
After b6cbc76 — genuine kept message before /compact
Earlier-head comparison —
fc7a25199a7a6cfa0f5cf50929443d27519e3444: submit typedActually kept before message, then/compact, and quit before provider initialization. Both reopen quietly in that order, but the typed card says “Not sent yet — press Send to send it.” AfterNext turn before kept, only the new own message and/compactrun; the recovered typed card remains waiting. Its journal row retains the old per-message hold. No manual Send or deletion was used to repair this comparison. Onb6cbc76c74b, the next own message releases the typed message before compaction, and both finish.Before fc7a251 — kept typed message stays waiting while /compact runs
Pre-existing limitation on both tested heads:
/compactsubmitted in a fresh chat before provider initialization shows the exact inline refusal “/compact is not supported by this chat host.” The text stays in the composer and no command card is created. The fresh-chat probe reproduced the same result atfc7a25199a7andb6cbc76c74b; it is separate from the passing initialized-conversation recovery case above.Same early fresh-chat refusal on both heads
This focused run checks the real Windows native-chat host and renderer with a protocol stand-in. Real-agent, phone, SSH, folder-workspace and other desktop-platform flows were not checked in this round.
Testing
New and updated tests:
Host,
structured-agent-session-queued-compact.test.ts(real host rig)./compactwaits as a card behind:Also covered:
/compact; the chat's next accepted turn releases both, and compaction runs only after the kept message;/cleardrops command cards;deliveryparses only asqueue-if-active, and a/clearsent with it is refused by the host with nothing queued.Host,
structured-conversation-command-admission.test.tsandstructured-agent-session-rewind.test.ts: the unified Working rule.Desktop,
use-structured-agent-session.queued-gating.test.tsx. Lit:/clearnames its Retry and/compactgoes;working,prompt,background,sending,retry); a failed send drops "still being sent", a resend drops "Retry";/clearbehind only a failed message names its Retry, and after a Stop names no Retry it doesn't show;Dark: exactly today's behaviour, plus the plain words.
Desktop,
structured-conversation-command-send.test.ts(holds, the in-flight second command, every sentence),structured-agent-session-queued-cards.test.ts, andNativeChatQueuedMessageList.test.tsx(no Steer, Send only while idle, no Edit, Delete and Resume work).Shared,
agent-session-refusal-notice.test.ts, andreturned-draft-text.test.ts(a lone/compactor/cleargives way to the message handed back;/clearlyis kept), andstructured-agent-session-coalescer.test.ts(a queue list lands at once).Phone:
mobile-structured-composer-command.test.ts,mobile-structured-agent-session-host-support.test.ts,mobile-structured-queued-message-cards.test.ts,MobileNativeChatQueuedMessages.test.tsx.Ablations (each behaviour removed, tests fail, then restored):
/clearfilter/compactwords/compactand/cleargive wayEarlier directory runs, by explicit file list (historical evidence, before the current head):
src/main/native-chat/agent-session-wireandagent-session-journal: 220 of 221 files pass; one can't load locally (see below). Two restart tests hit temp-directory races once under load and pass on a rerun.src/renderer/src/components/native-chatandsrc/renderer/src/runtime: 471 files pass.src/shared: 866 pass; 11 can't load locally.mobile/src/session: 217 files pass.Gates: localization (catalogs, extraction, coverage) passes. The sentences are hand-translated in es/fr/ja/ko/zh, and the runtime English catalog was regenerated. Typecheck: web, node and cli show no errors beyond the known
stream-json/stream-chainones. The mobile app and mobile tests show nothing new: only the existing missing generated-file errors, and everysrc/sessionerror file is already in the ratchet baseline. Locally, the fulloxlintover every file this PR changes (the changed-lines gate can't see max-lines), anti-slop, the changed-code quality gate and React Doctor pass.Current head
b6cbc76c74bincludes main through7f43d9b2c26, the ordered recovery integration, and the failed-send fixture correction. Web, node and CLI typechecks passed, and all 22 static checks passed. The initial explicit-file host/renderer run had 573 passing files and 5,482 passing tests; its recovery test failure was corrected and the full compaction file then passed 23/23. Four files added by the latest main merge passed 43/43. Four failures remain in two unchanged main test files: three exact elapsed-time labels drifted under load, and a historical-edit stream test exceeded its 30-second limit while recording zero repeated edit work. Their source and components have no PR changes; no broader rerun or unrelated timeout fix was made. Final-head CI completed successfully: all 16 checks that ran passed, including all five test shards, static analysis/typechecking, both packages, mobile verification/bundling, relay integration and cross-version wire compatibility. Sixteen other checks were skipped; they are not counted as passes.Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
ConversationCommandParamsis strict, sodeliveryis sent only after the host advertisesagent-session.queued-commands.v1, and only while it also advertises the queue. A queued command card is a card body the queue already publishes (AgentJournalMessageItemwith its existing optionalcommand).Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)