Skip to content

fix(agent-prompt): do not paste onto residual composer text - #24245

Open
brynnclaw wants to merge 7 commits into
stablyai:mainfrom
brynnclaw:fix/agent-prompt-residual-composer
Open

brynnclaw wants to merge 7 commits into
stablyai:mainfrom
brynnclaw:fix/agent-prompt-residual-composer

Conversation

@brynnclaw

@brynnclaw brynnclaw commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

When Orca hands a prompt to an agent in a terminal, it pastes the text into the agent's input box and presses Enter. It never checked whether the box was already holding something. If a person had typed /hel, or an earlier attempt had left its text sitting there, the new prompt was glued onto the old text and sent as one garbled message. Now Orca looks at the input box first. If it holds someone else's text, Orca stops and says so instead of mixing the two. If it holds Orca's own earlier paste of this exact prompt, whose Enter never landed, Orca presses Enter instead of pasting a second copy, but only after checking again, right before that Enter, that the box still holds exactly that paste. If it still shows a prompt the agent has already taken, only because the screen has not caught up yet, Orca pastes as usual, but only until the box has been seen empty or changed once: after that, the same words are someone's recalled draft, and Orca stops. A moment when the screen does not show the box at all (the agent has hidden its cursor or moved it away to redraw a status line) proves nothing either way, so it does not end that allowance.

What Changed

Before: orca terminal send --text 'Responda apenas: OK4' --enter into a Claude Code pane whose composer holds /hel submits /helResponda apenas: OK4. A retry after an unobserved submit pastes a second copy onto the first.

After, in writeTerminalAgentPrompt only (the single path every agent prompt takes):

  • New src/main/runtime/agent-prompt-composer-residue.ts (pure): classifyAgentPromptComposerResidue(cursorContext, pastePayload, ownPaste) reads the composer's typed text and returns one of three verdicts:

    • none: the composer is empty or not recognized, or it still shows Orca's own last paste after the agent's turn start confirmed that paste (a late repaint).
    • same-prompt: Orca's own last paste on this pane is byte-identical to this prompt, its turn start was not observed, and the composer still shows that paste. Matching words on screen are never enough on their own. The screen is matched to Orca's paste by its words, because the composer re-wraps and re-indents what it renders. Byte identity between that paste and this prompt is what tells prompts apart when they differ only in whitespace.
    • foreign: any other text, including words that match this prompt but that Orca did not paste here byte for byte.
  • src/shared/terminal-composer-draft.ts: detectTerminalComposerDraft takes an optional { typedOnly: true }. In that mode, a dim suggestion is not read as input, on the cursor row or on the rows it wraps onto below the cursor. Codex footer recognition is unchanged, and every existing caller keeps the default. New isTerminalComposerAtCursor says whether the cursor sits in a composer the detector recognizes, empty or not (false for a hidden cursor or a cursor off the composer). detectTerminalComposerDraft and hasTerminalComposerPlaceholder return what they returned before.

  • orca-runtime-write-terminal-agent-prompt.ts: before the paste, it reads the pane's headless emulator and acts on the verdict:

    • foreign: throws agent_prompt_composer_not_empty and writes nothing.
    • same-prompt: writes only Enter. The pre-write check (beforeWrite) is awaited first. After it, the writer waits for the pane's screen model to parse whatever arrived during the check, then re-reads the composer synchronously, right before the Enter. Output that leaves the parked paste in place (a title update, say) does not block the Enter. If the composer no longer reads as that parked paste, or more PTY output was queued after that parse, it throws agent_prompt_composer_not_empty and writes nothing. A bare Enter therefore never submits text that replaced the parked prompt during the check.
    • none, or a screen model that is not the whole screen (provider-preferred snapshot or pending hydration): today's behavior, unchanged.

    It remembers the last prompt it pasted on each live pane (same PTY generation), and marks it as landed when the agent's turn start is observed. A paste counts as landed on the hook or lifecycle turn-start edge, or on the strict path's verification when the agent was idle beforehand. It never counts on output alone. A landed paste is treated as late paint for at most 10 s after its turn start, and only until the composer is first seen reading anything else: on the turn start, and after each later output chunk once the screen model has parsed it, the composer is read again, and the first read that shows the composer no longer holding that paste (emptied, or other text) ends the exemption. A read that cannot see the composer (a hidden cursor, or a cursor parked off the composer while the agent redraws a status line) proves nothing and leaves the exemption in place. A late repaint never passes through an emptied composer. A recalled or retyped draft does pass through one once the agent repaints after its turn start, so after that point, or after 10 s, the same text on screen is someone's draft again. A recall made before Orca observes the turn start is not seen this way (see Known limits). The watch stops at that first read, or when the window ends. Within 1 s of Orca's own Enter on the same pane, it also waits out that window before judging. The map prunes itself to live panes.

  • New src/main/runtime/agent-prompt-composer-ledger.ts: holds that per-pane record of Orca's own last paste and last Enter (the 1 s settle, the 10 s landed window and the repaint watch), and the two composer reads that rest on it (the read before the paste and the synchronous re-read before an Enter-only re-submit). It reads the pane's screen through a narrow host, so it is type-checked. These parts were moved out of the runtime writer so the writer stays within the repo's line limit.

  • orca-runtime-create-agent-prompt-render-gate.ts: watchAgentPromptComposer reads the composer after the screen model has parsed the output so far, then after each later output chunk, through the existing subscribeToTerminalData hook the render gate already uses. A screen model that is not the whole screen (hydrating or provider-preferred) is skipped.

  • agent-prompt-composer-residue.ts: composerNoLongerShowsAgentPromptPaste is what the watch asks after each read. It is true only when a recognized composer is empty or holds other words, and false for a read that finds no composer. It reads typed text only, as the classifier does: a dim suggestion is not input, so it counts neither as the paste still showing nor as other words.

  • orca-runtime-controller-knows-pty-is-live.ts: bytesWritten no longer counts a paste that was skipped.

  • rpc/errors.ts: agent_prompt_composer_not_empty is added to the runtime passthrough codes, so callers see a stable code instead of a generic error.

