Skip to content

fix(native-chat): a prompt card owns the chat input until its answer lands (terminal-backed chat, desktop and phone) - #25761

Merged
brennanb2025 merged 27 commits into
mainfrom
brennanb2025/nc-terminal-prompt-gate
Oct 6, 2026
Merged

brennanb2025 merged 27 commits into
mainfrom
brennanb2025/nc-terminal-prompt-gate

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 27 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2718 $\color{#cf222e}{\Huge{\mathbf{−}}}$​94 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2624
Prod 72 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2150 $\color{#cf222e}{\Huge{\mathbf{−}}}$​626 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1524

ELI5

In terminal-backed native chat, the chat drives an agent CLI (Claude Code, Codex, OpenCode) that runs in a terminal. When the agent stops to ask for approval ("Allow Bash?") or asks a question, the CLI is waiting for keystrokes. Before this PR, the ordinary chat box stayed active, so a message you typed was sent as keystrokes into that prompt. The agent could read it as your answer and approve or deny a tool you never meant to touch.

Now the prompt card owns the input while the agent is waiting. On desktop the card takes the chat box's place. On the phone, Send is disabled, but typing, dictation and attachments still work. You can collapse the card to a one-line strip at any time to get the chat box back. Collapsing sends nothing to the agent, and Escape collapses instead of denying.

What Changed

Before: an approval or question card appeared above the chat box, and pressing Enter in the chat box sent your draft into the agent's prompt as an answer.

After, desktop:

  • While the agent waits, the card replaces the chat box. Your draft is kept: the chat box stays mounted but hidden and cannot send.
  • When you answer with Allow, Deny or a question choice, the card goes away for that prompt, and the chat box comes back with your draft.
  • The chevron in the card header, or Escape, collapses the card to a one-line strip above the chat box. The chat box is usable again and nothing is written to the agent. The strip's chevron expands the card back. A half-answered question keeps its progress.
  • A new prompt, meaning a new wait from the agent, always shows the full card again.
  • If a message was about to send when the card appeared, it is not sent. It stays in the chat as "Message not sent", because its Enter would have answered the prompt.
  • The card takes keyboard focus only from its own pane, never from a text field elsewhere in the app.

After, phone: while a card is shown, only Send is disabled. You can still type, dictate and attach. The same collapse chevron frees Send for that prompt.

How it works:

  • The view decides between card and chat box from one value computed while rendering.
  • An answer hides only the prompt occurrence it answered. That record lives per pane on desktop and per chat tab on the phone, so switching between the chat and terminal views doesn't bring an answered prompt back. One small bounded store in src/shared holds these records for both platforms, and entries are dropped when the prompt clears or changes, or when the tab closes.
  • An answer only dismisses the card when the write is confirmed. Prompt answers send an optional requireWriteSettlement flag, and current hosts reply with whether the terminal actually accepted the bytes.
    • Older hosts ignore the flag and reply accepted: true as before. That is treated as accepted, so a newer phone or desktop works normally against an older host.
    • Refused or unknown writes keep the card with its choices enabled. You can also collapse it.
    • Ordinary chat sends and terminal-pane keys (Esc, Ctrl+C) are unchanged.
  • A multi-keystroke answer waits for each complete write before the next one, so Enter can no longer arrive before the text it submits.

Why

The bug was that ordinary chat input and prompt answers share one keystroke stream. Making the card the only input while a prompt waits removes that collision on both desktop and phone.

Orca can tell when a prompt opens, but agents send no event when one closes after Deny, Escape or an answer typed in the terminal. A host-side lock on ordinary messages could therefore strand the user indefinitely, so this PR keeps everything in the client's presentation layer and always leaves a way out: collapse. The host refusal of ordinary messages is deliberately not added. That should wait until real prompt-closing events have been captured.

Alternatives considered:

  • Dismiss the card on click, without confirming the write. Rejected: a lost write would give the chat box back while the agent was still waiting.
  • Lock input until the host status clears. Rejected: Deny or Escape typed in the terminal never clears it, and the phone keeps showing a waiting status for up to 30 minutes.

Differences from the common pattern

  • Prompt state is inferred from agent hooks and the terminal, not kept as a host-owned request record that closes on every ending. Temporary. Follow-up: capture real approval-closing lifecycles, then move prompt ownership to the host.
  • The chat box comes back while a card is collapsed. The common pattern keeps the composer hidden while any interaction is pending. Intended: because prompt state is inferred, the user must never be locked out by a stale prompt. The trade-off: a message sent while a still-open prompt is collapsed can answer it, as before this PR.
  • The card dismisses on a confirmed terminal write, not on a host-side state change. Temporary, same follow-up.
  • Escape collapses rather than answers, and the collapsed card keeps its content mounted. This matches the common pattern.

Linked Issue

Maintainer change; no linked issue. Related work: #25588 and #24934 → #25430 → #25624.

Visual Proof

Remote Electron app on a paired Mac. The app ran fully isolated: its own home and data folders, launched in the background, with screenshots taken through the developer protocol. The prompts were injected through the app's state the same way tests/e2e/native-chat-ask-user-question-card.spec.ts does. The agent was a stand-in that logs every byte it receives. All clicks and keys were real input.

Before (main): card above an active chat box After: the card owns the input
before after
Escape or chevron: collapsed strip, chat box usable, nothing sent Expanded again
collapsed expanded
After Allow: chat box back with the draft Card appeared mid-typing: Enter sent nothing
after allow typing race
Question card (collapse chevron beside Cancel) Question collapsed to a strip
question question strip
Escape collapsed the card, then typing went straight to the chat box Half-typed answer kept after collapse and expand
escape then type partial answer
A card does not take focus from a field outside the chat Collapse tooltip
focus stays outside tooltip

Testing

  • Manually tested the rendered desktop app on macOS (remote paired Mac, isolated profile; four rounds on successive heads; see Visual Proof). The stand-in's byte log showed exactly one 1 for Allow and one 2 for the question answer, and a double-clicked Cancel sent a single Escape. Typing, Enter, Escape, collapse, expand, a new prompt, and a card arriving mid-typing all wrote zero bytes.
  • Automated tests added or updated:
    • desktop and phone composition tests through the real controller, view, card and send path;
    • write-confirmation tests against both current and older hosts;
    • pane-key regression tests;
    • each fix has a test that fails without it.
  • Not tested on a phone device or simulator, Windows, WSL, Linux, a physical SSH host, real agent CLIs' live prompts, or deployed mixed versions. These rest on automated tests.

Review

Reviewed in several rounds, with independent reviewers per area (desktop, delivery and wire compatibility, phone, architecture), a readiness pass, and remote app QA. Details are in the review comment on this PR.

Agent skill upstream boundary

  • Not applicable; no upstream skill-installer material copied or translated.

Notes

Known limits, left as follow-ups:

  • If a write lands but its confirmation is lost, answering again can repeat a keystroke.
  • A write that has already been sent can't be recalled.
  • A card answer on a local daemon terminal can wait up to 5 seconds for confirmation. On an SSH terminal whose link has already stalled, the 60-second confirmation timeout closes that SSH connection.
  • On the phone, an Ask that the user answered in chat and the agent then re-asks identically while the phone shows the terminal view can stay hidden. Main behaves the same way.

Checklist

  • Scope: the prompt card owns chat input on desktop and phone, plus the write confirmation that dismissal needs
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots attached
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, mobile and mixed-version impact considered
  • Final-head CI passes on a039c40 (16 passed, 16 skipped, 0 failed)

@brennanb2025

brennanb2025 commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Review summary

The problem. In terminal-backed native chat, the agent CLI runs in a terminal. While it waits for an approval ("Allow Bash?") or a question answer, anything typed into the chat box was sent as keystrokes into that prompt, so a normal message could approve or deny a tool by accident.

What changes for the user.

  • While a prompt waits, the prompt card owns the input.
    • Desktop: the card takes the chat box's place, and the draft is kept.
    • Phone: only Send is disabled; typing, dictation and attachments still work.
  • Answering hides that prompt and gives the chat box back.
  • The card's chevron, or Escape on desktop, collapses it to a one-line strip above a usable chat box and sends nothing to the agent. The strip expands it again, and a half-answered question keeps its progress.
  • A new prompt always shows the full card.

Fixed in this review

Earlier rounds fixed F01–F05, R2-D01/M01/M02, CI-C01–C06 and N01, as recorded in the previous version of this comment. This review found and fixed:

P2, PR-caused or from earlier loop fixes

  • Mixed versions broke prompt answers. A newer phone or desktop treated every older host's normal "accepted" reply as unknown. Cards never went away, every answer showed "Response unconfirmed", and ordinary desktop sends showed "Delivery unconfirmed". Older hosts' replies now count as accepted again, as on main. Only prompt answers ask a current host to confirm the write, and the workaround code for older hosts was deleted.
  • Locked out after answering elsewhere. If a prompt was answered in the terminal or on another device, the phone's Send stayed disabled for up to 30 minutes, and the only way out typed another key into the agent. The card can now always be collapsed, which writes nothing.
  • Enter in the first frame went to the agent. For about one frame after a card appeared, the hidden chat box could still send (Enter) or deny (Escape) into the prompt. The hidden chat box now cannot write at all, and focus moves to the card.
  • An identical question could stay hidden. A question asked again with the same text stayed hidden, so the next chat message answered it. A new wait now always shows the card.

P3

  • Terminal panes:
    • Every terminal pane's Esc and Ctrl+C had started waiting for write confirmation, and frozen panes lost their recovery signal; they are back to their original path.
    • A partly failed large paste is now reported as unconfirmed.
  • Desktop chat box and focus:
    • A message sent just as a card appeared was silently lost. It now stays as "Message not sent", and no Ctrl+U is typed under the dialog.
    • Answered or collapsed cards came back after switching between the chat and terminal views.
    • Keyboard focus is fixed in three places: when a card appears, after a collapse, and the card no longer takes focus from text fields outside the chat.
  • Card behavior:
    • Escape denied the tool; it now collapses instead.
    • Collapsing in the middle of a multi-step answer cut it short.
    • Collapsing a question lost the partial answer.
    • The phone's question card showed Cancel and Hide side by side in the same style; there is now one collapse control.
  • Phone:
    • A collapsed question could reveal a stray "Permission requested" card whose Allow would type y.
    • Dictation and attachments were blocked while a card was up.
    • The keyboard stayed up after a collapse.
  • Code: the desktop and phone dismissal stores were two copies of one store; they are now a single shared store. Some file names and imports were also cleaned up.

Deferred, not regressions on main

  • If a write lands but its confirmation is lost, answering again can repeat a keystroke.
  • A write that has already been sent can't be recalled.
  • A card answer on a local daemon terminal can wait up to 5 seconds for confirmation.
  • On an SSH terminal whose link has already stalled, the 60-second confirmation timeout closes that SSH connection.
  • On the phone, an identical Ask re-asked while the phone shows the terminal view can stay hidden (main does the same).
  • Minor: OpenCode Stop now waits in the per-terminal send queue; "Message not sent" has no retry button.
  • Host-side refusal of ordinary messages remains a temporary follow-up. Agents send no event when a prompt closes, so live prompt-closing events must be captured first.

Verified

  • Independent reviewers in four rounds, covering:

    • desktop;
    • delivery and wire compatibility in both version directions;
    • phone;
    • architecture.

    Every finding above was reproduced before it was fixed and re-checked after. The fix tests fail when the fix is removed.

  • Readiness checklist, all eight categories, with no P0 or P1:

    • SSH and remote;
    • crashes and unbounded growth;
    • security;
    • performance;
    • functional correctness;
    • backward compatibility and data loss;
    • cross-platform;
    • code organization.
  • Remote rendered QA on a paired Mac, in four rounds on successive heads. The app ran with an isolated profile and was driven by a non-Claude agent. Prompts were injected through app state, and every click and key was real. A stand-in agent logged each byte it received:

    • Allow wrote 1 once.
    • The question answer wrote 2.
    • A double-clicked Cancel wrote one Escape.
    • Typing, Enter, Escape, collapse, expand, a new prompt, and a card arriving mid-typing wrote zero bytes.
    • Screenshots are in the PR body.
  • CI on the final head: head a039c40af78f88d7b3ed45b393ca0c51ea1eca22 (includes current main 7faa9f7): 16 checks passed, 16 skipped, 0 failed: PR Checks, Mobile Checks, test LoC. The skipped jobs (headless, rendered end-to-end, input-method, WSL) provide no evidence.

Not verified

  • Real agent CLIs' live prompts and closing events.
  • A refused or unknown write in the running app; this is covered by automated tests only.
  • Phone on a device or simulator.
  • Windows and WSL.
  • Linux and a physical SSH host.
  • Paired-host network failures.
  • Deployed mixed versions.

The PR remains a draft. Nothing was merged or marked ready.

A newer desktop or phone talking to a host that predates the write
settlement field read every accepted reply as "unconfirmed". Prompt cards
never dismissed, the phone showed "Response unconfirmed" on every tap and
ordinary chat messages were held as "Delivery unconfirmed".

The reader now uses writeSettlement when present and otherwise keeps the
host's whole-write accepted/refused verdict, exactly as before this branch.
Only prompt answers ask for provider settlement; ordinary callers
(follow-up delivery, paste drafts, option commands, composer sends) are
back on the original contract, so the legacy-handoff error class, its
flag, the sequence-only send helper and the mobile handoff hook are gone.
Every pane's Escape/Ctrl+C goes through pty:writeAccepted. This branch had
switched that IPC to wait for provider settlement, which dropped the
"remount this pane" signal for a daemon session awaiting recovery and could
stall later keystrokes behind a slow daemon acknowledgment.

pty:writeAccepted is back to its original local-only, synchronous write.
Prompt answers opt into settlement with requireWriteSettlement on the same
channel, and a settled refusal while the daemon recovers now sends the same
remount signal. Ordinary verified sends regain their original fallback write.
A settled local write split into chunks returned plain false when a later
chunk was refused after earlier ones were accepted. Callers read false as
"nothing was written", so chat showed "Message not sent" with a prefix
already in the agent's input. It now reports the write as unconfirmed,
the same verdict the paired host gives for a partial write.
When an approval or question card took the input region, the composer
unmounted. A message still waiting for its Enter was cancelled and its
bubble deleted after the draft had already been cleared, so the message
vanished without a notice; composer history was also wiped each time.

The composer now stays mounted but hidden while a card owns input, so its
state survives. A send that has not submitted yet is still stopped (its
Enter would answer the card), but its bubble stays with "Message not sent"
so the text is not lost. The composer ref is detached while hidden, so
root typing, paste and reveal focus never reach it.
The "answered" dismissal lived in component state. Toggling chat to
terminal and back, a PTY reconnect, or leaving the phone session and coming
back while the approved tool was still running brought the answered
approval back, and it then took over the input again.

Desktop now keeps the answered occurrence per pane outside the view;
phone keeps it per chat tab outside the controller. Both still retire it
when the pane observes the prompt clear or change, desktop also when the
tab retires, and both maps are size-bounded.
Older hosts' accepted answers now dismiss like acknowledged ones, the
composer stays mounted under a card, and answered prompts survive a view
remount. The gate's invariant, oracle, assertion list, new test files and
the two latest evidence runs now describe that contract.
… Send on the phone

The chat input could stay locked behind a card the host never closes (for
example after a Deny typed in the terminal), and Escape on a focused
approval card denied the tool even when the user meant to close a picker.

- A Hide control (chevron) on terminal approval and question cards, desktop
  and phone, hides that prompt occurrence and gives the input back. It writes
  nothing to the agent and uses the same per-occurrence dismissal as an
  acknowledged answer, so a new occurrence shows the card again.
- On desktop, Escape on a card now does the same Hide instead of Deny, and a
  card that appears while the user is typing no longer takes focus.
- On the phone, a card blocks only Send: typing, dictation and image attach
  keep working on the draft. The placeholder is back to the normal one.
Since an older host's accepted write now counts as delivered, the
requireWriteSettlement comment and the reliability gate's oracle said the
opposite of the code. Both now describe the current rule.
…e round-2 gaps

Hide removed a card completely, so nothing on screen said a prompt was still
waiting, and several edges let the chat type into a live prompt.

- Collapse (the header chevron, or Escape on desktop) folds the card to a
  one-line strip above the composer; the strip's chevron expands it back.
  Collapsing writes nothing, frees the composer, and is disabled while an
  answer is still being written. Each pane or tab keeps the occurrence as
  answered or collapsed, so a remount restores the same view.
- Questions now carry the host wait's start like approvals, so an identical
  question in a new wait shows again (desktop and phone). A transcript-only
  prompt, which has no wait start, is dropped when the view stops observing
  it, and a transcript still loading no longer clears a dismissal.
- Desktop: while a card owns the input, the hidden composer cannot send or
  interrupt even if it still has keyboard focus, and the card takes focus in
  the same commit. A send the card retires no longer types Ctrl+U under it.
- Phone: an Ask hides the heuristic card read from the same waiting status,
  and the dismissal store is scoped by host, worktree and tab.
…n pane

Collapsing a question card unmounted it, so expanding it again lost the
chosen step, selections and typed "Other" text; Escape typed in that text
field collapsed the card. A card arriving while the user typed in another
surface (sidebar, notes, a browser URL bar) also took the keyboard.

- The collapsed card now stays mounted but hidden (and inert on desktop)
  under its strip, on desktop and phone, so a partial answer survives
  collapse and expand. Escape inside the card's text field no longer
  collapses it. The question card shows the same focus ring as the approval
  card.
- A card takes focus only from inside its own pane (its hidden composer) or
  from the page body, never from a text field elsewhere.
- Desktop and phone share one dismissal store in src/shared, bounded by the
  existing scope-cache helper, which moves to src/shared with it.
- The card send imports the verified helper from its own module, and the
  phone files are split so each name matches its contents (header action,
  strip, lane selector).
Since a collapsed card stays mounted, Escape or the chevron left keyboard
focus inside the now hidden, inert card. The composer's reveal-focus took
that as focus already in the pane and stood down, then the browser dropped
focus to the page body, so typed keys went nowhere.

Reveal-focus now treats focus inside a hidden or inert subtree as not in the
pane and focuses the composer. On the phone, collapsing a card also
dismisses the keyboard so a hidden reply field does not keep it.
The question card header spread its three items with justify-between, which
put the new chevron in the middle of the header. The title now takes the
free space, as in the approval card, so the chevron sits next to Cancel at
the right edge.
…2025/nc-terminal-prompt-gate

# Conflicts:
#	mobile/src/session/MobileNativeChatView.tsx
Main now runs Vitest under Bun, which resolves a long data: URL import as a
package name, so the SSH delivery test loads its bundled mobile module from a
temp file instead. The phone prompt harnesses mock the live line that main's
view now renders, and add Platform, which main's text-selection helper reads,
the same way main's own view tests do.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 6, 2026 15:25
@coderabbitai

coderabbitai Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

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: fdedd425-001f-412a-9699-c351bd9a3905
📥 Commits

Reviewing files that changed from the base of the PR and between 7faa9f7 and a039c40.

📒 Files selected for processing (99)
  • config/reliability-gates.jsonc
  • config/scripts/ci-shard-timings.json
  • mobile/src/session/MobileNativeChatAsk.tsx
  • mobile/src/session/MobileNativeChatCardHeaderAction.test.tsx
  • mobile/src/session/MobileNativeChatCardHeaderAction.tsx
  • mobile/src/session/MobileNativeChatComposer.tsx
  • mobile/src/session/MobileNativeChatOverlay.tsx
  • mobile/src/session/MobileNativeChatPermission.test.ts
  • mobile/src/session/MobileNativeChatPermission.tsx
  • mobile/src/session/MobileNativeChatPromptCard.test.tsx
  • mobile/src/session/MobileNativeChatPromptCard.tsx
  • mobile/src/session/MobileNativeChatPromptStrip.tsx
  • mobile/src/session/MobileNativeChatQuestion.test.tsx
  • mobile/src/session/MobileNativeChatQuestion.tsx
  • mobile/src/session/MobileNativeChatView.prompt-presentation.test.ts
  • mobile/src/session/MobileNativeChatView.test.ts
  • mobile/src/session/MobileNativeChatView.tsx
  • mobile/src/session/__mocks__/mobile-prompt-controller.ts
  • mobile/src/session/mobile-native-chat-controller-contract.ts
  • mobile/src/session/mobile-native-chat-permission-send.test.ts
  • mobile/src/session/mobile-native-chat-permission-send.ts
  • mobile/src/session/mobile-native-chat-prompt-dismissals.ts
  • mobile/src/session/mobile-native-chat-send.ts
  • mobile/src/session/mobile-prompt-controller.test.ts
  • mobile/src/session/use-mobile-native-chat-answer-send.ts
  • mobile/src/session/use-mobile-native-chat-ask-dismiss.test.ts
  • mobile/src/session/use-mobile-native-chat-ask-dismiss.ts
  • mobile/src/session/use-mobile-native-chat-cancel-ask.ts
  • mobile/src/session/use-mobile-native-chat-controller.test.ts
  • mobile/src/session/use-mobile-native-chat-controller.ts
  • mobile/src/session/use-mobile-native-chat-input-lease.ts
  • mobile/src/session/use-mobile-native-chat-message-send.ts
  • mobile/src/session/use-mobile-native-chat-prompt-cards.ts
  • mobile/src/session/use-mobile-native-chat-prompt-dismiss.ts
  • mobile/src/session/use-mobile-native-chat-prompt-presentation.ts
  • mobile/src/session/use-mobile-native-chat-prompts.ts
  • src/main/ipc/pty/ipc/write-input-acceptance.test.ts
  • src/main/ipc/pty/ipc/write-input.ts
  • src/main/runtime/orca-runtime-controller-knows-pty-is-live.ts
  • src/main/runtime/orca-runtime-resolve-authoritative-terminal-wait-permission.ts
  • src/main/runtime/orca-runtime-runtime-id.ts
  • src/main/runtime/rpc/methods/terminal/terminal-send-method.ts
  • src/main/runtime/runtime-terminal-writer.test.ts
  • src/main/runtime/runtime-terminal-writer.ts
  • src/preload/api/pty-api.ts
  • src/preload/api/pty-bridge-session-control.ts
  • src/renderer/src/components/native-chat/NativeChatApprovalCard.test.tsx
  • src/renderer/src/components/native-chat/NativeChatApprovalCard.tsx
  • src/renderer/src/components/native-chat/NativeChatComposer.test.tsx
  • src/renderer/src/components/native-chat/NativeChatComposer.tsx
  • src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx
  • src/renderer/src/components/native-chat/NativeChatInteractiveCard.tsx
  • src/renderer/src/components/native-chat/NativeChatPromptCollapse.tsx
  • src/renderer/src/components/native-chat/NativeChatQuestionCard.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-composer.test.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.prompt-card-input.test.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
  • src/renderer/src/components/native-chat/NativeChatResolvedView.turn-status.test.tsx
  • src/renderer/src/components/native-chat/native-chat-command-marker.ts
  • src/renderer/src/components/native-chat/native-chat-composer-types.ts
  • src/renderer/src/components/native-chat/native-chat-draft-cache.test.ts
  • src/renderer/src/components/native-chat/native-chat-input-clear.ts
  • src/renderer/src/components/native-chat/native-chat-observed-send.test.ts
  • src/renderer/src/components/native-chat/native-chat-observed-send.ts
  • src/renderer/src/components/native-chat/native-chat-pending.ts
  • src/renderer/src/components/native-chat/native-chat-prompt-dismissals.ts
  • src/renderer/src/components/native-chat/native-chat-pty-send-queue.ts
  • src/renderer/src/components/native-chat/native-chat-runtime-send.test.ts
  • src/renderer/src/components/native-chat/native-chat-runtime-send.ts
  • src/renderer/src/components/native-chat/native-chat-session-option-cache.ts
  • src/renderer/src/components/native-chat/use-native-chat-composer-interrupt.ts
  • src/renderer/src/components/native-chat/use-native-chat-composer-reveal-focus.ts
  • src/renderer/src/components/native-chat/use-native-chat-interactive-send.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-interactive-send.ts
  • src/renderer/src/components/native-chat/use-native-chat-prompt-card-focus.ts
  • src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.test.ts
  • src/renderer/src/components/native-chat/use-native-chat-prompt-card-presentation.ts
  • src/renderer/src/components/native-chat/use-native-chat-send-lifecycle.ts
  • 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/runtime/runtime-terminal-input-recording.ts
  • src/renderer/src/runtime/runtime-terminal-inspection.ts
  • src/renderer/src/runtime/runtime-terminal-verified-input.test.ts
  • src/renderer/src/runtime/runtime-terminal-verified-input.ts
  • src/renderer/src/store/slices/retired-terminal-tab-state-sweep.ts
  • src/shared/native-chat-scope-cache.test.ts
  • src/shared/native-chat-scope-cache.ts
  • src/shared/prompt-dismissal-store.ts
  • src/shared/pty-write-settlement.ts
  • src/shared/rpc-contract/terminal-unary-params.ts
  • src/shared/runtime-terminal-contracts.ts
  • src/shared/runtime-terminal-send-contract.ts
  • src/shared/terminal-send-acknowledgment.ts
  • tests/e2e/__mocks__/native-chat-ssh-delivery.ts
  • tests/e2e/native-chat-ssh-delivery.unit.test.ts

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


📝 Walkthrough

Walkthrough

The changes add provider-settled terminal writes and use acknowledgment outcomes when handling native chat replies. Mobile and desktop prompt cards now track prompt occurrences, support collapse and expansion, and connect dismissal to reply outcomes. The composer remains mounted while a prompt card owns its input. Tests cover settlement results, prompt lifecycle, and older-host behavior. An experimental reliability gate records criteria and local evidence, alongside a CI timing-key update.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to a039c

A prompt card now owns chat input until its answer is confirmed written to the terminal, and replies from older hosts are still treated as accepted. No concrete defect remains open at this head. Live testing with real agent CLIs, on phone hardware, and on non-macOS platforms has not been done; that is a validation gap, not an identified bug.

🚥 Pre-merge checks | ✅ 4 | ❓ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ❓ Inconclusive Docstring coverage is 46.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 50 functions across 50 files. (48 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: prompt cards take control of chat input until the answer is confirmed. It also specifies the affected chat platforms.
Description check ✅ Passed The description covers the required sections with a clear user-facing explanation, rationale, linked-issue context, visual proof, testing details, review notes, platform limits, and checklist. The mai…
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.
Full details: Docstring Coverage

Explanation

Docstring coverage is 46.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 50 functions across 50 files. (48 skipped: 8 unsupported, 40 over the file limit.)

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • 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.

@brennanb2025
brennanb2025 merged commit 468e4e1 into main Oct 6, 2026
65 checks passed
zpyoung added a commit to zpyoung/orca that referenced this pull request Oct 12, 2026
…'s settled write

Upstream's SSH delivery suite (stablyai#25761) showed two gaps in the fork's adoption of
delivery uncertainty:
- Ask-answer groups were paced on fixed timers, so a slow body could be
  overtaken by the selector's Enter. With a settlement callback each group now
  waits for the previous group's provider acknowledgment, and settles the answer
  a submit gap after the last one, as upstream's observed writes do.
- The fork's acknowledged transport called its own pty:writeInputAccepted. Upstream's
  writeAccepted with requireWriteSettlement now answers the same question,
  SSH included, so the fork uses it; sends that wait on settlement also ask a
  paired host for it.
The SSH suite now drives the fork's target-based API.
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