Repository navigation
Conversation
|
There was a problem hiding this comment.
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) — pureclassifyAgentPromptComposerResiduereturnsnone/same-prompt/foreignby reusingdetectTerminalComposerDraftand 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:foreignthrowsagent_prompt_composer_not_emptywith no write,same-promptwrites only\r, everything else is unchanged; adds a 1 s settle window after Orca's own Enter and apastedflag on the result. - Byte accounting (
orca-runtime-controller-knows-pty-is-live.ts) —bytesWrittencounts the payload only whendelivery.pasted, so an Enter-only submission reports 1 byte. - Error passthrough (
rpc/errors.ts) — addsagent_prompt_composer_not_emptytoRUNTIME_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?DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (7)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughThe 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
|
5319db8 to
154dfcf
Compare
| if (ownPaste.landed) { | ||
| return 'none' |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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 emptiedandrefuses 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 startandrefuses 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 chunksand... 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 ...andstill 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 wordsandrefuses 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.
| !state || | ||
| state.writeChain !== parsedThrough || | ||
| this.getPtyLifecycleGeneration(ptyId) !== generation | ||
| ) { | ||
| return false |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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\rand is accepted withbytesWritten: 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 alonestill sends exactly one\r.
The PR description's known-limit line about harmless output refusing an Enter-only retry is removed.
There was a problem hiding this comment.
✅ 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 slandedwindow. - 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 atypedOnlymode 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) —isAgentPromptStillParkedre-checks the composer immediately before the Enter-only write, so output arriving duringbeforeWritecannot 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
AgentPromptComposerLedgerlanded window removes. Consider updating them so the description does not read as still-current behavior.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
|
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. |
|
Re: greptile summary Thanks for the summary. All three findings are addressed in 154dfcf, with details in their threads. Whitespace: |
|
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 ( 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. |
There was a problem hiding this comment.
✅ 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) —markLandednow subscribes to terminal output and reads the composer after each parsed chunk, settingrepaintedthe first time it no longer shows Orca's own paste;getOwnPastethen 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 nextrememberPaste. - Unreadable frames prove nothing (
agent-prompt-composer-residue.ts,28d21cb6ec) —composerNoLongerShowsAgentPromptPastereturns 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 afterbeforeWriteand 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.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
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.
4080baa to
50982cf
Compare
|
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:
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. |
|
Rebased onto current |

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' --enterinto a Claude Code pane whose composer holds/helsubmits/helResponda apenas: OK4. A retry after an unobserved submit pastes a second copy onto the first.After, in
writeTerminalAgentPromptonly (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:detectTerminalComposerDrafttakes 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. NewisTerminalComposerAtCursorsays whether the cursor sits in a composer the detector recognizes, empty or not (false for a hidden cursor or a cursor off the composer).detectTerminalComposerDraftandhasTerminalComposerPlaceholderreturn 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: throwsagent_prompt_composer_not_emptyand 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 throwsagent_prompt_composer_not_emptyand 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:watchAgentPromptComposerreads the composer after the screen model has parsed the output so far, then after each later output chunk, through the existingsubscribeToTerminalDatahook 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:composerNoLongerShowsAgentPromptPasteis 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:bytesWrittenno longer counts a paste that was skipped.rpc/errors.ts:agent_prompt_composer_not_emptyis 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_stalledhalf is already addressed onmain: orchestration and agent-launch prompt callers now passacceptQueuedand get aninput_acceptedreceipt (a plainterminal.sendwithout an orchestration mutation still verifies strictly), and the coordinator maps a stall todispatched-unobservedwithout 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:
[Pasted text #1 +N lines]) as "ours": rejected. A placeholder does not prove whose paste it was, so such a composer isforeignand the retry is refused rather than doubled.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
New
src/main/runtime/agent-prompt-composer-residue.test.ts, 38 cases:foreign.nonefor the next prompt, identical or not, while other text staysforeign.terminal-composer-draft.test.ts.❯composer:/helin the composer: rejected withagent_prompt_composer_not_emptyand zero writes.agent_prompt_stalled): exactly one more write,\r, withbytesWritten: 1.beforeWrite): when the check leaves the parked prompt alone, exactly one\r; when another writer replaces the composer text during the check, rejected withagent_prompt_composer_not_emptyand nothing written.\r, accepted withbytesWritten: 1./helin the composer while the screen model is still hydrating: the check steps aside and the prompt is pasted as before.agent_prompt_composer_not_emptyand nothing written.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.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.tsgets two cases: the wrapped-suggestion fixture read with{ typedOnly: true }gives no draft, andisTerminalComposerAtCursoris 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
\rand 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:
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 besidepasted. That path put the writer over the 300-line limit, so the two composer reads moved unchanged into the ledger.pnpm exec vitest runover 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.tsandorca-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.oxlintandoxfmt --checkpass on the changed files.tsc --noEmitpasses forconfig/tsconfig.node.json,config/tsconfig.tc.web.jsonandconfig/tsconfig.tc.cli.json, andpnpm buildpasses, each under a 16 GB per-command cap.pnpm exec vitest runover the new file and the shared detector suite: 61 passed / 0 failed (38/38 and 23/23), the same as on 9640818. Everypnpm lintstage 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.pnpm exec vitest runover the 40 test files that import the changed modules or drivesendTerminalAgentPrompt: 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:cliand a narrow node-projecttscover the changed test file passed. No source file changed in that commit, so the build was not re-run.pnpm exec vitest runover 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:cliand a narrow node-projecttscover the changed test file passed. No source file changed in that commit, so the build was not re-run.pnpm exec vitest runfor 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.tsandrpc/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.pnpm exec vitest runover the 40 test files that import the changed modules or drivesendTerminalAgentPrompt(includingorca-runtime.test.tsand 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).pnpm lint,pnpm typecheck:web(which coverssrc/shared),pnpm typecheck:cli, a narrow node-projecttscover the ledger, residue and shared detector modules, andpnpm run build:electron-vitepass.pnpm typecheck:nodeagain exceeded the 8 GB per-command cap.tscover the changed modules and their tests passed, including the new ledger module. The runtime writer is under the repo's existing@ts-nochecksplit marker, so its types are not checked by this.oxlintwith the repo config on the changed files, andpnpm run check:code-quality:changed, passed (0 new findings across the 8 files changed then).pnpm lintpasses in full.pnpm typecheck:webandpnpm typecheck:clipass.pnpm typecheck:node(the project holding the runtime) exceeded the machine's 8 GB per-command cap, so it was replaced by a narrowtscover 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-nochecksplit marker.pnpm buildstarts with that same full typecheck, sopnpm run build:electron-vite(which bundles the main process with these files) was run instead and passes. CI is relied on for the fullpnpm test,pnpm typecheckandpnpm 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 existingterminal-composer-drafttests 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:
❯, bold›/»with a footer). Other TUIs, shells and OMP's paste-with-Enter mode getnoneunless 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.worker-start,dispatch --injectand the coordinator see an ordinary error. Nothing was delivered, so failing the dispatch is correct and no preamble is duplicated.Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)