Why

#15976 has two halves. The false agent_prompt_stalled half is already addressed on main: orchestration and agent-launch prompt callers now pass acceptQueued and get an input_accepted receipt (a plain terminal.send without an orchestration mutation still verifies strictly), and the coordinator maps a stall to dispatched-unobserved without resending. The reporter confirmed it no longer reproduces on 1.4.199. The concatenation half still reproduced (4/4 on 1.4.199), and it is what turns one failure into a cascade.

Alternatives considered:

  • Clear the composer before pasting (Ctrl-U, Escape, and so on): rejected. The keystroke differs per agent, and it would silently destroy a draft a person typed.
  • Recognize paste placeholders ([Pasted text #1 +N lines]) as "ours": rejected. A placeholder does not prove whose paste it was, so such a composer is foreign and the retry is refused rather than doubled.
  • Match the parked prompt by its words alone: rejected. The screen drops indentation and line breaks, so two prompts that differ only in whitespace look the same, and Enter would submit the wrong one. Only Orca's own byte-identical paste counts as this prompt.
  • A longer fixed settle after Orca's Enter: rejected. It slows every back-to-back prompt and still fails one repaint later. The agent's turn start is direct evidence that the paste landed.
  • Refuse and report is what the issue proposes. It is agent-neutral: it keys off the composer shapes Orca already recognizes, and any TUI it does not recognize keeps today's behavior.

Linked Issue

Fixes #15976

Visual Proof

N/A: no UI change. The change is in the runtime's prompt write path. The visible effect is a CLI or RPC error code (agent_prompt_composer_not_empty) where a garbled submission used to happen.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

New src/main/runtime/agent-prompt-composer-residue.test.ts, 38 cases:

  • 9 for the classifier:
    • foreign text.
    • Orca's own parked paste, with re-wrapped whitespace.
    • The typed lead line.
    • Matching words that Orca did not paste byte for byte (a whitespace-only difference): foreign.
    • Orca's own landed paste still painted: none for the next prompt, identical or not, while other text stays foreign.
    • A dim cursor-right suggestion is ignored.
    • A dim suggestion wrapped below the cursor is ignored. This uses the shared detector's own wrapped-suggestion fixture from terminal-composer-draft.test.ts.
    • A Codex composer with a dim footer still reads its typed text.
    • An empty or unrecognized composer.
  • 29 runtime cases that drive the real headless emulator with a framed ❯ composer:
    • /hel in the composer: rejected with agent_prompt_composer_not_empty and zero writes.
    • A retry after a first attempt whose Enter was lost (agent_prompt_stalled): exactly one more write, \r, with bytesWritten: 1.
    • The same retry for an agent that takes Enter in the paste write (OMP).
    • The same retry with an awaiting pre-write check (beforeWrite): when the check leaves the parked prompt alone, exactly one \r; when another writer replaces the composer text during the check, rejected with agent_prompt_composer_not_empty and nothing written.
    • The same retry when the pre-write check only emits output that leaves the composer as it was (a title update): exactly one \r, accepted with bytesWritten: 1.
    • Text Orca did not paste, swapped for other text during the pre-write check: rejected, nothing written.
    • A retry of a prompt that differs from the parked earlier paste only in indentation: refused, nothing written.
    • /hel in the composer while the screen model is still hydrating: the check steps aside and the prompt is pasted as before.
    • A dim suggestion on the cursor row, and a dim suggestion wrapped onto the row below the cursor: each pasted once and submitted once.
    • Two concurrent prompts to one pane, with the emptied composer repainted 0.8 s after the Enter: both pasted.
    • Two concurrent prompts, a different one and an identical one, with the emptied composer repainted 1.5 s after the Enter while the turn start is seen at once: both pasted and both submitted.
    • Two concurrent prompts where only a title arrives while the first prompt is still painted: the second is still pasted (unrelated output does not end the late-repaint reading).
    • A first prompt whose turn starts and whose composer empties, then is recalled into the composer 2 s later, followed by a different and by an identical next prompt: each refused with agent_prompt_composer_not_empty and nothing written.
    • The same recall when the composer empties only 1.5 s after the turn start was observed, so only a read after a later output chunk can see it: a different and an identical next prompt, each refused, nothing written.
    • The slow-repaint case (emptied composer painted 1.5 s after the Enter) with one mid-turn frame, split across two output chunks, during which the composer cannot be read: a hidden cursor, a cursor moved up to redraw a status line, and a cursor moved below the composer to redraw a status line. In each, the next different prompt is still pasted (2 pastes).
    • A composer that empties to a dim suggestion of the same words 1.5 s after the turn start was observed, then has the prompt recalled into it, followed by a different and by an identical next prompt: each refused, nothing written. The dim suggestion is not typed text, so that read counts as an emptied composer.
    • The slow-repaint case where the still-painted paste first gains a dim completion after it, 0.3 s after the Enter: the next different prompt is still pasted (2 pastes).
    • The slow-repaint case (emptied composer painted 1.5 s after the Enter) with a multi-line first prompt (if ready:\ndeploy(), rendered on two composer rows) and with a double-spaced one (first task): the next different prompt is still pasted (2 pastes). The screen re-wraps and re-indents what it renders, so the watch compares both sides with whitespace normalized.
    • A composer that goes from the landed paste straight to other words (fix the) 1.5 s after the turn start was observed, with no empty frame read in between, then has the prompt recalled over them, followed by a different and by an identical next prompt: each refused, nothing written. Other words end the late-repaint reading just as an emptied composer does.

src/shared/terminal-composer-draft.test.ts gets two cases: the wrapped-suggestion fixture read with { typedOnly: true } gives no draft, and isTerminalComposerAtCursor is true for an empty composer at the cursor and false for a hidden cursor, a cursor below the composer, and no screen.

In the first revision: ten of the cases then new failed on the revision before it: the three classifier cases for whitespace, the landed paste and the wrapped suggestion; the four runtime cases for indentation, the wrapped suggestion and both slow repaints; and the three pre-write-check cases (text Orca did not paste, a parked prompt replaced during the check, and a title emitted during the check). The shared detector case then new failed there too. Without the synchronous re-check, the parked-prompt retry wrote \r and was reported accepted. The title case pinned the output half of that re-check: with only the composer re-read and no check for newer output, it still wrote \r. After that change, all 23 cases then in the file passed, and so did the existing shared detector suite.

The second review round added three cases and changed one. On 154dfcf, the two recalled-draft cases fail: the next prompt is pasted onto the recalled draft. The title-during-check case fails too: the retry is refused with zero writes. All three pass after the fix. The title case is the earlier refusal case with its expectation inverted, and the unrelated-output case guards the late-repaint reading.

A further self-review of e62cf9e found that one mid-turn read that could not see the composer ended the late-repaint reading, so the first round's slow-repaint fix regressed whenever the agent hid its cursor or moved it off the composer before repainting. It also found that the per-chunk half of the watch had no test. The third commit (28d21cb) adds five cases and the shared detector case. The three unreadable-frame cases fail on e62cf9e (the next prompt is refused with agent_prompt_composer_not_empty, one paste) and pass on 154dfcf and after the fix. The two later-emptying recall cases pass on e62cf9e and after the fix, and fail on 154dfcf. They also fail when the per-chunk subscription is removed and only the read at the turn start is kept, which the earlier cases did not catch.

The fourth commit (7078d9d) changes tests only and adds the three dim-suggestion cases above. They pass on 28d21cb unchanged. They fail when the watch compares the composer's raw text, dim suggestion included, instead of its typed text: the two recall cases then paste the next prompt onto the recalled draft, and the dim-completion case refuses the next prompt. No earlier case caught that.

The fifth commit (9640818) changes tests only and adds the four cases above. They pass on 7078d9d's source unchanged (the same source as 28d21cb). The multi-line and double-spaced cases fail when either side of the watch's comparison is left un-normalized: the still-painted landed prompt then reads as other words, and the next prompt is refused with agent_prompt_composer_not_empty (one paste). The other-words cases fail when the watch ends only on an empty composer: the next prompt is then pasted onto the recalled draft. No earlier case caught either.

The sixth commit (4080baa) changes one test comment only. The comment above the multi-line and double-spaced late-repaint frames said the composer shows the landed prompt with its whitespace collapsed; those frames keep their line break and double space, so it now says the composer still shows the landed prompt, re-wrapped or with its own spacing.

Local runs on Linux, on a heavily loaded machine, each under a memory and runtime cap:

  • Rebased onto current main (seventh commit, a move with no behavior change): main's composer-ready launch path (feat(agent-launch): keep long prompts off the launch line and paste them after readiness (step 1 of 7) #24257) changed the writer, so the render gate is now skipped for a parked prompt or a ready composer, and the composer-ready second Enter keeps its count beside pasted. That path put the writer over the 300-line limit, so the two composer reads moved unchanged into the ledger. pnpm exec vitest run over the new file, the shared detector suite, agent-prompt-submission-runtime.test.ts, agent-prompt-submission-runtime-hook-and-generation.test.ts, agent-prompt-submission-windows-submit-delay.test.ts, rpc/methods/agent-launch-terminal-prompt.test.ts, rpc/terminal-agent-prompt-send.test.ts and orca-runtime.test.ts: 1476 passed / 1 skipped / 0 failed. With this PR's source reverted and its tests kept, 20 of the 61 cases in the two PR test files fail. oxlint and oxfmt --check pass on the changed files. tsc --noEmit passes for config/tsconfig.node.json, config/tsconfig.tc.web.json and config/tsconfig.tc.cli.json, and pnpm build passes, each under a 16 GB per-command cap.
  • Current head (4080baa, one test comment only): pnpm exec vitest run over the new file and the shared detector suite: 61 passed / 0 failed (38/38 and 23/23), the same as on 9640818. Every pnpm lint stage passes; the type-aware stage ran on the changed test file only, because the full stage exceeded the 4 GB per-command cap. No code changed in this commit, so typecheck and the build were not re-run.
  • Fifth commit (9640818, tests only): pnpm exec vitest run over the 40 test files that import the changed modules or drive sendTerminalAgentPrompt: 1827 passed / 1 skipped / 0 failed, including 38/38 in the new file and 23/23 in the shared detector suite. pnpm lint, pnpm typecheck:web, pnpm typecheck:cli and a narrow node-project tsc over the changed test file passed. No source file changed in that commit, so the build was not re-run.
  • Fourth commit (7078d9d, tests only): pnpm exec vitest run over the same 40 test files: 1823 passed / 1 skipped / 0 failed, including 34/34 in the then-new file and 23/23 in the shared detector suite. pnpm lint, pnpm typecheck:web, pnpm typecheck:cli and a narrow node-project tsc over the changed test file passed. No source file changed in that commit, so the build was not re-run.
  • In the first revision: pnpm exec vitest run for the changed test files plus the existing prompt-path suites: agent-prompt-submission-runtime.test.ts, agent-prompt-submission-runtime-hook-and-generation.test.ts, orca-runtime.test.ts and rpc/terminal-agent-prompt-send.test.ts, in two batches to fit the runtime cap. All 6 files passed, 1420 passed / 1 skipped / 0 failed, including 23/23 in the then-new file and 22/22 in the shared detector suite.
  • Second round (e62cf9e): pnpm exec vitest run over the 40 test files that import the changed modules or drive sendTerminalAgentPrompt (including orca-runtime.test.ts and the shared composer-draft suite): 1814 passed / 1 skipped / 0 failed. The same 40 files with this PR's previous revision's source and the new tests: exactly the 3 new cases fail (1811 passed / 1 skipped).
  • Third commit (28d21cb), same conditions: the same 40 files pass, 1820 passed / 1 skipped / 0 failed. pnpm lint, pnpm typecheck:web (which covers src/shared), pnpm typecheck:cli, a narrow node-project tsc over the ledger, residue and shared detector modules, and pnpm run build:electron-vite pass. pnpm typecheck:node again exceeded the 8 GB per-command cap.
  • In the first revision: a narrow tsc over the changed modules and their tests passed, including the new ledger module. The runtime writer is under the repo's existing @ts-nocheck split marker, so its types are not checked by this.
  • In the first revision: oxlint with the repo config on the changed files, and pnpm run check:code-quality:changed, passed (0 new findings across the 8 files changed then).
  • Second round, on a heavily loaded machine with each command under a memory and runtime cap: pnpm lint passes in full. pnpm typecheck:web and pnpm typecheck:cli pass. pnpm typecheck:node (the project holding the runtime) exceeded the machine's 8 GB per-command cap, so it was replaced by a narrow tsc over the two type-checked changed modules (agent-prompt-composer-ledger.ts, agent-prompt-composer-residue.ts), which passes; the two changed runtime class files sit under the repo's existing @ts-nocheck split marker. pnpm build starts with that same full typecheck, so pnpm run build:electron-vite (which bundles the main process with these files) was run instead and passes. CI is relied on for the full pnpm test, pnpm typecheck and pnpm build.

Not run against a live Claude Code or Codex session. No composer transcript was captured with docs/reference/agent-pty-transcript-capture.md, because that process records a live, signed-in agent CLI, and none was used for this change. So the runtime frames are hand-built in the shapes the existing terminal-composer-draft tests cover. A captured, scrubbed Claude and Codex composer transcript is the right follow-up (see Notes).

AI Disclosure

Claude Opus 5.5 (claude-opus-5-5), Anthropic.

Review

AI coding-agent review summary:

  • Cross-platform: no platform-specific code. The check runs on the headless emulator the runtime already keeps for every PTY. Windows ConPTY paste timing is untouched, because the check happens before the paste and the Enter-only path skips paste-ingest waits that no longer apply.
  • SSH/remote/local: the check runs where the runtime's emulator runs. Over SSH, the emulator lags the remote agent by the link latency. When the agent's turn start is observed, that lag no longer turns a landed prompt into a refusal or a dropped copy. Where no turn start is observed, the 1 s settle has to absorb the latency (see Notes). When the screen model is not authoritative (provider-preferred snapshot or pending hydration), the check steps aside and behavior is unchanged. There is no new wire field. The new error code is a plain string that older clients surface as an error message.
  • Agents and integrations: vendor-neutral. The check keys off the existing composer detector (framed ❯, bold › / » with a footer). Other TUIs, shells and OMP's paste-with-Enter mode get none unless their composer is recognized. When Orca's own parked paste is recognized in OMP's mode, only the Enter is sent. The shared detector's new option is opt-in, so the terminal projection and its other callers are unchanged.
  • Orchestration callers: a refused prompt is a pre-write failure, so worker-start, dispatch --inject and the coordinator see an ordinary error. Nothing was delivered, so failing the dispatch is correct and no preamble is duplicated.
  • Performance: one extra read of the existing emulator per agent prompt: an await on its write chain plus a cursor-context scan of a few rows. An Enter-only re-submit awaits the write chain once more after the pre-write check and scans those rows again, synchronously, right before its Enter. After a landed prompt, the composer is scanned again after each output chunk, but only until the first scan that shows the composer no longer holding that prompt (normally the agent's own empty repaint right after the turn start), and never past 10 s. The runtime keeps one remembered paste and at most one such watch per live pane. The only added wait is up to 1 s, and only when Orca itself pressed Enter on the same pane within the last second.
  • UI: none.
  • Security: no new input surface. The check only reads the screen and never writes on a refusal. It lowers risk: an injected preamble can no longer be silently prefixed with unrelated text, and a parked prompt can no longer be submitted in place of a different one. Enter alone, which delivers none of the payload itself, is sent only when the composer is re-read after the last await and still holds Orca's own byte-identical paste, so text another writer puts there during the pre-write check is not submitted and credited to this request.

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

  • Known limits:
    • The composer shapes are pinned by hand-built frames, not by a captured transcript. Capturing one needs a live, signed-in Claude Code or Codex session, which this change did not use.
    • A parked multi-line prompt that the agent shows only as a paste placeholder is refused rather than re-submitted.
    • A parked prompt that this runtime did not paste is refused, even when its words match: a person's typing, a paste from before an Orca restart, or a different PTY generation. Nothing is written, so the caller can clear the composer and retry.
    • The slow-repaint handling needs an observed turn start. Without one, it falls back to the 1 s settle after Orca's own Enter. That is the case for an agent with no lifecycle verifier on the queued path, for a first prompt that stalled, and on the strict path for an agent that was already working. If the repaint is slower than that, a different back-to-back prompt is refused (nothing written), and an identical one gets only an Enter into an emptied composer while being reported as accepted.
    • Before an Enter-only re-submit, output that arrives after the post-check parse and before the Enter (a few microtasks) still makes Orca refuse rather than send the Enter. Nothing is written, and a retry reads the composer again.
    • A recall is recognized by the composer being seen emptied or changed first, after Orca observed the turn start. If an agent never repaints its composer after a turn start (the old text stays painted until a person recalls the same words over it), or keeps it out of view (a hidden cursor, or the cursor parked off the composer) until then, that recall still reads as a late repaint for up to 10 s.
    • The same holds when the composer empties and the prompt is recalled before Orca observes the turn start (between Orca's Enter and the turn-start edge): the watch starts only at the turn start, never sees the emptied composer, and the next prompt is pasted onto the recalled draft. The window is usually short, but it is not covered.
    • A person typing between the check and the paste is not covered, which is inherent to any screen check. An Enter-only re-submit re-reads the composer with no await before its Enter, but keystrokes already on their way to the agent and not yet painted are invisible to any screen check.
  • Behavior change for callers: a send into an occupied composer now errors instead of "succeeding" with a corrupted prompt.

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)

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Medium risk] Adds logic to detect and block pasting into a composer with existing text.

The PR is not yet safe to merge because a recalled draft can be concatenated with a new prompt and harmless output can block a valid parked retry.

Findings

  1. P1 Recalled draft gets pasted over ▶
  2. P1 Unrelated output blocks parked retries ▶

Summary

The PR adds composer-residue checks before agent-prompt writes, remembers Orca’s own paste and submission state, and reports a stable refusal code instead of pasting over recognized foreign text.

  • Retries can submit a recognized parked paste with Enter alone, while submission receipts count only bytes actually written.
  • The new landed-paste exception and parked-retry recheck introduce the two failures identified above.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Agent prompt request] --> B[Read headless composer and paste ledger]
  B --> C{Residue verdict}
  C -->|Foreign| D[Refuse without writing]
  C -->|Own paste parked| E[Recheck, then send Enter]
  C -->|None or landed repaint| F[Paste prompt, then submit]
  E --> G[Verify submission]
  F --> G
  G --> H[Update landing record]
Loading

Reviews (2) · Last reviewed commit: "fix(agent-prompt): do not paste onto res..."

Comment thread src/main/runtime/orca-runtime-write-terminal-agent-prompt.ts Outdated
Comment thread src/main/runtime/orca-runtime-write-terminal-agent-prompt.ts Outdated
Comment thread src/main/runtime/agent-prompt-composer-residue.ts

@pullfrog pullfrog 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.

Important

The new classifier treats a wrapped dim suggestion below the cursor as typed residue, so it returns foreign and refuses a prompt into an otherwise-empty Claude composer. Reproduced locally against the shared detector's own fixture; details inline.

Reviewed changes

  • Residue classifier (agent-prompt-composer-residue.ts) — pure classifyAgentPromptComposerResidue returns none / same-prompt / foreign by reusing detectTerminalComposerDraft and comparing normalized draft text against the paste payload.
  • Prompt write path (orca-runtime-write-terminal-agent-prompt.ts) — reads the pane's headless composer before writing: foreign throws agent_prompt_composer_not_empty with no write, same-prompt writes only \r, everything else is unchanged; adds a 1 s settle window after Orca's own Enter and a pasted flag on the result.
  • Byte accounting (orca-runtime-controller-knows-pty-is-live.ts) — bytesWritten counts the payload only when delivery.pasted, so an Enter-only submission reports 1 byte.
  • Error passthrough (rpc/errors.ts) — adds agent_prompt_composer_not_empty to RUNTIME_PASSTHROUGH_CODES.
  • Tests (agent-prompt-composer-residue.test.ts) — 5 classifier cases plus 6 runtime cases driving the headless emulator.

ℹ️ Screen shapes are only pinned by synthetic frames

The classifier's correctness rests on exact real-screen shapes (empty composer, dim suggestion, parked prompt, post-submit repaint), but those are covered only by hand-built TerminalCursorContext fixtures — the PR notes that no Claude or Codex composer transcript was ever captured. AGENTS.md calls for a captured transcript for any rule that reads what an agent CLI paints. A captured Claude composer transcript would have exposed the false positive below and would also guard the parked/repaint assumptions the settle window depends on.

Technical details
# Terminal-reading rule has no captured-transcript evidence

## Affected sites
- `src/main/runtime/agent-prompt-composer-residue.test.ts:35` — `composerFrame` synthesizes the Claude shape rather than replaying a capture.
- `docs/reference/agent-pty-transcript-capture.md` — the repo's required process for these rules.

## Required outcome
- At least one captured, scrubbed Claude (and ideally Codex) composer transcript drives the classifier cases: empty composer with a wrapped suggestion, parked own prompt, foreign draft, and post-submit repaint.

## Open questions for the human
- Is there a captured transcript available internally that could be added here?

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/main/runtime/agent-prompt-composer-residue.ts Outdated
@coderabbitai

coderabbitai Bot commented Oct 1, 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: 146a8065-142e-4962-b396-24e405d342c6

📥 Commits

Reviewing files that changed from the base of the PR and between 154dfcf and 4080baa.


📒 Files selected for processing (7)
  • src/main/runtime/agent-prompt-composer-ledger.ts
  • src/main/runtime/agent-prompt-composer-residue.test.ts
  • src/main/runtime/agent-prompt-composer-residue.ts
  • src/main/runtime/orca-runtime-create-agent-prompt-render-gate.ts
  • src/main/runtime/orca-runtime-write-terminal-agent-prompt.ts
  • src/shared/terminal-composer-draft.test.ts
  • src/shared/terminal-composer-draft.ts

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 runtime checks composer text before it submits an agent prompt. It rejects foreign text and submits a matching parked prompt with Enter alone. A per-pane ledger tracks submissions and pastes across composer repaint and parsing delays. Prompt results and byte accounting reflect whether the runtime pasted the payload. The change also adds typed-only composer detection, runtime tests, and RPC passthrough for the composer-not-empty error.


Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk

Merge Risk: ⚪ Minimal · up to 4080b

The change stops agent prompts from being pasted onto leftover composer text and submits an already parked matching prompt with Enter alone. No concrete merge-blocking issue was found. The composer frames are hand-built rather than captured from live Claude Code or Codex sessions, which is worth following up but does not block merge.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4080b

The change reduces accidental submission of existing text, but delayed confirmation can leave retry state inconsistent. The identified issue is confined to the affected terminal session; write-completion guarantees remain unverified.

Retained concerns

  • Medium · reliability · observed: Late recovery updates a request to turn_started without settling the new paste-ownership ledger. After initial observation times out, a later successful observation leaves the paste unlanded. If the screen still shows that paste, a subsequent identical request remains eligible for Enter-only submission, while a different request is classified as foreign. The delivery receipt and the safety gate therefore disagree about whether the earlier text was consumed.

Security review details

Security Blast Radius

  • inferred — The identified state inconsistency is pane-local: paste records are keyed by terminal identity and lifecycle generation, and mismatched generations cannot reuse ownership. The inspected exports do not establish a new remote endpoint or cross-tenant authority.

Trust Boundaries and Controls

  • observed — Enter-only retries require a local ownership record and a synchronous final check of the terminal generation, output write-chain, and matching composer text. Existing permission checks remain before the write, limiting stale-screen and permission-transition races.
  • observed — Typed composer rows are derived from undimmed cells in the terminal's rendered buffer, not authenticated input events. Local payload ownership therefore supplements a visual heuristic; it does not prove the current composer content's provenance. Attacker reachability sufficient to exploit this distinction was not established.

Resilience and Maintainability Implications

  • observed — Submission serialization is scoped to terminal identity and generation. New pastes replace prior pane records and stop prior repaint watchers; landed-paste exemptions expire or end after a recognized composer repaint.

Hardening Proposals

  • proposed — Unify immediate and delayed turn-start settlement so both update paste ownership using the same pane, generation, and request binding. Preserve the existing protection against applying a late confirmation to a newer paste.



🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 9 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 Issue [#15976] asks Orca to prevent retries from concatenating onto residual composer text. The runtime rejects recognized foreign text without writing and sends Enter alone only for its own matching …
Out of Scope Changes check Passed The composer detector, per-pane ledger, render watch, byte-count adjustment, RPC error passthrough, and tests all support safe handling of residual composer text and Enter-only retries for [#15976]. N…
Title check Passed The title clearly and concisely describes the primary change: preventing agent prompts from being pasted onto residual composer text.
Description check Passed The description follows the required template and provides a clear user-facing explanation, implementation details, linked issue, testing results, known limits, review considerations, and checklist st…


  • Fix all pre-merge checks with AI
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Autopilot is currently an internal CodeRabbit preview.


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.

@brynnclaw
brynnclaw force-pushed the fix/agent-prompt-residual-composer branch from 5319db8 to 154dfcf Compare October 1, 2026 07:45
Comment on lines +46 to +47
if (ownPaste.landed) {
return 'none'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Recalled draft gets pasted over If someone recalls or retypes the previous prompt within 10 seconds of its turn starting, this branch treats that real composer text as a late repaint and returns none. A different prompt is then pasted onto the draft, concatenating the two messages.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, confirmed: a prompt recalled into the emptied composer within the 10 s window was read as a late repaint, and the next prompt was pasted onto it. Fixed in e62cf9e, with a follow-up in 28d21cb and more tests in 7078d9d and 9640818.

A landed paste now counts as a late repaint only until the composer is first seen reading anything else. When the turn start marks the paste as landed, Orca reads the composer once its screen model has parsed the output so far, and again after each later output chunk. The first time a read finds the composer no longer showing that paste (it was emptied, or holds other text), the exemption ends for good. A read that cannot see the composer at all, because the agent hid its cursor or moved it off the composer to redraw a status line, proves nothing and leaves the exemption in place. In e62cf9e such a read also ended it, which brought back the slow-repaint refusal from the first round; 28d21cb fixes that. A late repaint never passes through an emptied composer, while a recall or retype goes through one as soon as the agent repaints after its turn start, so the same words after that are someone's draft and are refused with agent_prompt_composer_not_empty, with nothing written.

Two cases are left, and the PR description lists both as known limits. First, an agent that never repaints after its turn start (or keeps its composer out of view until then), where a recall paints the same words over the stale ones. Second, a composer that empties and gets the prompt recalled before Orca observes the turn start: the watch starts at the turn start, so it never sees that emptied composer. The watch stops at the first read that shows the composer changed, or at the end of the 10 s window, so it adds no work to a pane's output after that.

Tests in agent-prompt-composer-residue.test.ts:

  • refuses a different next prompt onto its landed prompt recalled after the composer emptied and refuses an identical next prompt ...: the first prompt's turn starts and its composer empties, then the prompt is recalled into the composer 2 s later. On 154dfcf both cases paste the next prompt onto the recalled draft. Now both are refused with no write.
  • refuses a different next prompt onto a recall after a composer that emptied after the turn start and refuses an identical next prompt ...: the composer empties only 1.5 s after the turn start, so only the read after that later output chunk can see it. Both are refused with no write. Both fail if the per-chunk read is removed and only the read at the turn start is kept.
  • still reads its landed prompt as a late repaint while only unrelated output arrives: a title arriving while the old paste is still painted does not end the exemption, so the late-repaint fix from the first round still holds. The two earlier late-repaint cases (repaint 1.5 s after the Enter, for a different and an identical next prompt) still pass unchanged.
  • still pastes a different next prompt over a late repaint after a hidden-cursor frame split across chunks, ... after a cursor-up status redraw split across chunks and ... after a status line redrawn below the composer split across chunks: the slow-repaint case with one mid-turn read that cannot see the composer. All three fail on e62cf9e (the next prompt is refused) and pass on 154dfcf and on 28d21cb.
  • refuses a different next prompt onto a recall after the composer emptied to a dim ghost of the same words, refuses an identical next prompt ... and still pastes a different next prompt over a late repaint that shows a dim completion after the landed text (7078d9d, tests only): the watch reads the composer's typed text, so a dim suggestion of the same words counts as an emptied composer, and the recall after it is refused with no write, while a dim completion after the still-painted paste does not end the exemption. All three fail if the watch compares the composer's raw text, dim suggestion included, and pass on 28d21cb unchanged.
  • still pastes a different next prompt over a late repaint of a multi-line landed prompt, ... of a double-spaced landed prompt, refuses a different next prompt onto a recall after the composer showed other words and refuses an identical next prompt ... (9640818, tests only): the watch compares the composer's text with the paste with whitespace normalized on both sides, so a still-painted multi-line or double-spaced landed prompt keeps the late-repaint reading and the next prompt is pasted. A composer that goes from the paste straight to other words ends the exemption just as an emptied one does, so the recall after it is refused with no write. The first two fail if either side of the comparison is not normalized, the last two if only an empty composer ends the exemption. All four pass on 28d21cb unchanged.

The PR description's known-limit line about recalling the last prompt within 10 s is replaced by the two narrower limits above.

Comment on lines +89 to +93
!state ||
state.writeChain !== parsedThrough ||
this.getPtyLifecycleGeneration(ptyId) !== generation
) {
return false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unrelated output blocks parked retries If a title update or other harmless PTY output arrives during the awaited pre-write check, writeChain changes even when the composer still holds the parked prompt. This check then refuses a valid Enter-only retry with agent_prompt_composer_not_empty, leaving the prompt unsent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, agreed. Refusing on any new output was stricter than the retained concern needed: a title update during the pre-write check refused a valid Enter-only retry. Fixed in e62cf9e. This reverses the "newer output alone is enough to refuse" behaviour described in our reply to CodeRabbit's retained concern; the part of that concern that matters, text replacing the parked prompt during the check, is still refused.

After beforeWrite returns, an Enter-only retry now waits for the pane's screen model to parse whatever arrived during the check. It then re-reads the composer synchronously, right before the Enter. Output that leaves Orca's own parked paste in place, like a title, no longer blocks the Enter. The safety check is unchanged: if the composer no longer reads as that byte-identical parked paste, or more output arrives after that parse and before the Enter, Orca refuses with agent_prompt_composer_not_empty and writes nothing. So a bare Enter still cannot submit text that replaced the parked prompt.

Tests in agent-prompt-composer-residue.test.ts:

  • submits a parked prompt when only a title arrives during the pre-write check: on 154dfcf this retry was refused with zero writes. Now it sends exactly one \r and is accepted with bytesWritten: 1. This case replaces the earlier one that pinned the refusal.
  • judges a parked prompt again after a pre-write check that replaces the parked prompt (the retained-concern case) still refuses with zero writes, and ... that leaves the parked prompt alone still sends exactly one \r.

The PR description's known-limit line about harmless output refusing an Enter-only retry is removed.

@pullfrog pullfrog 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.

✅ No new issues found. The wrapped dim-suggestion false positive from the prior review is fixed, the new per-pane ledger closes the slow-repaint gaps the PR previously listed as limits, and no new concatenation or systematic-refusal path turned up.

Reviewed changes

  • Own-paste ledger (agent-prompt-composer-ledger.ts) — moved Orca's per-pane paste and Enter bookkeeping into a new class: a 1 s settle window keyed by lifecycle generation, plus a remembered last paste with a 10 s landed window.
  • Landed-prompt handling (agent-prompt-composer-residue.ts, write path) — a prompt the agent already took, still painted on screen, now classifies as an empty composer (none) instead of parked or foreign, which fixes the different/identical back-to-back cases the PR previously listed as known limits.
  • Typed-only composer read (terminal-composer-draft.ts) — added a typedOnly mode that excludes dim rows below the cursor (not just the cursor row), and the classifier now uses it. This resolves the prior review's wrapped-suggestion false positive; callers passing no options are behavior-identical.
  • Synchronous pre-write re-read (orca-runtime-write-terminal-agent-prompt.ts) — isAgentPromptStillParked re-checks the composer immediately before the Enter-only write, so output arriving during beforeWrite cannot turn a parked prompt into a blind Enter.
  • Coverage — tests now drive the landed late-repaint path (different and identical next prompt), the hydration bypass, the pre-write race, and the wrapped dim suggestion.

ℹ️ Nitpicks

  • The PR's Notes still list slow-repaint limits — a different back-to-back prompt being refused, and an identical one being dropped while reported as delivered — that the new AgentPromptComposerLedger landed window removes. Consider updating them so the description does not read as still-current behavior.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@brynnclaw

Copy link
Copy Markdown
Contributor Author

Re: pullfrog review: IMPORTANT + "Screen shapes are only pinned by synthetic frames"

Thanks. The IMPORTANT finding is fixed in 154dfcf (see the inline thread). On captured transcripts: agreed that AGENTS.md asks for one. docs/reference/agent-pty-transcript-capture.md records a live, signed-in Claude Code or Codex session, and none was used for this change, so the frames are still hand-built in the shapes the existing terminal-composer-draft tests use. The PR description states this as a known limit. A scrubbed Claude and Codex composer capture is the follow-up: empty with a wrapped suggestion, parked own prompt, foreign draft, and post-submit repaint.

@brynnclaw

Copy link
Copy Markdown
Contributor Author

Re: greptile summary

Thanks for the summary. All three findings are addressed in 154dfcf, with details in their threads. Whitespace: same-prompt now needs Orca's own byte-identical paste. Slow repaint, both the refused different prompt and the dropped identical one: a paste whose turn start was seen counts as a late repaint. The case with no observed turn start is a stated known limit.

@brynnclaw

Copy link
Copy Markdown
Contributor Author

Re: coderabbit: retained concern + pre-merge Docstring Coverage

Thanks for the retained concern, confirmed. Fixed in 154dfcf. Before an Enter-only re-submit, the composer is now re-read synchronously after the last await (beforeWrite) and right before the Enter. If any PTY output was queued for the pane's screen model since the composer was read, or it no longer reads as Orca's own byte-identical parked paste, the send is refused with agent_prompt_composer_not_empty and nothing is written. A bare Enter therefore no longer submits text that replaced the parked prompt and credits it to this request. A runtime test replaces the parked text inside beforeWrite: it now rejects with zero writes, where it previously wrote \r and was reported accepted. A paired case shows that an untouched parked prompt still gets exactly one \r. Another case emits only a title during beforeWrite, which leaves the composer as it was: it is refused with zero writes, so newer output alone is enough to refuse.

Thanks. Orca doesn't use a docstring-coverage threshold; AGENTS.md asks for short comments only where needed, so we're keeping the changed functions as they are.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

This run re-reviewed the commits since the prior pullfrog review (154dfcf4af), which add the late-repaint watch and two related fixes.

  • Late-repaint watch (agent-prompt-composer-ledger.ts, agent-prompt-composer-residue.ts, orca-runtime-create-agent-prompt-render-gate.ts) — markLanded now subscribes to terminal output and reads the composer after each parsed chunk, setting repainted the first time it no longer shows Orca's own paste; getOwnPaste then returns null so identical words are treated as a foreign draft again. The watch is identity-guarded, uses an unref'd 10 s expiry, and is torn down on the next rememberPaste.
  • Unreadable frames prove nothing (agent-prompt-composer-residue.ts, 28d21cb6ec) — composerNoLongerShowsAgentPromptPaste returns false when the cursor is not in a recognized composer (hidden cursor, cursor off the composer), so a mid-turn redraw no longer ends the exemption and causes a false refusal.
  • Harmless pre-write output (orca-runtime-write-terminal-agent-prompt.ts, e62cf9e18e) — the Enter-only retry re-reads the composer after beforeWrite and only refuses when the composer no longer reads as the parked paste, so a title update during the check no longer blocks the Enter.

I could not construct a concrete lifecycle or race bug in the watch beyond the limits the PR body already documents: concurrent prompts are serialized per pane so a stale watch cannot mutate a newer record; generation-keyed records prevent a dead incarnation's verdict from applying; a state that goes non-judgeable mid-watch yields no read and the exemption lapses at the window. A local run of the new and shared suites passes (61/61). The prior inline thread is resolved.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

brynnclaw and others added 7 commits October 9, 2026 11:34
sendTerminalAgentPrompt pasted into the agent composer without looking at it,
so leftover text (a human draft or an earlier attempt whose Enter did not land)
was concatenated with the new prompt and submitted as one garbled line.

Before pasting, read the pane's headless emulator and classify the composer's
typed text, with dim suggestions excluded on the cursor row and on the rows
they wrap onto. Refuse other text with agent_prompt_composer_not_empty. Submit
Orca's own byte-identical earlier paste with Enter alone, but only while its
turn start is unobserved. Read a paste whose turn start was observed as a late
repaint and paste the next prompt as usual. Keep today's behavior when the
composer is empty or unrecognized.

The pre-write check (beforeWrite) awaits, so before an Enter-only submit
re-read the composer synchronously: if any PTY output arrived for the screen model
since it was judged, or it no longer reads as that parked paste, refuse with
agent_prompt_composer_not_empty instead of sending a bare Enter.

Fixes stablyai#15976

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nder repaint and harmless output

A landed paste still painted counted as a late repaint for 10 s after its
turn start, so a prompt someone recalled or retyped into the emptied
composer in that window was pasted over. Read the composer after the turn
start and after each later output chunk, once parsed; the first read that no
longer shows the landed paste ends the exemption, and the same words after
that are refused as foreign. The watch stops then, or after 10 s.

An Enter-only retry refused whenever any PTY output arrived during the
pre-write check, even a title that left the parked prompt in place. Wait for
the screen model to parse that output, then re-read the composer
synchronously right before the Enter; refuse only if it no longer reads as
the parked paste or more output arrived after the parse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the late-repaint watch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… not a dim suggestion

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oth sides and ends on other words, not only an empty composer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stay within the writer's line limit

Main's composer-ready launch path (stablyai#24257) added a resubmit method to the
prompt writer, which put the rebased file at 330 counted lines against
the repo's 300-line limit. The two composer reads (the residue read and
the synchronous parked re-read) move unchanged into the type-checked
ledger, which reads the pane's screen through a narrow host. No behavior
change.
@brynnclaw
brynnclaw force-pushed the fix/agent-prompt-residual-composer branch from 4080baa to 50982cf Compare October 9, 2026 11:29
@brynnclaw

Copy link
Copy Markdown
Contributor Author

Thanks. The gap is real: a late observation does not mark the paste as landed. We are keeping it that way on purpose, because both outcomes it leaves are safe.

A paste marked as landed means "this text on screen is a late repaint, paste the next prompt over it". The late-repaint watch can only end that reading if it sees the composer emptied or changed after the mark. A late observation can come long after the actual turn start. By then the agent has normally repainted its composer, so if the words are still there, they are most likely a recalled or retyped draft. Marking the paste as landed at that point would start the 10 s late-repaint window on a draft the watch never saw appear. The next different prompt would then be pasted onto it, which is the concatenation this PR exists to stop. Today it widens only the short gap between Orca's Enter and the observed turn start, which the description lists as a known limit. Landing on a late observation would stretch that gap to the whole observation timeout.

Without the mark, the two cases you describe go like this:

  • A different next prompt is refused with agent_prompt_composer_not_empty and nothing is written. The caller can clear the composer and retry.
  • An identical next prompt gets Enter only, and only after a synchronous re-read right before the Enter confirms that the composer still holds Orca's own byte-identical paste. That Enter submits the same words the caller asked for. The one bad case is an agent that already emptied its composer while Orca's screen model still shows the old text. That needs the emulator to lag the agent by the whole observation timeout, and the 1 s settle after Orca's own Enter covers the usual lag.

So the receipt and the composer gate can disagree here, but the gate's answer is the one that fails safe. If maintainers prefer the receipt and the gate to agree, the change is small: store the request id with the remembered paste, and have the late observation land it only when the ids match. That also keeps a late confirmation from landing a newer paste. We would add it with a test that pins the trade-off above.

Reviewed before submission by an automated reviewer against the change and its tests.

@brynnclaw

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (1dab0d0). The only conflict was in orca-runtime-write-terminal-agent-prompt.ts, against the composer-ready launch path from #24257. The render gate is now skipped for a parked prompt or a ready composer. The composer-ready second Enter keeps its count beside the new pasted flag. That path put the writer over the 300-line limit, so a seventh commit moves the two composer reads, unchanged, into the type-checked ledger. The description's What Changed and Testing sections are updated to match.

This branch has not been deployed

No deployments
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.

[Bug]: agent_prompt_stalled is reported for prompts that were delivered and submitted, and the retry pastes onto the leftovers

1 participant