When a message carrying notes came back to the composer, the notes were cleared first and the
text written to the draft after. A crash between the two could lose both. The text is now saved
to the draft first, on every path that hands it back (the host's answer, the journal, a Stop).
Keys whose notes were not in the store yet stayed pending forever when the note never showed up:
a second "delivered" ending for notes already cleared, a page closed or a workspace removed, or a
note edited while its send was on the way. Each kept a store listener that scanned on every store
write for the rest of the run. A key now waits only while its workspace has not loaded; once it
has, a key with no note is dropped, and the listener detaches as soon as nothing is waiting.
A message an older build left waiting on its Retry is never sent again; once the chat's journal
loads it either comes back to the composer or the host's row shows it. Until then it was drawn as
a message on its way and read "Sending…", so QA saw it flash for a moment before its text went back
to the composer. It is now never drawn as one of this client's sends: no bubble and no notice. The
host's row, when there is one, or the composer is where it shows.
Notes sent to a chat had two owners when the message came back: its text went to the composer
and the notes returned to the shelf, so sending both delivered them twice. Notes now follow their
text: once the host has the message, or its text goes back to the composer (turned away, or taken
back by a Stop), the notes are used and leave the shelf. Only a message thrown away with nothing
handed back (a cancelled launch, or this window closing a failed chat) puts them back.
A send that ended before its workspace's or browser page's notes were in the store cleared
nothing, so those notes showed as unsent once they loaded. The keys are now kept until their
owner's notes load, then cleared.
The test that only called a mocked tab refresh now drives the real host-frame entry points (a
paired host's frame, and a local snapshot through the cancelled-launch filter). Stale "Retry"
wording is gone from a test name and a comment.
Notes handed to a chat were kept out of the next "Send notes" only in memory. After a reload,
while the chat still held the unsent message built from them, the notes were offered again and a
second send delivered them twice. A chat send also cleared its notes the moment it was queued,
so a message that came back to the composer left its notes gone from the shelf.
Now the chat's saved message carries the notes' keys (each naming its workspace or browser page),
for "Send notes to > New agent" and for a chat already open. A note stays off the shelf while a
message this client still holds carries its key and the host can still deliver that message; past
the host's window for it the hold lapses on its own. The message's own ending decides the rest:
once the host has it the notes are cleared as sent, from any send or resend and after a reload;
if it comes back, is withdrawn or is discarded, the notes return to the shelf (a returned
message's text is also in the composer). Nothing is released or deleted because a tab list or a
host sync no longer shows the chat.
One mechanism instead of two: the per-message watch and outcome promise are removed; the outbox's
entry endings drive both the launch prompt's result and the notes.
A press whose answer was lost stays quiet because Orca will send the Stop again. If a newer
message then makes Orca give the Stop up before that resend (or a resend is dropped), nothing was
ever said. The press's own notice ("The agent wasn't stopped.") is now shown when the Stop is
given up; it is honest, since the outcome is unknown.
Since the line under a message Orca keeps resending dropped every step, a refusal whose reason is
a failure fact (signed out, history too large, the agent stopped while starting, a Claude account
problem) or an older host lost its cause too, and said only "Orca will keep trying to send it."
for as long as a day. Each now keeps a cause-only sentence ("The agent is not signed in for the
selected account.", "This needs a newer Orca on the computer running this chat.") with no step,
in all six languages.
If the open chat's send had already settled the prompt when the launch picked up the shared send,
no ending reached the launch, which then waited forever. It now takes how that shared send ended.
When the first Stop's answer was lost, the press stayed quiet because Orca would send the Stop
again, but a refusal answering that resend was never said either, so the person was never told
the agent wasn't stopped. A refusal is now said on any attempt; a lost answer stays quiet only
while a resend is still owed.
The line under a message Orca keeps resending said things like "Send your message again" or
"Start a new chat" beside "Orca will keep trying to send it." Following that step while Orca
resends could send the message twice. The line now keeps only what stopped it (when the refusal
names a cause) and that Orca keeps trying:
- an "outcome unknown" the request threw says nothing, as the same answer returned does;
- a stage that couldn't be saved also says Orca keeps trying, which it does;
- a Stop that takes back the message the line is about clears the line.
Also corrects a comment: this build's replay makes the same stand-in record as an older host's,
for an accepted send whose row a new journal epoch dropped.
Two gaps in keeping notes out of another send while a saved chat message
carries them:
- The web client never installed the clearing that removes notes once a
chat sends them on, so after a Retry there the notes stayed listed. It is
now installed, once per renderer, by the background services both the
desktop and the web client load with App.
- A saved message carrying notes was thrown away only by this window's own
close. A chat closed from the phone, another window or while Orca was off,
or closed here without a known host, kept its notes held for good. A chat
the host stops listing (affirmed, and not a launch still starting or
failed) now has its queued messages thrown away once that sync lands, as
does a close that can name no host, so the notes come back.
The hold that keeps notes sent to a new agent out of the next send lived only
in memory. A reload while that chat had not yet sent them put the notes back
on the shelf while the chat's saved message still carried their text, so a
second "Send notes" sent them twice.
The staged message now saves the send keys of the notes it was built from.
The shelf treats a note as on its way while any saved message carries its key,
read from the saved outboxes on first use and kept current by the outbox's one
write funnel. When a message carrying notes is sent on (its own start, a Retry
or the re-check), those notes are cleared from their shelf; when it is thrown
away with its chat, they come back. Browser annotations sent to a new chat use
the same keys while the app runs. Running-agent sends keep their in-memory hold.
This replaces the per-message watch and outcome promise from the previous
change.
The launch caller was answered after the prompt's first attempt. When that attempt got no answer,
the chat kept resending the prompt, but the caller had already given up: the notes stayed on the
shelf and could be sent a second time. Each outbox entry's final ending (the host holds it, or it
came back, was withdrawn or was discarded) is now published per entry, and the launch settlement
waits through resends for it. Delivered fires onPromptDelivered once; anything else reports that
the chat already said what happened.
The outbox sender imported the launch prompt's shared in-flight map from the launch prompt module,
which imports the sender: a dependency cycle CI's lint rejects. The map now lives in
structured-agent-launch-prompt-in-flight-dispatches, which both import. No behaviour change.
A window that had already closed while the journal was still loading would arm a zero-delay timer
that re-armed itself every tick. Only windows still open arm one now; a closed one is settled when
the journal loads. Adds a test that a Stop-outrun send comes back once its window closes.
A repeated hand-back is the same text, so the repeat no longer adds leading spaces (the first
line's indentation is now kept). A launch prompt the host refused comes back to the chat, which
says why, so its caller is told the failure was already shown.
What one send attempt's answer may conclude, tightened where it could duplicate, lose, or strand
a message:
- A request the host turned away (unknown method, bad params, not authorized) proves no record
only on a first attempt; on a resend an earlier attempt may have landed, so it is sent again.
- "First attempt" is checked again when the answer arrives: another view that staged the same id
meanwhile makes it a resend.
- A reused message id on a resend is settled by the journal, as a conflict is.
- An older host's made-up record for a row its journal lost is the chat's only when a loaded row
shows it; otherwise the message comes back with "couldn't confirm" words instead of vanishing.
- A stage that can't be saved no longer hands the message back (an earlier attempt may have
landed): it waits, says "Couldn't save your message." once, and is tried again.
- When a resend stays unanswered because of a refusal Orca can't trust, the chat line says why
once, followed by "Orca will keep trying to send it.", and clears once the message lands.
- Sends a Stop outran, and messages an older build left, end when the host's window for their id
closes, even if nothing else moves by then.
- The queue stays held through the capability check, so a later send can't overtake the head.
- A launch prompt the chat handed back or keeps sending no longer also makes the caller toast
"could not be sent" with a copy button.
- A newer message used to make Orca give up on a Stop's outrun sends even while that Stop's own
request was still out, so a send could come back as "couldn't confirm" before the Stop's real
answer arrived. It now waits for the request to end.
- The first press no longer shows "The agent wasn't stopped." for a lost answer that Orca is about
to resend; it still does when nothing will resend it, and always for a refusal.
- This app's own sends are compared with the press by when they were queued, on this machine's
clock; the comment now says another client's are compared by the time in their id.
Two ways a Stop could leave messages stuck on "Sending…":
- the resend timer picked the first message in doubt even when it was one the Stop outran (never
resent), so a later message in doubt behind it was never resent either; it now resends the
message the queue is actually waiting on;
- a Stop answered while the agent was idle writes nothing to the journal, so its answer's position
was one the chat had already read, and the settling step only ran when the journal moved. It now
also runs when the outbox changes, so the outrun send comes back to the composer at once.
A message handed back to the composer was dropped whenever its text appeared anywhere in the
draft ("go" inside a longer sentence), and its first line lost its indentation. It is now skipped
only when the draft is that text or ends with it after a blank line, and only the end is trimmed.
A send now ends only by what the host said: it holds a record (the host's row shows the message
from then on), it proved there is no record (the text goes back into the conversation's draft and
the reason is said once), or nothing answered yet (the same id goes again, quietly, as "Sending…").
One shared settlement decides it for the open chat and a launch prompt alike.
Removed: the Retry control and its id rotation, the rejected and held-for-Retry outbox states, the
Stop latch, the per-window failure memory, the parked "unknown" entry, the launch prompt's own send
path, and the error-text regex (errors are read by code). A Stop stamps a send already on its way
with the Stop's own id; the Stop's answer settles it. Drafts of a structured chat are keyed by the
conversation and survive their tab closing. Entries older builds left waiting for a Retry are
settled once the journal loads, never sent again.
The row keeps #24606's "Sending…" in the time's slot and #24918's muted not-sent line; the notices
keep both: an entry still sending reads as sending, and a recorded rejection no entry carries reads
as not sent.
Notes sent to a new agent were released back to the shelf as soon as the
chat's start failed, while that failed chat kept the same text staged for its
own Retry. Re-sending and pressing Retry put the notes in two chats, and Retry
alone left delivered notes listed as unsent.
For a new chat, the notes now follow the prompt it staged: held while that
message is in the chat's outbox, cleared from the shelf when any dispatch
(Retry or re-check included) sends it on, and back on the shelf when the
chat is closed and its outbox thrown away. A paired server's chat is found
once its start settles. Running-agent sends keep their own result.
While notes are only on their way, the send button reads "Sending…" rather
than "All notes sent".
When every note or annotation in a send is held by an earlier send still in
flight, the built prompt is empty. The notes menu already disabled its
trigger then, but its New agent rows still started an agent with no text,
the annotation Send buttons still opened, and a sidebar agent row could be
sent an empty prompt. The New agent rows, the annotation Send buttons and the
sidebar send now offer nothing when there is nothing to send.
Notes sent to an agent stayed in the notes shelf until their chat delivered
them, so a second "Send notes" made while the first new chat was still
starting collected the first notes again. With every different request now
opening its own chat, those notes reached two chats.
When notes or browser annotations are handed to a send (a new agent, a
running agent from the menu, or a sidebar agent row), they are held in
memory against that send's own delivery result and left out of the next
send. Delivered notes are removed as before; a failed, refused or
undelivered send releases the hold, so they come back for the next send.
The notes menu now builds each scope's prompt from the notes it will send.
A blank chat that is still starting was claimable by the first request with
text even after its user had sent a message into it or typed into its
composer, so notes or a Fix with AI prompt could land in a conversation the
user had already started. Emptiness is now read from what the chat holds:
nothing in its outbox and no text in its composer draft. A chat its user has
used stays theirs, and the request opens a new chat.
If the claiming text cannot be saved, the claim is not recorded: the request
falls through to a new launch, whose save failure shows on that chat as for
any new launch, instead of reporting a failure nothing showed.
A grant also covers its path's own real path, so a stored spelling that is
itself a link inside the paste folder could have granted an outside file
while the path as restored reached a real paste. The stored spelling is
now granted only when it resolves to the same file; otherwise only the real
path is, and the preview falls back to the generic icon.
An id the ledger refuses (expired, conflict, invalid, capacity) is answered as admission would,
with no journal read, preparation or write, so a closed chat or a read-only store answers it too.
A re-read after the replay open that comes back refused returns that refusal.
Whether a /clear is in flight is read when a send arrives and applied in the send's preparation
for a first run only: an id the ledger holds by the send's turn, including one whose earlier
attempt was queued ahead of the clear, is answered from its record. MutationPlan makes
settlesWithWrite and settledOutcome exclusive; the capability text no longer promises a refused
id never sends.
Restore granted only a paste's real path, but the composer reads the
preview by the path its draft stored, so with user data reached through a
link the restored chip showed a generic icon. The stored spelling is
granted too; it is already proven to sit inside the paste folder. A test
also pins that a composer paste asks for the paste folder.
A blank chat that is still starting (a + pick, the empty-workspace default
chat) is empty, so the first request with text, such as notes sent to a new
agent, now goes into it instead of opening a second chat beside it. That
request becomes the chat's own request: an identical repeat joins and is sent
once, and any other request opens a new chat.
Move the logic that decides which launch a start joins out of the launch
registry into its own module, so the registry stays under the line limit
once #24904 lands beside it. Pin that a repeat arriving while the opening
text is still sending is sent once.
Every local clipboard image save had moved into the paste folder, whose
macOS path has a space, so a screenshot pasted into a terminal or sent to
a terminal-backed agent no longer attached. Only a native-chat composer
paste goes there now, through an optional field on the existing save
call; terminal, editor and phone pastes stay in the system temp folder as
before. An image path sent to an agent's input is escaped the way a
dropped image is.
The restore re-grant now grants only the file's real path, and only when
both the real path and the path as written are inside the paste folder,
which must not itself be a link. The sweep deletes only Orca's paste
files and skips a linked folder.
A placeholder now shows the image's name and a short hint on the chip
itself, "Attach again" for a file and "Not kept" for a pasted image, with
the longer explanation in its tooltip. The copy no longer says the image
wasn't saved: it says it couldn't be brought back, and for a pasted image
it asks only for removal, since a new paste can't match it. The send
button now says to remove the image to send.
Only an image the user attaches (picking, dropping or pasting) takes the
place of a placeholder with its file name. An image Stop gives back is
added beside it, so a different file with the same name no longer hides
the reminder.